/* 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;
}
DAV_DECLARE(
const char *) dav_get_base_path(request_rec *r)
{
dav_dir_conf *conf = ap_get_module_config(r->per_dir_config, &dav_module);
return conf && conf->base ? conf->base : NULL;
}
/*
* 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_ex(r,
"text/html; charset=ISO-8859-1", 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(
"
\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_ex(r, DAV_XML_CONTENT_TYPE, 1);
ap_rputs(DAV_XML_HEADER DEBUG_CR
"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
"%s" DEBUG_CR,
err->namespace,
err->tagname, err->childtags, err->tagname);
}
else {
ap_rprintf(r,
">" DEBUG_CR
"%s" DEBUG_CR,
err->tagname, err->childtags, err->tagname);
}
}
else {
if (err->namespace != NULL) {
ap_rprintf(r,
" xmlns:C=\"%s\">" DEBUG_CR
"" DEBUG_CR,
err->namespace, err->tagname);
}
else {
ap_rprintf(r,
">" DEBUG_CR
"" DEBUG_CR, err->tagname);
}
}
/* here's our mod_dav specific tag: */
if (err->desc != NULL) {
ap_rprintf(r,
"%d\">" DEBUG_CR
"%s" DEBUG_CR
"" DEBUG_CR,
err->error_id,
apr_xml_quote_string(r->pool, err->desc, 0));
}
ap_rputs("" 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,
"");
}
else {
ap_fputs(r->output_filters, bb,
");
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 "",
dav_xml_escape_uri(pool, response->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,
"HTTP/1.1 ",
ap_get_status_line(response->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,
"",
response->desc,
"" DEBUG_CR,
NULL);
}
ap_fputs(r->output_filters, bb, "" 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_ex(r, DAV_XML_CONTENT_TYPE, 1);
/* Send the headers and actual multistatus response now... */
ap_fputs(r->output_filters, bb, DAV_XML_HEADER DEBUG_CR
"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, "" 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,
"" 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,
"",
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,
"",
name, "\"/>" DEBUG_CR, NULL);
apr_text_append(r->pool, body, s);
}
}
}
}
apr_text_append(r->pool, body, "" 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, "" 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, "" 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, "" 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,
"",
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:name=\"",
rp->name,
"\" D:namespace=\"",
rp->nmspace,
"\"/>" DEBUG_CR, NULL);
apr_text_append(r->pool, body, s);
break;
}
}
}
}
}
apr_text_append(r->pool, body, "" 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) {
--> --------------------
--> maximum size reached
--> --------------------