util.c revision 5a8f3bcf803321e69b226d3b98314305a68a586c
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
*/
/*
** DAV extension module for Apache 2.0.*
** - various utilities, repository-independent
*/
#include "apr_strings.h"
#include "apr_lib.h"
#define APR_WANT_STRFUNC
#include "apr_want.h"
#include "mod_dav.h"
#include "http_request.h"
#include "http_config.h"
#include "http_vhost.h"
#include "http_log.h"
#include "http_protocol.h"
{
int save_errno = errno;
/* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */
return err;
}
const char *namespace,
const char *tagname)
{
return err;
}
{
return err;
}
{
/* grow the buffer if necessary */
char *newbuf;
}
}
{
/* NOTE: this does not retain prior contents */
/* NOTE: this function is used to init the first pointer, too, since
the PAD will be larger than alloc_len (0) for zeroed structures */
/* grow if we don't have enough for the requested size plus padding */
/* set the new length; min of MINSIZE */
}
}
/* initialize a buffer and copy the specified (null-term'd) string into it */
const char *str)
{
}
/* append a string to the end of the buffer, adjust length */
const char *str)
{
}
/* place a string on the end of the buffer, do NOT adjust length */
const char *str)
{
}
/* place some memory on the end of a buffer; do NOT adjust length */
{
}
/*
** dav_lookup_uri()
**
** Extension for ap_sub_req_lookup_uri() which can't handle absolute
** URIs properly.
**
** If NULL is returned, then an error occurred with parsing the URI or
** the URI does not match the current server.
*/
int must_be_absolute)
{
dav_lookup_result result = { 0 };
const char *scheme;
char *new_file;
const char *domain;
/* first thing to do is parse the URI into various components */
return result;
}
/* the URI must be an absoluteURI (WEBDAV S9.3) */
return result;
}
/* the URI must not have a query (args) or a fragment */
"Destination URI contains invalid components "
"(a query or a fragment).";
return result;
}
/* If the scheme or port was provided, then make sure that it matches
### hmm. if a port wasn't provided (does the parse return port==0?),
### but we're on a non-standard port, then we won't detect that the
### URI's port implies the wrong one.
*/
{
/* ### not sure this works if the current request came in via https: */
scheme = ap_http_method(r);
/* insert a port if the URI did not contain one */
/* now, verify that the URI uses the same scheme as the current.
request. the port must match our port.
*/
#endif
) {
"Destination URI refers to "
"different scheme or port "
"(%s://hostname:%d)" APR_EOL_STR
"(want: %s://hostname:%d)",
return result;
}
}
/* we have verified the scheme, port, and general structure */
/*
** Hrm. IE5 will pass unqualified hostnames for both the
** Host: and Destination: headers. This breaks the
** http_vhost.c::matches_aliases function.
**
** For now, qualify unqualified comp.hostnames with
** r->server->server_hostname.
**
** ### this is a big hack. Apache should provide a better way.
** ### maybe the admin should list the unqualified hosts in a
** ### <ServerAlias> block?
*/
}
/* now, if a hostname was provided, then verify that it represents the
same server as the current connection. note that we just use our
port, since we've verified the URI matches ours */
return result;
}
#endif
/* we have verified that the requested URI denotes the same server as
the current request. Therefore, we can use ap_sub_req_lookup_uri() */
/* reconstruct a URI as just the path */
/*
* Lookup the URI and return the sub-request. Note that we use the
* same HTTP method on the destination. This allows the destination
* to apply appropriate restrictions (e.g. readonly).
*/
return result;
}
/* ---------------------------------------------------------------
**
** XML UTILITY FUNCTIONS
*/
/* validate that the root element uses a given DAV: tagname (TRUE==valid) */
{
}
/* find and return the (unique) child with a given DAV: tagname */
{
return child;
return NULL;
}
/* gather up all the CDATA into a single string */
int strip_white)
{
apr_size_t len = 0;
const apr_xml_elem *child;
char *cdata;
char *s;
int found_count = 0;
++found_count;
}
++found_count;
}
}
/* some fast-path cases:
* 1) zero-length cdata
* 2) a single piece of cdata with no whitespace to strip
*/
if (len == 0)
return "";
if (found_count == 1) {
if (!strip_white
|| (!apr_isspace(*found_text)
return found_text;
}
s += tlen;
}
s += tlen;
}
}
*s = '\0';
if (strip_white) {
/* trim leading whitespace */
++cdata;
/* trim trailing whitespace */
continue;
}
return cdata;
}
{
return xi;
}
{
/* this "should" not overwrite a prefix mapping */
/* note: this may overwrite an existing URI->prefix mapping, but it
doesn't matter -- any prefix is usuable to specify the URI. */
}
const char *uri)
{
const char *prefix;
APR_HASH_KEY_STRING)) != NULL)
return prefix;
return prefix;
}
const char *prefix)
{
}
const char *uri)
{
}
{
const void *prefix;
void *uri;
const char *s;
}
}
/* ---------------------------------------------------------------
**
** Timeout header processing
**
*/
/* dav_get_timeout: If the Timeout: header exists, return a time_t
* when this lock is expected to expire. Otherwise, return
* a time_t of DAV_TIMEOUT_INFINITE.
*
* It's unclear if DAV clients are required to understand
* Seconds-xxx and Infinity time values. We assume that they do.
* In addition, for now, that's all we understand, too.
*/
{
return DAV_TIMEOUT_INFINITE;
/* Use the first thing we understand, or infinity if
* we don't understand anything.
*/
return DAV_TIMEOUT_INFINITE;
}
val += 7;
/* ### We need to handle overflow better:
* ### timeout will be <= 2^32 - 1
*/
}
}
return DAV_TIMEOUT_INFINITE;
}
/* ---------------------------------------------------------------
**
** If Header processing
**
*/
/* add_if_resource returns a new if_header, linking it to next_ih.
*/
{
return NULL;
return ih;
}
/* add_if_state adds a condition to an if_header.
*/
const char *state_token,
dav_if_state_type t, int condition,
const dav_hooks_locks *locks_hooks)
{
if (t == dav_if_opaquelock) {
/* In cases where the state token is invalid, we'll just skip
* it rather than return 400.
*/
return NULL;
}
else {
/* ### maybe add a higher-level description */
return err;
}
}
}
else
return NULL;
}
/* fetch_next_token returns the substring from str+1
* to the next occurence of char term, or \0, whichever
* occurs first. Leading whitespace is ignored.
*/
{
char *sp;
char *token;
token++;
return NULL;
*sp = '\0';
return token;
}
/* dav_process_if_header:
*
* If NULL (no error) is returned, then **if_header points to the
* "If" productions structure (or NULL if "If" is not present).
*
* ### this part is bogus:
* If an error is encountered, the error is logged. Parent should
* return err->status.
*/
{
char *str;
char *list;
const char *state_token;
apr_size_t uri_len = 0;
int condition;
return NULL;
while (*str) {
switch(*str) {
case '<':
/* Tagged-list production - following states apply to this uri */
"Invalid If-header: unclosed \"<\" or "
"unexpected tagged-list production.");
}
/* 2518 specifies this must be an absolute URI; just take the
* relative part for later comparison against r->uri */
"Invalid URI in tagged If-header.");
}
/* note that parsed_uri.path is allocated; we can trash it */
/* clean up the URI a bit */
break;
case '(':
/* List production */
/* If a uri has not been encountered, this is a No-Tagged-List */
"Invalid If-header: unclosed \"(\".");
}
/* ### dav_add_if_resource() should return an error for us! */
"Internal server error parsing \"If:\" "
"header.");
}
while (*list) {
/* List is the entire production (in a uri scope) */
switch (*list) {
case '<':
/* ### add a description to this error */
}
/* ### maybe add a higher level description */
return err;
}
break;
case '[':
/* ### add a description to this error */
}
/* ### maybe add a higher level description */
return err;
}
break;
case 'N':
if (condition != DAV_IF_COND_NORMAL) {
"Invalid \"If:\" header: "
"Multiple \"not\" entries "
"for the same state.");
}
}
list += 2;
break;
case ' ':
case '\t':
break;
default:
apr_psprintf(r->pool,
"Invalid \"If:\" "
"header: Unexpected "
"character encountered "
"(0x%02x, '%c').",
}
list++;
}
break;
case ' ':
case '\t':
break;
default:
apr_psprintf(r->pool,
"Invalid \"If:\" header: "
"Unexpected character "
"encountered (0x%02x, '%c').",
}
str++;
}
return NULL;
}
const dav_hooks_locks *locks_hooks)
{
const dav_if_state_list *state_list;
state_list != NULL;
/* given state_list->locktoken, match it */
/*
** The resource will have one or more lock tokens. We only
** need to match one of them against any token in the
** If: header.
**
** One token case: It is an exclusive or shared lock. Either
** way, we must find it.
**
** N token case: They are shared locks. By policy, we need
** to match only one. The resource's other
** tokens may belong to somebody else (so we
** shouldn't see them in the If: header anyway)
*/
return 1;
}
}
}
}
}
return 0;
}
/* dav_validate_resource_state:
*/
const dav_resource *resource,
const dav_if_header *if_header,
int flags,
request_rec *r)
{
const char *uri;
const char *etag;
const dav_if_header *ifhdr_scan;
int num_matched;
int num_that_apply;
int seen_locktoken;
/* DBG1("validate: <%s>", resource->uri); */
/*
** The resource will have one of three states:
**
** 1) No locks. We have no special requirements that the user supply
** specific locktokens. One of the state lists must match, and
** we're done.
**
** 2) One exclusive lock. The locktoken must appear *anywhere* in the
** If: header. Of course, asserting the token in a "Not" term will
** quickly fail that state list :-). If the locktoken appears in
** one of the state lists *and* one state list matches, then we're
** done.
**
** 3) One or more shared locks. One of the locktokens must appear
** *anywhere* in the If: header. If one of the locktokens appears,
** and we match one state list, then we are done.
**
** The <seen_locktoken> variable determines whether we have seen one
** of this resource's locktokens in the If: header.
*/
/*
** If this is a new lock request, <flags> will contain the requested
** lock scope. Three rules apply:
**
** 1) Do not require a (shared) locktoken to be seen (when we are
** applying another shared lock)
** 2) If the scope is exclusive and we see any locks, fail.
** 3) If the scope is shared and we see an exclusive lock, fail.
*/
/* we're in State 1. no locks. */
}
else {
/*
** ### hrm... we don't need to have these fully
** ### resolved since we're only looking at the
** ### locktokens...
**
** ### use get_locks w/ calltype=PARTIAL
*/
return dav_push_error(p,
"The locks could not be queried for "
"verification against a possible \"If:\" "
"header.",
err);
}
/* lock_list now determines whether we're in State 1, 2, or 3. */
}
/*
** For a new, exclusive lock: if any locks exist, fail.
** For a new, shared lock: if an exclusive lock exists, fail.
** else, do not require a token to be seen.
*/
if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
return dav_new_error(p, HTTP_LOCKED, 0,
"Existing lock(s) on the requested resource "
"prevent an exclusive lock.");
}
/*
** There are no locks, so we can pretend that we've already met
** any requirement to find the resource's locks in an If: header.
*/
seen_locktoken = 1;
}
else if (flags & DAV_LOCKSCOPE_SHARED) {
/*
** Strictly speaking, we don't need this loop. Either the first
** (and only) lock will be EXCLUSIVE, or none of them will be.
*/
return dav_new_error(p, HTTP_LOCKED, 0,
"The requested resource is already "
"locked exclusively.");
}
}
/*
** The locks on the resource (if any) are all shared. Set the
** <seen_locktoken> flag to indicate that we do not need to find
** the locks in an If: header.
*/
seen_locktoken = 1;
}
else {
/*
** For methods other than LOCK:
**
** If we have no locks, then <seen_locktoken> can be set to true --
** pretending that we've already met the requirement of seeing one
** of the resource's locks in the If: header.
**
** Otherwise, it must be cleared and we'll look for one.
*/
}
/*
** If there is no If: header, then we can shortcut some logic:
**
** 1) if we do not need to find a locktoken in the (non-existent) If:
** header, then we are successful.
**
** 2) if we must find a locktoken in the (non-existent) If: header, then
** we fail.
*/
if (seen_locktoken)
return NULL;
return dav_new_error(p, HTTP_LOCKED, 0,
"This resource is locked and an \"If:\" header "
"was not supplied to allow access to the "
"resource.");
}
/* the If: header is present */
/*
** If a dummy header is present (because of a Lock-Token: header), then
** we are required to find that token in this resource's set of locks.
** If we have no locks, then we immediately fail.
**
** This is a 400 (Bad Request) since they should only submit a locktoken
** that actually exists.
**
** Don't issue this response if we're talking about the parent resource.
** It is okay for that resource to NOT have this locktoken.
** (in fact, it certainly will not: a dummy_header only occurs for the
** UNLOCK method, the parent is checked only for locknull resources,
** and the parent certainly does not have the (locknull's) locktoken)
*/
if (flags & DAV_VALIDATE_IS_PARENT)
return NULL;
return dav_new_error(p, HTTP_BAD_REQUEST, 0,
"The locktoken specified in the \"Lock-Token:\" "
"header is invalid because this resource has no "
"outstanding locks.");
}
/*
** Prepare the input URI. We want the URI to never have a trailing slash.
**
** When URIs are placed into the dav_if_header structure, they are
** guaranteed to never have a trailing slash. If the URIs are equivalent,
** then it doesn't matter if they both lack a trailing slash -- they're
** still equivalent.
**
** Note: we could also ensure that a trailing slash is present on both
** URIs, but the majority of URIs provided to us via a resource walk
** will not contain that trailing slash.
*/
}
/* get the resource's etag; we may need it during the checks */
/* how many state_lists apply to this URI? */
num_that_apply = 0;
/* If there are if-headers, fail if this resource
* does not match at least one state_list.
*/
for (ifhdr_scan = if_header;
ifhdr_scan != NULL;
/* DBG2("uri=<%s> if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
/*
** A tagged-list's URI doesn't match this resource's URI.
** Skip to the next state_list to see if it will match.
*/
continue;
}
/* this state_list applies to this resource */
/*
** ### only one state_list should ever apply! a no-tag, or a tagged
** ### where S9.4.2 states only one can match.
**
** ### revamp this code to loop thru ifhdr_scan until we find the
** ### matching state_list. process it. stop.
*/
/* To succeed, resource must match *all* of the states
* specified in the state_list.
*/
state_list != NULL;
switch(state_list->type) {
case dav_if_etag:
{
/*
** The specified entity-tag does not match the
** entity-tag on the resource. This state_list is
** not going to match. Bust outta here.
*/
reason =
"an entity-tag was specified, but the resource's "
"actual ETag does not match.";
goto state_list_failed;
}
&& !mismatch) {
/*
** The specified entity-tag DOES match the
** entity-tag on the resource. This state_list is
** not going to match. Bust outta here.
*/
reason =
"an entity-tag was specified using the \"Not\" form, "
"but the resource's actual ETag matches the provided "
"entity-tag.";
goto state_list_failed;
}
break;
}
case dav_if_opaquelock:
/* the locktoken is definitely not there! (success) */
continue;
}
/* condition == DAV_IF_COND_NORMAL */
/*
** If no lockdb is provided, then validation fails for
** this state_list (NORMAL means we were supposed to
** find the token, which we obviously cannot do without
** a lock database).
**
** Go and try the next state list.
*/
reason =
"a State-token was supplied, but a lock database "
"is not available for to provide the required lock.";
goto state_list_failed;
}
/* Resource validation 'fails' if:
* ANY of the lock->locktokens match
* a NOT state_list->locktoken,
* OR
* NONE of the lock->locktokens match
* a NORMAL state_list->locktoken.
*/
num_matched = 0;
/*
DBG2("compare: rsrc=%s ifhdr=%s",
(*locks_hooks->format_locktoken)(p, lock->locktoken),
(*locks_hooks->format_locktoken)(p, state_list->locktoken));
*/
/* nothing to do if the locktokens do not match. */
continue;
}
/*
** We have now matched up one of the resource's locktokens
** to a locktoken in a State-token in the If: header.
** Note this fact, so that we can pass the overall
** requirement of seeing at least one of the resource's
** locktokens.
*/
seen_locktoken = 1;
/*
** This state requires that the specified locktoken
** is NOT present on the resource. But we just found
** it. There is no way this state-list can now
** succeed, so go try another one.
*/
reason =
"a State-token was supplied, which used a "
"\"Not\" condition. The State-token was found "
"in the locks on this resource";
goto state_list_failed;
}
/* condition == DAV_IF_COND_NORMAL */
/* Validate auth_user: If an authenticated user created
** the lock, only the same user may submit that locktoken
** to manipulate a resource.
*/
(!r->user ||
const char *errmsg;
r->user,
"\" submitted a locktoken created "
"by user \"",
}
/*
** We just matched a specified State-Token to one of the
** resource's locktokens.
**
** Break out of the lock scan -- we only needed to find
** one match (actually, there shouldn't be any other
** matches in the lock list).
*/
num_matched = 1;
break;
}
if (num_matched == 0
/*
** We had a NORMAL state, meaning that we should have
** found the State-Token within the locks on this
** resource. We didn't, so this state_list must fail.
*/
reason =
"a State-token was supplied, but it was not found "
"in the locks on this resource.";
goto state_list_failed;
}
break;
} /* switch */
} /* foreach ( state_list ) */
/*
** We've checked every state in this state_list and none of them
** have failed. Since all of them succeeded, then we have a matching
** state list and we may be done.
**
** The next requirement is that we have seen one of the resource's
** locktokens (if any). If we have, then we can just exit. If we
** haven't, then we need to keep looking.
*/
if (seen_locktoken) {
/* woo hoo! */
return NULL;
}
/*
** Haven't seen one. Let's break out of the search and just look
** for a matching locktoken.
*/
break;
/*
** This label is used when we detect that a state_list is not
** going to match this resource. We bust out and try the next
** state_list.
*/
;
} /* foreach ( ifhdr_scan ) */
/*
** The above loop exits for one of two reasons:
** 1) a state_list matched and seen_locktoken is false.
** 2) all if_header structures were scanned, without (1) occurring
*/
if (ifhdr_scan == NULL) {
/*
** We finished the loop without finding any matching state lists.
*/
/*
** If none of the state_lists apply to this resource, then we
** may have succeeded. Note that this scenario implies a
** tagged-list with no matching state_lists. If the If: header
** was a no-tag-list, then it would have applied to this resource.
**
** S9.4.2 states that when no state_lists apply, then the header
** should be ignored.
**
** If we saw one of the resource's locktokens, then we're done.
** If we did not see a locktoken, then we fail.
*/
if (num_that_apply == 0) {
if (seen_locktoken)
return NULL;
/*
** We may have aborted the scan before seeing the locktoken.
** Rescan the If: header to see if we can find the locktoken
** somewhere.
**
** Note that seen_locktoken == 0 implies lock_list != NULL
** which implies locks_hooks != NULL.
*/
locks_hooks)) {
/*
** We found a match! We're set... none of the If: header
** assertions apply (implicit success), and the If: header
** specified the locktoken somewhere. We're done.
*/
return NULL;
}
"This resource is locked and the \"If:\" "
"header did not specify one of the "
"locktokens for this resource's lock(s).");
}
/* else: one or more state_lists were applicable, but failed. */
/*
** If the dummy_header did not match, then they specified an
** incorrect token in the Lock-Token header. Forget whether the
** If: statement matched or not... we'll tell them about the
** bad Lock-Token first. That is considered a 400 (Bad Request).
*/
if (if_header->dummy_header) {
return dav_new_error(p, HTTP_BAD_REQUEST, 0,
"The locktoken specified in the "
"\"Lock-Token:\" header did not specify one "
"of this resource's locktoken(s).");
}
return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
"The preconditions specified by the \"If:\" "
"header did not match this resource.");
}
return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
apr_psprintf(p,
"The precondition(s) specified by "
"the \"If:\" header did not match "
"this resource. At least one "
"failure is because: %s", reason));
}
/* assert seen_locktoken == 0 */
/*
** ifhdr_scan != NULL implies we found a matching state_list.
**
** Since we're still here, it also means that we have not yet found
** one the resource's locktokens in the If: header.
**
** Scan all the if_headers and states looking for one of this
** resource's locktokens. Note that we need to go back and scan them
** all -- we may have aborted a scan with a failure before we saw a
** matching token.
**
** Note that seen_locktoken == 0 implies lock_list != NULL which implies
** locks_hooks != NULL.
*/
/*
** We found a match! We're set... we have a matching state list,
** and the If: header specified the locktoken somewhere. We're done.
*/
return NULL;
}
/*
** We had a matching state list, but the user agent did not specify one
** of this resource's locktokens. Tell them so.
**
** Note that we need to special-case the message on whether a "dummy"
** header exists. If it exists, yet we didn't see a needed locktoken,
** then that implies the dummy header (Lock-Token header) did NOT
** specify one of this resource's locktokens. (this implies something
** in the real If: header matched)
**
** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
*/
if (if_header->dummy_header) {
return dav_new_error(p, HTTP_BAD_REQUEST, 0,
"The locktoken specified in the "
"\"Lock-Token:\" header did not specify one "
"of this resource's locktoken(s).");
}
"This resource is locked and the \"If:\" header "
"did not specify one of the "
"locktokens for this resource's lock(s).");
}
/* dav_validate_walker: Walker callback function to validate resource state */
{
/* There was no error, so just bug out. */
return NULL;
}
/*
** If we have a serious server error, or if the request itself failed,
** then just return error (not a multistatus).
*/
/* ### maybe push a higher-level description? */
return err;
}
/* associate the error with the current URI */
return NULL;
}
/*
** dav_validate_request: Validate if-headers (and check for locks) on:
** (1) r->filename @ depth;
** (2) Parent of r->filename if check_parent == 1
**
** The check of parent should be done when it is necessary to verify that
** the parent collection will accept a new member (ie current resource
** state is null).
**
** Return OK on successful validation.
** On error, return appropriate HTTP_* code, and log error. If a multi-stat
** error is necessary, response will point to it, else NULL.
*/
{
int result;
int lock_db_opened_locally = 0;
dav_buffer work_buf = { 0 };
#if DAV_DEBUG
/*
** ### bleck. we can't return errors for other URIs unless we have
** ### a "response" ptr.
*/
"DESIGN ERROR: dav_validate_request called "
"with depth>0, but no response ptr.");
}
#endif
/* Do the standard checks for conditional requests using
* If-..-Since, If-Match etc */
/* ### fix this up... how? */
}
/* always parse (and later process) the If: header */
/* ### maybe add higher-level description */
return err;
}
/* If a locktoken was specified, create a dummy if_header with which
* to validate resources. In the interim, figure out why DAV uses
* locktokens in an if-header without a Lock-Token header to refresh
* locks, but a Lock-Token header without an if-header to remove them.
*/
}
/*
** If necessary, open the lock database (read-only, lazily);
** the validation process may need to retrieve or update lock info.
** Otherwise, assume provided lockdb is valid and opened rw.
*/
if (locks_hooks != NULL) {
/* ### maybe insert higher-level comment */
return err;
}
}
}
/* (1) Validate the specified resource, at the specified depth */
dav_walker_ctx ctx = { { 0 } };
ctx.r = r;
}
*response = multi_status;;
}
/* else: implies a 5xx status code occurred. */
}
else {
}
/* (2) Validate the parent resource if requested */
"Cannot access parent of repository root.");
}
&work_buf, r);
/*
** This error occurred on the parent resource. This implies that
** we have to create a multistatus response (to report the error
** against a URI other than the Request-URI). "Convert" this error
** into a multistatus response.
*/
new_response->desc =
"A validation error has occurred on the parent resource, "
"preventing the operation on the resource specified by "
"the Request-URI.";
" The error was: ",
}
/* assert: DAV_VALIDATE_PARENT implies response != NULL */
*response = new_response;
}
}
}
/*
** If we don't have a (serious) error, and we have multistatus responses,
** then we need to construct an "error". This error will be the overall
** status returned, and the multistatus responses will go into its body.
**
** For certain methods, the overall error will be a 424. The default is
** to construct a standard 207 response.
*/
if ((flags & DAV_VALIDATE_USE_424) != 0) {
/* manufacture a 424 error to hold the multistatus response(s) */
"An error occurred on another resource, "
"preventing the requested operation on "
"this resource.");
}
/*
** Whatever caused the error, the Request-URI should have a 424
** associated with it since we cannot complete the method.
**
** For a LOCK operation, insert an empty DAV:lockdiscovery property.
** For other methods, return a simple 424.
*/
if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
"<D:propstat>" DEBUG_CR
"<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
"<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
"</D:propstat>" DEBUG_CR;
}
/* create the 424 response */
new_response->desc =
"An error occurred on another resource, preventing the "
"requested operation on this resource.";
*response = new_response;
/* manufacture a 207 error for the multistatus response(s) */
"Error(s) occurred on resources during the "
"validation process.");
}
return err;
}
/* dav_get_locktoken_list:
*
* Sets ltl to a locktoken_list of all positive locktokens in header,
* else NULL if no If-header, or no positive locktokens.
*/
{
/* ### add a higher-level description? */
return err;
}
*ltl = lock_token;
}
}
}
/* No nodes added */
"No locktokens were specified in the \"If:\" "
"header, so the refresh could not be performed.");
}
return NULL;
}
#if 0 /* not needed right now... */
{
/* trim leading whitespace */
while (apr_isspace(*s)) /* assume: return false for '\0' */
++s;
/* trim trailing whitespace */
if (apr_isspace(s[idx])) {
--idx;
return s2;
}
return s;
}
#endif
#define DAV_LABEL_HDR "Label"
/* dav_add_vary_header
*
* If there were any headers in the request which require a Vary header
* in the response, add it.
*/
const dav_resource *resource)
{
/* ### this is probably all wrong... I think there is a function in
### the Apache API to add things to the Vary header. need to check */
/* Only versioning headers require a Vary response header,
* so only do this check if there is a versioning provider */
/* If Target-Selector specified, add it to the Vary header */
else
NULL);
}
}
}
/* dav_can_auto_checkout
*
* Determine whether auto-checkout is enabled for a resource.
* r - the request_rec
* resource - the resource
* auto_version - the value of the auto_versionable hook for the resource
* lockdb - pointer to lock database (opened if necessary)
* auto_checkout - set to 1 if auto-checkout enabled
*/
static dav_error * dav_can_auto_checkout(
request_rec *r,
dav_lockdb **lockdb,
int *auto_checkout)
{
*auto_checkout = 0;
if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
*auto_checkout = 1;
}
else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
if (locks_hooks == NULL) {
"Auto-checkout is only enabled for locked resources, "
"but there is no lock provider.");
}
"Cannot open lock database to determine "
"auto-versioning behavior.",
err);
}
}
return dav_push_error(r->pool,
"The locks could not be queried for "
"determining auto-versioning behavior.",
err);
}
*auto_checkout = 1;
}
return NULL;
}
/* see mod_dav.h for docco */
request_rec *r,
int parent_only,
{
/* Initialize results */
/* if no versioning provider, just return */
return NULL;
/* check parent resource if requested or if resource must be created */
goto done;
apr_psprintf(r->pool,
"Missing one or more intermediate "
"collections. Cannot create resource %s.",
goto done;
}
/* if parent versioned and not checked out, see if it can be */
int checkout_parent;
&lockdb, &checkout_parent))
!= NULL) {
goto done;
}
if (!checkout_parent) {
"<DAV:cannot-modify-checked-in-parent>");
goto done;
}
/* Try to checkout the parent collection.
* Note that auto-versioning can only be applied to a version selector,
* so no separate working resource will be created.
*/
!= NULL)
{
apr_psprintf(r->pool,
"Unable to auto-checkout parent collection. "
"Cannot create resource %s.",
err);
goto done;
}
/* remember that parent was checked out */
}
}
/* if only checking parent, we're done */
if (parent_only)
goto done;
/* if creating a new resource, see if it should be version-controlled */
apr_psprintf(r->pool,
"Unable to create versioned resource %s.",
err);
goto done;
}
/* remember that resource was created */
}
/* if resource is versioned, make sure it is checked out */
int checkout_resource;
goto done;
}
if (!checkout_resource) {
"<DAV:cannot-modify-version-controlled-content>");
goto done;
}
/* Auto-versioning can only be applied to version selectors, so
* no separate working resource will be created. */
!= NULL)
{
apr_psprintf(r->pool,
"Unable to checkout resource %s.",
err);
goto done;
}
/* remember that resource was checked out */
}
done:
/* make sure lock database is closed */
/* if an error occurred, undo any auto-versioning operations already done */
return err;
}
return NULL;
}
/* see mod_dav.h for docco */
request_rec *r,
int undo,
int unlock,
{
/* If no versioning provider, this is a no-op */
return NULL;
/* If undoing auto-checkouts, then do uncheckouts */
if (undo) {
if (av_info->resource_checkedout) {
apr_psprintf(r->pool,
"Unable to undo auto-checkout "
"of resource %s.",
err);
}
}
if (av_info->resource_versioned) {
/* ### should we do anything with the response? */
apr_psprintf(r->pool,
"Unable to undo auto-version-control "
"of resource %s.",
err);
}
}
}
apr_psprintf(r->pool,
"Unable to undo auto-checkout "
"of parent collection %s.",
err);
}
}
return NULL;
}
/* If the resource was checked out, and auto-checkin is enabled,
* then check it in.
*/
if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
0 /*keep_checked_out*/, NULL))
!= NULL) {
apr_psprintf(r->pool,
"Unable to auto-checkin resource %s.",
err);
}
}
}
/* If parent resource was checked out, and auto-checkin is enabled,
* then check it in.
*/
if (!unlock
if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
0 /*keep_checked_out*/, NULL))
!= NULL) {
apr_psprintf(r->pool,
"Unable to auto-checkin parent collection %s.",
err);
}
}
}
return NULL;
}