util.c revision 26250b0077972bf21b6d8a8d23772a4cf53f9477
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000 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 "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;
}
{
return err;
}
{
/* no text elements yet */
}
else {
/* append to the last text element */
}
}
{
/* 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 */
{
}
/* append a string to the end of the buffer, adjust length */
{
}
/* place a string on the end of the buffer, do NOT adjust length */
{
}
/* place some memory on the end of a buffer; do NOT adjust length */
{
}
#if APACHE_RELEASE == 10304100
/* ### this code can be used for 1.3.4 installations. use this function
* ### instead of ap_sub_req_method_uri()
*/
/*
* ### don't look at this code.
* ### it is a Crime Against All That is Right and Good
*/
#include "http_core.h" /* for SATISFY_* */
{
int res;
/* ### these aren't exported properly from the headers */
return rnew;
/* re-run portions with a modified method */
&& (!ap_some_auth_required(rnew)
) {
}
return rnew;
}
#endif /* APACHE_RELEASE == 10304100 */
/*
** dav_lookup_uri()
**
** Extension for ap_sub_req_lookup_uri() which can't handle absolute
** URIs properly.
**
** If NULL is returned, then an error occurred with parsing the URI or
** the URI does not match the current server.
*/
{
dav_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;
}
/* ### 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.
the URI must not have a query (args) or a fragment
*/
"Destination URI refers to different "
"scheme or port (%s://hostname:%d)\n"
"(want: %s://hostname:%d)",
return result;
}
"Destination URI contains invalid components "
"(a query or a fragment).";
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;
}
/* 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).
*/
#if APACHE_RELEASE == 10304100
#else
#endif
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;
}
/* how many characters for the given integer? */
static int dav_text_size(const dav_text *t)
{
int size = 0;
for (; t; t = t->next)
return size;
}
{
const dav_xml_attr *attr;
size = 0;
if (style == DAV_X2T_FULL_NS_LANG) {
int i;
/*
** The outer element will contain xmlns:ns%d="%s" attributes
** and an xml:lang attribute, if applicable.
*/
for (i = namespaces->nelts; i--;) {
/* compute size of: ' xmlns:ns%d="%s"' */
}
/* compute size of: ' xml:lang="%s"' */
}
}
/* compute size of: <%s> */
}
else {
/* compute size of: <ns%d:%s> */
}
if (DAV_ELEM_IS_EMPTY(elem)) {
/* insert a closing "/" */
size += 1;
}
else {
/*
* two of above plus "/":
* <ns%d:%s> ... </ns%d:%s>
* OR <%s> ... </%s>
*/
}
/* compute size of: ' %s="%s"' */
}
else {
/* compute size of: ' ns%d:%s="%s"' */
}
}
/*
** If the element has an xml:lang value that is *different* from
** its parent, then add the thing in: ' xml:lang="%s"'.
**
** NOTE: we take advantage of the pointer equality established by
** the parsing for "inheriting" the xml:lang values from parents.
*/
}
}
else if (style == DAV_X2T_LANG_INNER) {
/*
* This style prepends the xml:lang value plus a null terminator.
* If a lang value is not present, then we insert a null term.
*/
}
else
size = 0;
/* the size of the child element plus the CDATA that follows it */
}
return size;
}
static char *dav_write_text(char *s, const dav_text *t)
{
for (; t; t = t->next) {
s += len;
}
return s;
}
{
const dav_xml_elem *child;
int ns;
const dav_xml_attr *attr;
}
else {
}
s += len;
else
s += len;
}
/* add the xml:lang value if necessary */
(style == DAV_X2T_FULL_NS_LANG ||
s += len;
}
/* add namespace definitions, if required */
if (style == DAV_X2T_FULL_NS_LANG) {
int i;
for (i = namespaces->nelts; i--;) {
DAV_GET_URI_ITEM(namespaces, i));
s += len;
}
}
/* no more to do. close it up and go. */
if (empty) {
*s++ = '/';
*s++ = '>';
return s;
}
/* just close it */
*s++ = '>';
}
else if (style == DAV_X2T_LANG_INNER) {
/* prepend the xml:lang value */
s += len;
}
*s++ = '\0';
}
}
}
else {
}
s += len;
}
return s;
}
/* convert an element to a text string */
void dav_xml2text(ap_pool_t * p,
const dav_xml_elem *elem,
int style,
int *ns_map,
const char **pbuf,
{
/* get the exact size, plus a null terminator */
*pbuf = s;
if (psize)
}
{
/*
* The prefix (xml...) is already within the prop name, or
* the element simply has no prefix.
*/
}
}
/*
** dav_quote_string: quote an XML string
**
** Replace '<', '>', and '&' with '<', '>', and '&'.
** If quotes is true, then replace '"' with '"'.
**
** quotes is typically set to true for XML strings that will occur within
** double quotes -- attribute values.
*/
{
const char *scan;
int len = 0;
int extra = 0;
char *qstr;
char *qscan;
char c;
if (c == '<' || c == '>')
else if (c == '&')
else if (quotes && c == '"')
}
/* nothing to do? */
if (extra == 0)
return s;
if (c == '<') {
*qscan++ = '&';
*qscan++ = 'l';
*qscan++ = 't';
*qscan++ = ';';
}
else if (c == '>') {
*qscan++ = '&';
*qscan++ = 'g';
*qscan++ = 't';
*qscan++ = ';';
}
else if (c == '&') {
*qscan++ = '&';
*qscan++ = 'a';
*qscan++ = 'm';
*qscan++ = 'p';
*qscan++ = ';';
}
else if (quotes && c == '"') {
*qscan++ = '&';
*qscan++ = 'q';
*qscan++ = 'u';
*qscan++ = 'o';
*qscan++ = 't';
*qscan++ = ';';
}
else {
*qscan++ = c;
}
}
*qscan = '\0';
return qstr;
}
{
/* convert the element's text */
}
}
/* convert the attribute values */
}
/* convert the child elements */
}
}
/* ---------------------------------------------------------------
**
** 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) {
/* ### 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;
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:
ap_psprintf(r->pool,
"Invalid \"If:\" "
"header: Unexpected "
"character encountered "
"(0x%02x, '%c').",
}
list++;
}
break;
case ' ':
case '\t':
break;
default:
ap_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.
*/
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,
ap_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;
}
}
else {
"Resource validation failed because no "
"lock hooks were found.");
}
}
/* (1) Validate the specified resource, at the specified depth */
dav_walker_ctx ctx = { 0 };
ctx.r = r;
}
}
/* else: implies a 5xx status code occurred. */
}
else {
}
/* (2) Validate the parent resource if requested */
if (parent_resource == NULL) {
"Cannot access parent of repository root.");
}
else {
&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;
}
/* dav_get_target_selector:
*
* Returns any Target-Selector header in a request
* (used by versioning clients)
*/
const char *dav_get_target_selector(request_rec *r)
{
}
/* Ensure that a resource is writable. If there is no versioning
* provider, then this is essentially a no-op. Versioning repositories
* require explicit resource creation and checkout before they can
* be written to. If a new resource is to be created, or an existing
* resource deleted, the parent collection must be checked out as well.
*
* Set the parent_only flag to only make the parent collection writable.
* Otherwise, both parent and child are made writable as needed. If the
* child does not exist, then a new versioned resource is created and
* checked out.
*
* The parent_resource and parent_was_writable arguments are optional
* (i.e. they may be NULL). If parent_only is set, then the
* resource_existed and resource_was_writable arguments are ignored.
*
* The previous states of the resources are returned, so they can be
* restored after the operation completes (see
* dav_revert_resource_writability())
*/
int parent_only,
int *resource_existed,
int *resource_was_writable,
int *parent_was_writable)
{
const char *body;
int auto_version;
if (parent_resource != NULL)
*parent_resource = NULL;
if (!parent_only) {
*resource_was_writable = 0;
}
if (parent_was_writable != NULL)
*parent_was_writable = 0;
/* if a Target-Selector header is present, then the client knows about
* versioning, so it should not be relying on implicit versioning
*/
/* check parent resource if requested or if resource must be created */
"Missing one or more intermediate collections. "
"Cannot create resource %s.",
}
if (parent_resource != NULL)
/* if parent not versioned, assume child can be created */
if (!parent_only)
*resource_was_writable = 1;
if (parent_was_writable != NULL)
*parent_was_writable = 1;
return NULL;
}
/* if no versioning provider, something is terribly wrong */
"INTERNAL ERROR: "
"versioned resource with no versioning "
"provider?");
}
/* remember whether parent was already writable */
if (parent_was_writable != NULL)
/* parent must be checked out */
"Unable to checkout parent collection. "
"Cannot create resource %s.",
}
}
/* if not just checking parent, create new child resource */
if (!parent_only) {
"Unable to create versioned resource %s.",
}
}
}
else {
/* resource exists: if not versioned, then assume it is writable */
*resource_was_writable = 1;
return NULL;
}
}
/* if not just checking parent, make sure child resource is checked out */
"Unable to checkout resource %s.",
}
}
return NULL;
}
/* Revert the writability of resources back to what they were
* before they were modified. If undo == 0, then the resource
* modifications are maintained (i.e. they are checked in).
* If undo != 0, then resource modifications are discarded
* (i.e. they are unchecked out).
*
* The resource and parent_resource arguments are optional
* (i.e. they may be NULL).
*/
int undo,
int resource_existed,
int parent_was_writable)
{
const char *body;
if (undo)
else
"Unable to %s resource %s.",
}
}
/* ### should we do anything with the response? */
"Unable to undo creation of resource %s.",
}
}
}
if (undo)
else
"Unable to %s parent collection of %s.",
}
}
return NULL;
}
/* return the URI's (existing) index, or insert it and return a new index */
{
int i;
const char **pelt;
return i;
}
}