mod_dav.c revision 89c7a19f9c47b03f00f622a979490c9bccb2ff03
/* 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
*
*
* 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"
enum {
DAV_ENABLED_UNSET = 0,
};
/* per-dir configuration */
typedef struct {
const char *provider_name;
const dav_provider *provider;
const char *dir;
int locktimeout;
int allow_depthinfinity;
} dav_dir_conf;
/* per-server configuration */
typedef struct {
int unused;
/* forward-declare for use in configuration lookup */
/* DAV methods */
enum {
DAV_M_BIND = 0,
};
static int dav_methods[DAV_M_LAST];
server_rec *s)
{
/* DBG0("dav_init_handler"); */
/* Register DAV methods */
ap_add_version_component(p, "DAV/2");
return OK;
}
{
/* ### this isn't used at the moment... */
return newconf;
}
{
#if 0
#endif
/* ### nothing to merge right now... */
return newconf;
}
{
/* NOTE: dir==NULL creates the default per-dir config */
/* clean up the directory to remove any trailing slash */
char *d;
apr_size_t l;
d = apr_pstrdup(p, dir);
l = strlen(d);
if (l > 1 && d[l - 1] == '/')
d[l - 1] = '\0';
}
return conf;
}
{
/* DBG3("dav_merge_dir_config: new=%08lx base=%08lx overrides=%08lx",
(long)newconf, (long)base, (long)overrides); */
"\"DAV Off\" cannot be used to turn off a subtree "
"of a DAV-enabled location.");
}
parent->provider_name) != 0) {
"A subtree cannot specify a different DAV provider "
"than its parent.");
}
}
return newconf;
}
{
/* 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 dav_get_provider(r)->locks;
}
{
return dav_get_provider(r)->propdb;
}
{
return dav_get_provider(r)->vsn;
}
{
return dav_get_provider(r)->binding;
}
{
return dav_get_provider(r)->search;
}
/*
* Command handler for the DAV directive, which is TAKE1.
*/
{
}
}
else {
}
/* lookup and cache the actual provider now */
/* by the time they use it, the provider should be loaded and
registered with us. */
"Unknown DAV provider: %s",
}
}
return NULL;
}
/*
* Command handler for the DAVDepthInfinity directive, which is FLAG.
*/
int arg)
{
if (arg)
else
return NULL;
}
/*
* Command handler for DAVMinTimeout directive, which is TAKE1
*/
const char *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)
*/
{
ap_set_content_type(r, "text/html; charset=ISO-8859-1");
/* begin the response now... */
ap_rvputs(r,
r->status_line,
&r->status_line[4],
body,
ap_psignature("<hr />\n", r),
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,
{
"<D:error xmlns:D=\"DAV:\"", r);
/* ### should move this namespace somewhere (with the others!) */
ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r);
}
ap_rprintf(r,
" xmlns:C=\"%s\">" DEBUG_CR
"<C:%s/>" DEBUG_CR,
}
else {
ap_rprintf(r,
">" DEBUG_CR
}
/* here's our mod_dav specific tag: */
ap_rprintf(r,
"<m:human-readable errcode=\"%d\">" DEBUG_CR
"%s" DEBUG_CR
"</m:human-readable>" DEBUG_CR,
}
/* 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.
*/
{
/* check the easy case... */
return e_uri;
/* there was a '&', so more work is needed... sigh. */
/*
* Note: this is a teeny bit of overkill since we know there are no
* '<' or '>' characters, but who cares.
*/
return apr_xml_quote_string(p, e_uri, 0);
}
/* Write a complete RESPONSE object out as a <DAV:repsonse> xml
element. Data is sent into brigade BB, which is auto-flushed into
OUTPUT filter stack. 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.]
*/
{
}
else {
}
}
DEBUG_CR "<D:href>",
"</D:href>" DEBUG_CR,
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.
*/
"<D:status>HTTP/1.1 ",
"</D:status>" DEBUG_CR,
NULL);
}
else {
/* assume this includes <propstat> and is quoted properly */
}
}
/*
* We supply the description, so we know it doesn't have to
*/
"<D:responsedescription>",
"</D:responsedescription>" DEBUG_CR,
NULL);
}
}
/* 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. */
request_rec *r, int status,
{
/* Set the correct status and Content-Type */
/* Send the headers and actual multistatus response now... */
"<D:multistatus xmlns:D=\"DAV:\"");
if (namespaces != NULL) {
int i;
for (i = namespaces->nelts; i--; ) {
}
}
}
/* Finish a multistatus response started by dav_begin_multistatus: */
{
apr_bucket *b;
/* indicate the end of the response body */
/* deliver whatever might be remaining in the brigade */
}
{
r->connection->bucket_alloc);
}
dav_finish_multistatus(r, bb);
}
/*
* dav_log_err()
*
* Write error information to the log.
*/
{
/* Log the errors */
/* ### should have a directive to log the first or all */
continue;
}
}
/*
* 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
*/
{
/* log the errors */
/* our error messages are safe; tell Apache this */
/* 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. */
return dav_error_response_tag(r, stackerr);
}
return DONE;
}
/* handy function for return values of methods that (may) create things */
int replaced)
{
const char *body;
}
/* 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 */
/* ### 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. */
}
/* ### move to dav_util? */
{
return def_depth;
}
return DAV_INFINITY;
}
return 0;
}
return 1;
}
/* The caller will return an HTTP_BAD_REQUEST. This will augment the
* default message that Apache provides. */
"An invalid Depth header was specified.");
return -1;
}
static int dav_get_overwrite(request_rec *r)
{
return 1; /* default is "T" */
}
return 0;
}
return 1;
}
/* The caller will return an HTTP_BAD_REQUEST. This will augment the
* default message that Apache provides. */
"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.
*/
{
/* if the request target can be overridden, get any target selector */
if (label_allowed) {
}
/* assert: conf->provider != NULL */
/* resolve the resource */
res_p);
"Could not fetch resource information.", err);
return err;
}
/* Note: this shouldn't happen, but just be sure... */
/* ### maybe use HTTP_INTERNAL_SERVER_ERROR */
apr_psprintf(r->pool,
"The provider did not define a "
"resource for %s.",
}
/* ### 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;
}
{
return NULL;
}
/* open the thing lazily */
}
static int dav_parse_range(request_rec *r,
{
const char *range_c;
char *range;
char *dash;
char *slash;
char *errp;
return 0;
/* malformed header. ignore it (per S14.16 of RFC2616) */
return 0;
}
/* ignore invalid ranges. (per S14.16 of RFC2616) */
|| *errp || *range_start < 0) {
return 0;
}
return 0;
}
if (*slash != '*') {
return 0;
}
}
/* we now have a valid range */
return 1;
}
/* handle the GET method */
static int dav_method_get(request_rec *r)
{
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.
*/
&resource);
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
/* set up the HTTP headers for the response */
"Unable to set up HTTP headers.",
err);
}
/* Handle conditional requests */
status = ap_meets_conditions(r);
if (status) {
return status;
}
if (r->header_only) {
return DONE;
}
/* okay... time to deliver the content */
r->output_filters)) != NULL) {
"Unable to deliver content.",
err);
}
return DONE;
}
static int dav_method_post(request_rec *r)
{
/* Ask repository module to resolve the resource */
&resource);
/* Note: depth == 0. Implies no need for a multistatus response. */
/* ### add a higher-level description? */
}
return DECLINED;
}
/* handle the PUT method */
static int dav_method_put(request_rec *r)
{
int resource_state;
const char *body;
int has_range;
/* Ask repository module to resolve the resource */
&resource);
/* If not a file or collection resource, PUT not allowed */
"Cannot create resource %s with PUT.",
}
/* Cannot PUT a collection */
if (resource->collection) {
return dav_error_response(r, HTTP_CONFLICT,
"Cannot PUT to a collection.");
}
/*
* 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.
*/
/* ### add a higher-level description? */
}
/* make sure the resource can be modified (if versioning repository) */
0 /* not parent_only */,
/* ### add a higher-level description? */
}
/* truncate and rewrite the file unless we see a Content-Range */
if (has_range) {
}
/* Create the new file in the repository */
/* ### assuming FORBIDDEN is probably not quite right... */
apr_psprintf(r->pool,
"Unable to PUT new contents for %s.",
err);
}
/* a range was provided. seek to the start */
}
apr_bucket *b;
int seen_eos = 0;
do {
if (rc != APR_SUCCESS) {
apr_psprintf(r->pool,
"Could not get next bucket "
"brigade (URI: %s)",
break;
}
for (b = APR_BRIGADE_FIRST(bb);
b != APR_BRIGADE_SENTINEL(bb);
b = APR_BUCKET_NEXT(b))
{
const char *data;
if (APR_BUCKET_IS_EOS(b)) {
seen_eos = 1;
break;
}
if (APR_BUCKET_IS_METADATA(b)) {
continue;
}
if (rc != APR_SUCCESS) {
apr_psprintf(r->pool,
"An error occurred while reading"
" the request body (URI: %s)",
break;
}
/* write whatever we read, until we see an error */
}
}
} while (!seen_eos);
/* no error during the write, but we hit one at close. use it. */
}
}
/*
* 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)
*/
}
/* restore modifiability of resources back to what they were */
0 /*unlock*/, &av_info);
/* check for errors now */
}
/* just log a warning */
"The PUT was successful, but there "
"was a problem automatically checking in "
"the resource or its parent collection.",
err2);
}
/* ### place the Content-Type and Content-Language into the propdb */
if (locks_hooks != NULL) {
/* The file creation was successful, but the locking failed. */
"The file was PUT successfully, but there "
"was a problem opening the lock database "
"which prevents inheriting locks from the "
"parent resources.",
err);
}
/* The file creation was successful, but the locking failed. */
"The file was PUT successfully, but there "
"was a problem updating its lock "
"information.",
err);
}
}
/* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
/* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
}
/* Use POOL to temporarily construct a dav_response object (from WRES
STATUS, and PROPSTATS) and stream it via WRES's ctx->brigade. */
int status,
{
dav_response resp = { 0 };
if (propstats) {
}
}
/* ### move this to dav_util? */
{
/* just drop some data into an dav_response */
if (propstats) {
}
}
/* handle the DELETE method */
static int dav_method_delete(request_rec *r)
{
int result;
int depth;
/* We don't use the request body right now, so torch it. */
return result;
}
/* Ask repository module to resolve the resource */
&resource);
/* 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).
*/
/* This supplies additional information for the default message. */
"Depth must be \"infinity\" for DELETE of a collection.");
return HTTP_BAD_REQUEST;
}
/* This supplies additional information for the default message. */
"Depth of \"1\" is not allowed for DELETE.");
return HTTP_BAD_REQUEST;
}
/*
** 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.
*/
apr_psprintf(r->pool,
"Could not DELETE %s due to a failed "
"precondition (e.g. locks).",
err);
}
/* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
* locked by the token(s) in the if_header.
*/
return result;
}
/* if versioned resource, make sure parent is checked out */
/* ### add a higher-level description? */
}
/* try to remove the resource */
/* restore writability of parent back to what it was */
0 /*unlock*/, &av_info);
/* check for errors now */
apr_psprintf(r->pool,
"Could not DELETE %s.",
err);
}
/* just log a warning */
"The DELETE was successful, but there "
"was a problem automatically checking in "
"the parent collection.",
err2);
}
/* ### 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 */
const apr_xml_elem *elem,
const apr_table_t *methods,
{
const apr_array_header_t *arr;
const apr_table_entry_t *elts;
char *s;
int i;
/* show all supported methods */
continue;
s = apr_psprintf(r->pool,
"<D:supported-method D:name=\"%s\"/>"
}
}
else {
/* check for support of specific methods */
/* go through attributes to find method name */
}
"A DAV:supported-method element "
"does not have a \"name\" attribute");
}
/* see if method is supported */
s = apr_psprintf(r->pool,
"<D:supported-method D:name=\"%s\"/>"
name);
}
}
}
}
return NULL;
}
/* generate DAV:supported-live-property-set OPTIONS response */
const dav_resource *resource,
const apr_xml_elem *elem,
{
/* open lock database, to report on supported lock properties */
/* ### should open read-only */
"The lock database could not be opened, "
"preventing the reporting of supported lock "
"properties.",
err);
}
/* open the property database (readonly) for the resource */
"The property database could not be opened, "
"preventing report of supported properties.",
err);
}
/* show all supported live properties */
}
else {
/* check for support of specific live property */
/* go through attributes to find name and namespace */
}
}
"A DAV:supported-live-property "
"element does not have a \"name\" "
"attribute");
break;
}
/* default namespace to DAV: */
nmspace = "DAV:";
/* check for support of property */
}
}
}
return err;
}
/* generate DAV:supported-report-set OPTIONS response */
const dav_resource *resource,
const apr_xml_elem *elem,
const dav_hooks_vsn *vsn_hooks,
{
char *s;
const dav_report_elem *reports;
const dav_report_elem *rp;
"DAV:supported-report-set could not be "
"determined due to a problem fetching the "
"available reports for this resource.",
err);
}
/* show all supported reports */
/* Note: we presume reports->namespace is
s = apr_psprintf(r->pool,
"<D:supported-report D:name=\"%s\" "
"D:namespace=\"%s\"/>" DEBUG_CR,
}
}
else {
/* check for support of specific report */
/* go through attributes to find name and namespace */
}
}
"A DAV:supported-report element "
"does not have a \"name\" attribute");
}
/* default namespace to DAV: */
nmspace = "DAV:";
/* Note: we presume reports->nmspace is
*/
s = apr_psprintf(r->pool,
"<D:supported-report "
"D:name=\"%s\" "
"D:namespace=\"%s\"/>"
break;
}
}
}
}
}
}
}
return NULL;
}
/* handle the SEARCH method */
static int dav_method_search(request_rec *r)
{
/* 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.
*/
&resource);
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
/* set up the HTTP headers for the response */
"Unable to set up HTTP headers.",
err);
}
if (r->header_only) {
return DONE;
}
/* okay... time to search the content */
/* Let's validate XML and process walk function
* in the hook function
*/
/* ### add a higher-level description? */
}
/* We have results in multi_status */
/* Should I pass namespace?? */
return DONE;
}
/* handle the OPTIONS method */
static int dav_method_options(request_rec *r)
{
const char *dav_level;
char *allow;
char *s;
const apr_array_header_t *arr;
const apr_table_entry_t *elts;
apr_text_header vsn_options = { 0 };
apr_text_header body = { 0 };
apr_text *t;
int text_size;
int result;
int i;
const apr_xml_elem *elem;
/* resolve the resource */
&resource);
/* parse any request body */
return result;
}
/* note: doc == NULL if no request body */
"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 header additions registered by external modules */
const dav_options_provider *options =
apr_text_header hoptions = { 0 };
}
}
/* ###
* 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.
* ###
*/
/*
* If there is a versioning provider, generate DAV headers
* for versioning options.
*/
}
/*
* 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.
*/
}
/* this tells MSFT products to skip looking for FrontPage extensions */
/*
* 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.
*/
/* ### take into account resource type */
switch (dav_get_resource_state(r, resource))
{
case DAV_RESOURCE_EXISTS:
/* resource exists */
if (!resource->collection)
if (locks_hooks != NULL) {
}
break;
case DAV_RESOURCE_LOCK_NULL:
/* resource is lock-null. */
if (locks_hooks != NULL) {
}
break;
case DAV_RESOURCE_NULL:
/* resource is null. */
if (locks_hooks != NULL)
break;
default:
/* ### internal error! */
break;
}
/* If there is a versioning provider, add versioning methods */
}
}
/* ### we might not support this DeltaV option */
}
}
else {
}
}
/* If there is a bindings provider, see if resource is bindable */
if (binding_hooks != NULL
}
/* If there is a search provider, set SEARCH in option */
if (search_hooks != NULL) {
}
/* additional methods registered by external modules */
const dav_options_provider *options =
apr_text_header hoptions = { 0 };
}
}
/* Generate the Allow header */
text_size = 0;
/* first, compute total length */
continue;
/* add 1 for comma or null */
}
continue;
if (s != allow)
*s++ = ',';
s += strlen(s);
}
/* 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
}
}
/* if there was no request body, then there is no response body */
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 */
/* check for something we recognize first */
int core_option = 0;
core_option = 1;
}
core_option = 1;
}
core_option = 1;
}
}
/* if unrecognized option, pass to versioning provider */
!= NULL) {
}
}
}
/* send the options response */
/* send the headers and response body */
"<D:options-response xmlns:D=\"DAV:\">" DEBUG_CR, r);
/* we've sent everything necessary to the client. */
return DONE;
}
{
const apr_xml_elem *elem;
apr_text_header hdr = { 0 };
/* just return if we built the thing already */
return;
}
"<D:propstat>" DEBUG_CR
"<D:prop>" DEBUG_CR);
}
"</D:prop>" DEBUG_CR
"<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
"</D:propstat>" DEBUG_CR);
}
{
dav_get_props_result propstats = { 0 };
/*
** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
** dav_get_allprops() does not need to do namespace translation,
** we're okay.
**
** Note: we cast to lose the "const". The propdb won't try to change
** the resource, however, since we are opening readonly.
*/
/* ### do something with err! */
dav_get_props_result badprops = { 0 };
/* some props were expected on this collection/resource */
}
else {
/* no props on this collection/resource */
}
return NULL;
}
/* ### what to do about closing the propdb on server failure? */
}
else {
}
/* 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. */
return NULL;
}
/* handle the PROPFIND method */
static int dav_method_propfind(request_rec *r)
{
int depth;
int result;
dav_walker_ctx ctx = { { 0 } };
/* Ask repository module to resolve the resource */
&resource);
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
/* dav_get_depth() supplies additional information for the
* default message. */
return HTTP_BAD_REQUEST;
}
&dav_module);
/* default is to DISALLOW these requests */
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)));
}
}
return result;
}
/* note: doc == NULL if no request body */
/* This supplies additional information for the default message. */
"The \"propfind\" element was not found.");
return HTTP_BAD_REQUEST;
}
/* ### validate that only one of these three elements is present */
/* note: no request body implies allprop */
}
}
}
else {
/* "propfind" element must have one of the above three children */
/* This supplies additional information for the default message. */
"The \"propfind\" element does not contain one of "
"the required child elements (the specific command).");
return HTTP_BAD_REQUEST;
}
ctx.r = r;
/* ### should open read-only */
"The lock database could not be opened, "
"preventing access to the various lock "
"properties for the PROPFIND.",
err);
}
/* if we have a lock database, then we can walk locknull resources */
}
/* 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. */
/* Have the provider walk the resource. */
}
/* 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. */
"Provider encountered an error while streaming"
" a multistatus PROPFIND response.", err);
return DONE;
}
/* the response has been sent. */
return DONE;
}
{
apr_text_header hdr = { 0 };
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>");
/* nothing was assigned here yet, so make it a 424 */
if (err424_set == NULL)
"Attempted DAV:set operation "
"could not be completed due "
"to other errors.");
}
if (err424_delete == NULL)
"Attempted DAV:remove "
"operation could not be "
"completed due to other "
"errors.");
}
}
s = apr_psprintf(p,
"<D:status>"
"HTTP/1.1 %d (status)"
"</D:status>" DEBUG_CR,
apr_text_append(p, &hdr, s);
/* ### we should use compute_desc if necessary... */
}
}
}
{
apr_text_header hdr = { 0 };
/*
* ### 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,
"</D:prop>" DEBUG_CR
"<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
"</D:propstat>" DEBUG_CR);
}
{
}
/*
* 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.
*/
int reverse)
{
if (reverse)
ctx += i;
while (i--) {
if (reverse)
--ctx;
return 1;
}
if (!reverse)
++ctx;
}
return 0;
}
/* handle the PROPPATCH method */
static int dav_method_proppatch(request_rec *r)
{
int result;
int failure = 0;
dav_response resp = { 0 };
/* Ask repository module to resolve the resource */
&resource);
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
return result;
}
/* note: doc == NULL if no request body */
/* This supplies additional information for the default message. */
"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. */
/* ### add a higher-level description? */
}
/* make sure the resource can be modified (if versioning repository) */
0 /* not parent_only */,
/* ### add a higher-level description? */
}
/* undo any auto-checkout */
apr_psprintf(r->pool,
"Could not open the property "
"database for %s.",
err);
}
/* ### what to do about closing the propdb on server failure? */
/* ### validate "live" properties */
/* set up an array to hold property operation contexts */
/* do a first pass to ensure that all "remove" properties exist */
int is_remove;
continue;
}
/* undo any auto-checkout */
/* This supplies additional information for the default message. */
"A \"prop\" element is missing inside "
"the propertyupdate command.");
return HTTP_BAD_REQUEST;
}
ctx->r = r; /* for later use by dav_prop_log_errors() */
if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
failure = 1;
}
}
}
/* execute all of the operations */
failure = 1;
}
if (failure) {
}
else {
}
/* make sure this gets closed! */
/* complete any auto-versioning */
/* log any errors that occurred */
/* ### should probably use something new to pass along this text... */
/* 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). */
/* make sure to set the Apache request fields properly. */
r->read_body = REQUEST_NO_BODY;
r->read_chunked = 0;
r->remaining = 0;
if (tenc) {
/* Use this instead of Apache's default error string */
"Unknown Transfer-Encoding %s", tenc);
return HTTP_NOT_IMPLEMENTED;
}
r->read_chunked = 1;
}
else if (lenp) {
++pos;
}
if (*pos != '\0') {
/* This supplies additional information for the default message. */
"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)
{
int resource_state;
int result;
/* handle the request body */
/* ### this may move lower once we start processing bodies */
return result;
}
/* Ask repository module to resolve the resource */
&resource);
/* 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;
}
/*
* 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.
*/
/* ### add a higher-level description? */
}
/* if versioned resource, make sure parent is checked out */
/* ### add a higher-level description? */
}
/* try to create the collection */
/* restore modifiability of parent back to what it was */
0 /*unlock*/, &av_info);
/* check for errors now */
}
/* just log a warning */
"The MKCOL was successful, but there "
"was a problem automatically checking in "
"the parent collection.",
err2);
}
if (locks_hooks != NULL) {
/* The directory creation was successful, but the locking failed. */
"The MKCOL was successful, but there "
"was a problem opening the lock database "
"which prevents inheriting locks from the "
"parent resources.",
err);
}
/* The dir creation was successful, but the locking failed. */
"The MKCOL was successful, but there "
"was a problem updating its lock "
"information.",
err);
}
}
/* return an appropriate response (HTTP_CREATED) */
}
/* handle the COPY and MOVE methods */
{
dav_auto_version_info src_av_info = { 0 };
dav_auto_version_info dst_av_info = { 0 };
const char *body;
const char *dest;
int is_dir;
int overwrite;
int depth;
int result;
int replace_dest;
int resnew_state;
/* Ask repository module to resolve the resource */
0 /* use_checked_in */, &resource);
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
}
/* get the destination URI */
/* Look in headers provided by Netscape's Roaming Profiles */
}
/* This supplies additional information for the default message. */
"The request is missing a Destination header.");
return HTTP_BAD_REQUEST;
}
/* This supplies additional information for the default message. */
return HTTP_BAD_REQUEST;
}
/* ### this assumes that dav_lookup_uri() only generates a status
* ### that Apache can provide a status line for!! */
}
"WWW-Authenticate");
/* propagate the WWW-Authorization header up from the
* subreq so the client sees it. */
}
/* ### how best to report this... */
"Destination URI had an error.");
}
return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED,
"DAV not enabled for Destination URI.");
}
/* Resolve destination resource */
0 /* use_checked_in */, &resnew);
/* are the two resources handled by the same repository? */
/* ### 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. */
/* 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? */
/* Supply some text for the error response body. */
return dav_error_response(r, HTTP_FORBIDDEN,
"Source and Destination URIs are the same.");
}
/* get and parse the Depth header value. "0" and "infinity" are legal. */
/* 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. */
"Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
return HTTP_BAD_REQUEST;
}
/* This supplies additional information for the default message. */
"Depth must be \"infinity\" when moving a collection.");
return HTTP_BAD_REQUEST;
}
/*
* Check If-Headers and existing locks for each resource in the source
* if we are performing a MOVE. 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, since 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 (is_move
apr_psprintf(r->pool,
"Could not MOVE %s due to a failed "
"precondition on the source "
"(e.g. locks).",
err);
}
/*
* Check If-Headers and existing locks for destination. Note that we
* use depth==infinity since the target (hierarchy) will be deleted
*
* Note that we are overwriting the target, which implies a DELETE, so
* will return a 424 error if any of the validations fail.
* (see dav_method_delete() for more information)
*/
apr_psprintf(r->pool,
"failed precondition on the "
"destination (e.g. locks).",
err);
}
if (is_dir
&& depth == DAV_INFINITY
/* Supply some text for the error response body. */
return dav_error_response(r, HTTP_FORBIDDEN,
"Source collection contains the "
"Destination.");
}
if (is_dir
/* The destination must exist (since it contains the source), and
* a condition above implies Overwrite==T. Obviously, we cannot
* 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 */
return result;
}
/* ### add a higher-level description? */
}
/* 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 :-(
*
* ### since a failure after this would have removed locks (technically,
* ### this is okay to do, but really...)
*/
/* ### this is wrong! it blasts direct locks on parent resources */
/* ### pass lockdb! */
}
/* if this is a move, then the source parent collection will be modified */
if (is_move) {
&src_av_info)) != NULL) {
/* ### add a higher-level description? */
}
}
/*
* Remember the initial state of the destination, so the lock system
* can be notified as to how it changed.
*/
/* 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.
*/
replace_dest = 0;
replace_dest = 1;
replace_dest = 1;
replace_dest = 1;
else
replace_dest = 0;
/* If the destination must be created or replaced,
* make sure the parent collection is writable
*/
&dst_av_info)) != NULL) {
/* could not make destination writable:
* if move, restore state of source parent
*/
if (is_move) {
0 /*unlock*/, &src_av_info);
}
/* ### add a higher-level description? */
}
}
/* 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 destination is being replaced, remove it first
*/
if (replace_dest)
if (is_move)
else
}
/* perform any auto-versioning cleanup */
0 /*unlock*/, &dst_av_info);
if (is_move) {
0 /*unlock*/, &src_av_info);
}
else
apr_psprintf(r->pool,
err);
}
/* check for errors from auto-versioning */
/* just log a warning */
"problem automatically checking in the "
"source parent collection.",
err2);
}
/* just log a warning */
"problem automatically checking in the "
"destination or its parent collection.",
err3);
}
/* propagate any indirect locks at the target */
"was a problem updating the lock "
"information.",
err);
}
}
/* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
}
/* dav_method_lock: Handler to implement the DAV LOCK method
* Returns appropriate HTTP_* response.
*/
static int dav_method_lock(request_rec *r)
{
const dav_hooks_locks *locks_hooks;
int result;
int depth;
int new_lock_request = 0;
int resource_state;
/* If no locks provider, decline the request */
if (locks_hooks == NULL)
return DECLINED;
return result;
"Depth must be 0 or \"infinity\" for LOCK.");
return HTTP_BAD_REQUEST;
}
/* Ask repository module to resolve the resource */
&resource);
/* Check if parent collection exists */
/* ### add a higher-level description? */
}
apr_psprintf(r->pool,
"The parent resource of %s does not "
"exist or is not a collection.",
}
/*
* Open writable. Unless an error occurs, we'll be
* writing into the database.
*/
/* ### add a higher-level description? */
}
/* ### add a higher-level description to err? */
goto error;
}
new_lock_request = 1;
}
/*
* Check If-Headers and existing locks.
*
* If this will create a locknull resource, then the LOCK will affect
* validate the parent resource's conditions.
*/
apr_psprintf(r->pool,
"Could not LOCK %s due to a failed "
"precondition (e.g. other locks).",
err);
goto error;
}
if (new_lock_request == 0) {
/*
* 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.
*/
apr_psprintf(r->pool,
"The lock refresh for %s failed "
"because no lock tokens were "
"specified in an \"If:\" "
"header.",
err);
goto error;
}
dav_get_timeout(r),
/* ### add a higher-level description to err? */
goto error;
}
} else {
/* New lock request */
char *locktoken_txt;
&dav_module);
/* apply lower bound (if any) from DAVMinTimeout directive */
/* ### add a higher-level description to err? */
goto error;
}
">", NULL);
}
else {
ap_rprintf(r,
"<D:lockdiscovery>" DEBUG_CR
"%s" DEBUG_CR
"</D:lockdiscovery>" DEBUG_CR,
}
ap_rputs("</D:prop>", r);
/* the response has been sent. */
return DONE;
}
/* dav_method_unlock: Handler to implement the DAV UNLOCK method
* Returns appropriate HTTP_* response.
*/
static int dav_method_unlock(request_rec *r)
{
const dav_hooks_locks *locks_hooks;
int result;
const char *const_locktoken_txt;
char *locktoken_txt;
int resource_state;
/* If no locks provider, decline the request */
if (locks_hooks == NULL)
return DECLINED;
"Lock-Token")) == NULL) {
"Unlock failed (%s): "
"No Lock-Token specified in header", r->filename);
return HTTP_BAD_REQUEST;
}
if (locktoken_txt[0] != '<') {
/* ### should provide more specifics... */
return HTTP_BAD_REQUEST;
}
/* ### should provide more specifics... */
return HTTP_BAD_REQUEST;
}
apr_psprintf(r->pool,
"The UNLOCK on %s failed -- an "
"invalid lock token was specified "
"in the \"If:\" header.",
err);
}
/* Ask repository module to resolve the resource */
&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.
*/
/* ### add a higher-level description? */
}
/* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
* _all_ resources locked by locktoken are released. It does not say
* resource has to be the root of an infinte lock. Thus, an UNLOCK
* on any part of an infinte lock will remove the lock on all resources.
*
* For us, if r->filename represents an indirect lock (part of an infinity lock),
* we must actually perform an UNLOCK on the direct lock for this resource.
*/
return result;
}
return HTTP_NO_CONTENT;
}
static int dav_method_vsn_control(request_rec *r)
{
int resource_state;
int result;
/* if no versioning provider, decline the request */
return DECLINED;
/* ask repository module to resolve the resource */
&resource);
/* remember the pre-creation resource state */
/* parse the request body (may be a version-control element) */
return result;
}
/* note: doc == NULL if no request body */
const apr_xml_elem *child;
"The request body does not contain "
"a \"version-control\" element.");
return HTTP_BAD_REQUEST;
}
/* get the version URI */
"The \"version-control\" element does not contain "
"a \"version\" element.");
return HTTP_BAD_REQUEST;
}
"The \"version\" element does not contain "
"an \"href\" element.");
return HTTP_BAD_REQUEST;
}
/* get version URI */
if (tsize == 0) {
"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 */
"<DAV:initial-version-required/>");
}
/* cannot add resource to existing version history */
"<DAV:cannot-add-to-existing-history/>");
}
/* resource must be unversioned and versionable, or version selector */
"<DAV:must-be-versionable/>");
}
/* the DeltaV spec says if resource is a version selector,
* then VERSION-CONTROL is a no-op
*/
/* set the Cache-Control header, per the spec */
/* 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 in versioned collection, make sure parent is checked out */
}
/* attempt to version-control the resource */
apr_psprintf(r->pool,
"Could not VERSION-CONTROL resource %s.",
err);
}
/* revert writability of parent directory */
/* just log a warning */
"The VERSION-CONTROL was successful, but there "
"was a problem automatically checking in "
"the parent collection.",
err);
}
/* if the resource is lockable, let lock system know of new resource */
if (locks_hooks != NULL
/* The resource creation was successful, but the locking failed. */
"The VERSION-CONTROL was successful, but there "
"was a problem opening the lock database "
"which prevents inheriting locks from the "
"parent resources.",
err);
}
/* The dir creation was successful, but the locking failed. */
"The VERSION-CONTROL was successful, but there "
"was a problem updating its lock "
"information.",
err);
}
}
/* set the Cache-Control header, per the spec */
/* return an appropriate response (HTTP_CREATED) */
}
/* handle the CHECKOUT method */
static int dav_method_checkout(request_rec *r)
{
int result;
int apply_to_vsn = 0;
int is_unreserved = 0;
int is_fork_ok = 0;
int create_activity = 0;
/* If no versioning provider, decline the request */
return DECLINED;
return result;
const apr_xml_elem *aset;
/* This supplies additional information for the default msg. */
"The request body, if present, must be a "
"DAV:checkout element.");
return HTTP_BAD_REQUEST;
}
/* ### 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;
}
create_activity = 1;
}
else {
const char *href;
1 /* strip_white */);
}
}
if (activities->nelts == 0) {
/* no href's is a DTD violation:
<!ELEMENT activity-set (href+ | new)>
*/
/* This supplies additional info for the default msg. */
"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 */
/* 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.
*/
return dav_error_response(r, HTTP_CONFLICT,
"Cannot checkout this type of resource.");
}
return dav_error_response(r, HTTP_CONFLICT,
"Cannot checkout unversioned resource.");
}
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 */
&working_resource)) != NULL) {
apr_psprintf(r->pool,
"Could not CHECKOUT resource %s.",
err);
}
/* set the Cache-Control header, per the spec */
/* 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;
}
}
/* handle the UNCHECKOUT method */
static int dav_method_uncheckout(request_rec *r)
{
int result;
/* If no versioning provider, decline the request */
return DECLINED;
return result;
}
/* Ask repository module to resolve the resource */
&resource);
/* 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.
*/
return dav_error_response(r, HTTP_CONFLICT,
"Cannot uncheckout this type of resource.");
}
return dav_error_response(r, HTTP_CONFLICT,
"Cannot uncheckout unversioned resource.");
}
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 */
apr_psprintf(r->pool,
"Could not UNCHECKOUT resource %s.",
err);
}
/* no body */
ap_set_content_length(r, 0);
return DONE;
}
/* handle the CHECKIN method */
static int dav_method_checkin(request_rec *r)
{
int result;
int keep_checked_out = 0;
/* If no versioning provider, decline the request */
return DECLINED;
return result;
/* This supplies additional information for the default msg. */
"The request body, if present, must be a "
"DAV:checkin element.");
return HTTP_BAD_REQUEST;
}
}
/* Ask repository module to resolve the resource */
&resource);
/* 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.
*/
return dav_error_response(r, HTTP_CONFLICT,
"Cannot checkin this type of resource.");
}
return dav_error_response(r, HTTP_CONFLICT,
"Cannot checkin unversioned resource.");
}
return dav_error_response(r, HTTP_CONFLICT,
"The resource is not checked out.");
}
/* ### do lock checks, once behavior is defined */
/* Do the checkin */
!= NULL) {
apr_psprintf(r->pool,
"Could not CHECKIN resource %s.",
err);
}
}
static int dav_method_update(request_rec *r)
{
int is_label = 0;
int depth;
int result;
const char *target;
/* If no versioning provider, or UPDATE not supported,
* decline the request */
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 */
return result;
}
/* This supplies additional information for the default message. */
"The request body does not contain "
"an \"update\" element.");
return HTTP_BAD_REQUEST;
}
/* check for label-name or version element, but not both */
is_label = 1;
/* get the href element */
"The version element does not contain "
"an \"href\" element.");
return HTTP_BAD_REQUEST;
}
}
else {
"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 */
"Depth must be zero for UPDATE with a version");
return HTTP_BAD_REQUEST;
}
/* get the target value (a label or a version URI) */
if (tsize == 0) {
"A \"label-name\" or \"href\" element does not contain "
"any content.");
return HTTP_BAD_REQUEST;
}
/* Ask repository module to resolve the resource */
&resource);
/* 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)
*/
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) {
/* This supplies additional information for the default message. */
return HTTP_BAD_REQUEST;
}
/* ### this assumes that dav_lookup_uri() only generates a status
* ### that Apache can provide a status line for!! */
}
/* ### how best to report this... */
"Version URI had an error.");
}
/* resolve version resource */
0 /* use_checked_in */, &version);
/* NULL out target, since we're using a version resource */
}
/* do the UPDATE operation */
apr_psprintf(r->pool,
"Could not UPDATE %s.",
err);
}
/* set the Cache-Control header, per the spec */
/* no body */
ap_set_content_length(r, 0);
return DONE;
}
/* context maintained during LABEL treewalk */
typedef struct dav_label_walker_ctx
{
/* input: */
/* 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;
{
/* 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)
*/
"<DAV:must-be-version-or-version-selector/>");
}
"<DAV:must-not-be-checked-out/>");
}
else {
/* do the label operation */
else
}
/* ### need utility routine to add response with description? */
}
return NULL;
}
static int dav_method_label(request_rec *r)
{
int depth;
int result;
dav_label_walker_ctx ctx = { { 0 } };
/* If no versioning provider, or the provider doesn't support
* labels, decline the request */
return DECLINED;
/* Ask repository module to resolve the resource */
&resource);
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
if ((depth = dav_get_depth(r, 0)) < 0) {
/* dav_get_depth() supplies additional information for the
* default message. */
return HTTP_BAD_REQUEST;
}
/* parse the request body */
return result;
}
/* This supplies additional information for the default message. */
"The request body does not contain "
"a \"label\" element.");
return HTTP_BAD_REQUEST;
}
/* check for add, set, or remove element */
}
}
}
else {
"The \"label\" element does not contain "
"an \"add\", \"set\", or \"remove\" element.");
return HTTP_BAD_REQUEST;
}
/* get the label string */
"The label command element does not contain "
"a \"label-name\" element.");
return HTTP_BAD_REQUEST;
}
if (tsize == 0) {
"A \"label-name\" element does not contain "
"a label name.");
return HTTP_BAD_REQUEST;
}
/* do the label operation walk */
/* some sort of error occurred which terminated the walk */
"The LABEL operation was terminated prematurely.",
err);
}
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) {
multi_status->desc);
multi_status = NULL;
}
else {
"Errors occurred during the LABEL operation.");
}
}
/* set the Cache-Control header, per the spec */
/* no body */
ap_set_content_length(r, 0);
return DONE;
}
static int dav_method_report(request_rec *r)
{
int result;
int label_allowed;
/* If no versioning provider, decline the request */
return DECLINED;
return result;
/* This supplies additional information for the default msg. */
"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.
*/
&resource);
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
/* set up defaults for the report response */
/* run report hook */
r->output_filters)) != NULL) {
if (! r->sent_bodyct)
/* No data has been sent to client yet; throw normal error. */
/* 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. */
"Provider encountered an error while streaming"
" a REPORT response.", err);
return DONE;
}
return DONE;
}
static int dav_method_make_workspace(request_rec *r)
{
int result;
/* if no versioning provider, or the provider does not support workspaces,
* decline the request
*/
return DECLINED;
/* ask repository module to resolve the resource */
&resource);
/* parse the request body (must be a mkworkspace element) */
return result;
}
"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 */
"<DAV:resource-must-be-null/>");
}
/* ### what about locking? */
/* attempt to create the workspace */
apr_psprintf(r->pool,
"Could not create workspace %s.",
err);
}
/* set the Cache-Control header, per the spec */
/* return an appropriate response (HTTP_CREATED) */
}
static int dav_method_make_activity(request_rec *r)
{
int result;
/* if no versioning provider, or the provider does not support activities,
* decline the request
*/
return DECLINED;
/* ask repository module to resolve the resource */
&resource);
/* MKACTIVITY does not have a defined request body. */
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 */
"<DAV:resource-must-be-null/>");
}
/* the provider must say whether the resource can be created as
an activity, i.e. whether the location is ok. */
"<DAV:activity-location-ok/>");
}
/* ### what about locking? */
/* attempt to create the activity */
apr_psprintf(r->pool,
"Could not create activity %s.",
err);
}
/* set the Cache-Control header, per the spec */
/* return an appropriate response (HTTP_CREATED) */
}
static int dav_method_baseline_control(request_rec *r)
{
/* ### */
return HTTP_METHOD_NOT_ALLOWED;
}
static int dav_method_merge(request_rec *r)
{
int result;
const char *source;
int no_auto_merge;
int no_checkout;
/* If no versioning provider, decline the request */
return DECLINED;
return result;
/* This supplies additional information for the default msg. */
"The request body must be present and must be a "
"DAV:merge element.");
return HTTP_BAD_REQUEST;
}
/* This supplies additional information for the default msg. */
"The DAV:merge element must contain a DAV:source "
"element.");
return HTTP_BAD_REQUEST;
}
/* This supplies additional information for the default msg. */
"The DAV:source element must contain a DAV:href "
"element.");
return HTTP_BAD_REQUEST;
}
/* get a subrequest for the source, so that we can get a dav_resource
for that source. */
/* This supplies additional information for the default message. */
return HTTP_BAD_REQUEST;
}
/* ### this assumes that dav_lookup_uri() only generates a status
* ### that Apache can provide a status line for!! */
}
/* ### how best to report this... */
"Merge source URI had an error.");
}
0 /* use_checked_in */, &source_resource);
/* ### 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 */
&resource);
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
/* ### do lock checks, once behavior is defined */
/* set the Cache-Control header, per the spec */
/* ### correct? */
/* 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. */
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. */
r->output_filters)) != NULL) {
/* ### is err->status the right error here? */
apr_psprintf(r->pool,
"Could not MERGE resource \"%s\" "
"into \"%s\".",
err);
}
/* 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)
{
const char *dest;
int overwrite;
/* If no bindings provider, decline the request */
if (binding_hooks == NULL)
return DECLINED;
/* Ask repository module to resolve the resource */
&resource);
/* Apache will supply a default error for this. */
return HTTP_NOT_FOUND;
}
/* get the destination URI */
/* This supplies additional information for the default message. */
"The request is missing a Destination header.");
return HTTP_BAD_REQUEST;
}
/* This supplies additional information for the default message. */
return HTTP_BAD_REQUEST;
}
/* ### 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!! */
}
/* ### how best to report this... */
"Destination URI had an error.");
}
/* resolve binding resource */
0 /* use_checked_in */, &binding);
/* are the two resources handled by the same repository? */
/* ### 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. */
return dav_error_response(r, HTTP_PRECONDITION_FAILED,
"Destination is not empty and "
"Overwrite is not \"T\"");
}
/* are the source and destination the same? */
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
*
* Note that we are overwriting the target, which implies a DELETE, so
* will return a 424 error if any of the validations fail.
* (see dav_method_delete() for more information)
*/
apr_psprintf(r->pool,
"Could not BIND %s due to a "
"failed precondition on the "
"destination (e.g. locks).",
err);
}
/* guard against creating circular bindings */
if (resource->collection
return dav_error_response(r, HTTP_FORBIDDEN,
"Source collection contains the Destination.");
}
if (resource->collection
/* 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 */
/* could not make destination writable */
}
/* If target exists, remove it first (we know Ovewrite must be TRUE).
* Then try to bind to the resource.
*/
}
/* restore parent collection states */
0 /* unlock */, &av_info);
apr_psprintf(r->pool,
"Could not BIND %s.",
err);
}
/* check for errors from reverting writability */
/* just log a warning */
"The BIND was successful, but there was a "
"problem automatically checking in the "
"source parent collection.",
err2);
}
/* return an appropriate response (HTTP_CREATED) */
/* ### spec doesn't say what happens when destination was replaced */
}
/*
* Response handler for DAV resources
*/
static int dav_handler(request_rec *r)
{
return DECLINED;
/* Reject requests with an unescaped hash character, as these may
* be more destructive than the user intended. */
"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 ?? */
/*
* ### 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 */
return dav_method_bind(r);
}
/* DASL method */
return dav_method_search(r);
}
/* ### add'l methods for Advanced Collections, ACLs */
return DECLINED;
}
static int dav_fixups(request_rec *r)
{
/* quickly ignore any HTTP/0.9 requests which aren't subreqs. */
if (r->assbackwards && !r->main) {
return DECLINED;
}
&dav_module);
/* if DAV is not enabled, then we've got nothing to do */
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.
*/
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)
{
}
/*---------------------------------------------------------------------------
*
* Configuration info for the module
*/
static const command_rec dav_cmds[] =
{
"specify the DAV provider for a directory or location"),
"specify minimum allowed timeout"),
"allow Depth infinity PROPFIND requests"),
{ NULL }
};
{
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 */
};
)
(uris))
(const dav_resource *resource,
const dav_hooks_liveprop **hooks),