/* 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.* ** - various utilities, repository-independent
*/
/* src error doesn't exist so nothing to join just return dest */ if (src == NULL) { return dest;
}
/* dest error doesn't exist so nothing to join just return src */ if (curr == NULL) { return src;
}
/* find last error in dest stack */ while (curr->prev != NULL) {
curr = curr->prev;
}
/* add the src error onto end of dest stack and return it */
curr->prev = src; return dest;
}
/* ### Unclear if this was designed to be used with an uninitialized * dav_buffer struct, but is used on by dav_lock_get_activelock().
* Hence check for pbuf->buf. */
DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
apr_size_t extra_needed)
{ /* grow the buffer if necessary */ if (pbuf->cur_len + extra_needed > pbuf->alloc_len) { char *newbuf;
DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf,
apr_size_t size)
{ /* 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 */ if (size + DAV_BUFFER_PAD > pbuf->alloc_len) { /* set the new length; min of MINSIZE */
pbuf->alloc_len = size + DAV_BUFFER_PAD; if (pbuf->alloc_len < DAV_BUFFER_MINSIZE)
pbuf->alloc_len = DAV_BUFFER_MINSIZE;
/* initialize a buffer and copy the specified (null-term'd) string into it */
DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf, constchar *str)
{
dav_set_bufsize(p, pbuf, strlen(str));
memcpy(pbuf->buf, str, pbuf->cur_len + 1);
}
/* append a string to the end of the buffer, adjust length */
DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf, constchar *str)
{
apr_size_t len = strlen(str);
dav_check_bufsize(p, pbuf, len + 1);
memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
pbuf->cur_len += len;
}
/* place a string on the end of the buffer, do NOT adjust length */
DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf, constchar *str)
{
apr_size_t len = strlen(str);
dav_check_bufsize(p, pbuf, len + 1);
memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
}
/* place some memory on the end of a buffer; do NOT adjust length */
DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf, constvoid *mem, apr_size_t amt,
apr_size_t pad)
{
dav_check_bufsize(p, pbuf, amt + pad);
memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
}
/* ** 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_DECLARE(dav_lookup_result) dav_lookup_uri(constchar *uri,
request_rec * r, int must_be_absolute)
{
dav_lookup_result result = { 0 }; constchar *scheme;
apr_port_t port;
apr_uri_t comp; char *new_file; constchar *domain;
/* first thing to do is parse the URI into various components */ if (apr_uri_parse(r->pool, uri, &comp) != APR_SUCCESS) {
result.err.status = HTTP_BAD_REQUEST;
result.err.desc = "Invalid syntax in Destination URI."; return result;
}
/* the URI must be an absoluteURI (WEBDAV S9.3) */ if (comp.scheme == NULL && must_be_absolute) {
result.err.status = HTTP_BAD_REQUEST;
result.err.desc = "Destination URI must be an absolute URI."; return result;
}
/* the URI must not have a query (args) or a fragment */ if (comp.query != NULL || comp.fragment != NULL) {
result.err.status = HTTP_BAD_REQUEST;
result.err.desc = "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 the scheme/port of this request. If the request must be absolute, then require the (explicit/implicit) scheme/port be matching.
### 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.
*/ if (comp.scheme != NULL || comp.port != 0 || must_be_absolute)
{ /* ### not sure this works if the current request came in via https: */
scheme = r->parsed_uri.scheme; if (scheme == NULL)
scheme = ap_http_scheme(r);
/* insert a port if the URI did not contain one */ if (comp.port == 0)
comp.port = apr_uri_port_of_scheme(comp.scheme);
/* now, verify that the URI uses the same scheme as the current. request. the port must match our port.
*/
port = r->connection->local_addr->port; if (ap_cstr_casecmp(comp.scheme, scheme) != 0 #ifdef APACHE_PORT_HANDLING_IS_BUSTED
|| comp.port != port #endif
) {
result.err.status = HTTP_BAD_GATEWAY;
result.err.desc = apr_psprintf(r->pool, "Destination URI refers to " "different scheme or port " "(%s://hostname:%d)" APR_EOL_STR "(want: %s://hostname:%d)",
comp.scheme ? comp.scheme : scheme,
comp.port ? comp.port : port,
scheme, port); 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?
*/ if (comp.hostname != NULL
&& strrchr(comp.hostname, '.') == NULL
&& (domain = strchr(r->server->server_hostname, '.')) != NULL) {
comp.hostname = apr_pstrcat(r->pool, comp.hostname, domain, NULL);
}
/* 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 */ #ifdef APACHE_PORT_HANDLING_IS_BUSTED if (comp.hostname != NULL &&
!ap_matches_request_vhost(r, comp.hostname, port)) {
result.err.status = HTTP_BAD_GATEWAY;
result.err.desc = "Destination URI refers to a different server."; 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 */
new_file = apr_uri_unparse(r->pool, &comp, APR_URI_UNP_OMITSITEPART);
/* * 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).
*/
result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
return result;
}
/* --------------------------------------------------------------- ** ** XML UTILITY FUNCTIONS
*/
/* validate that the root element uses a given DAV: tagname (TRUE==valid) */
DAV_DECLARE(int) dav_validate_root_ns(const apr_xml_doc *doc, int ns, constchar *tagname)
{ return doc->root &&
doc->root->ns == ns &&
strcmp(doc->root->name, tagname) == 0;
}
/* validate that the root element uses a given DAV: tagname (TRUE==valid) */
DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc, constchar *tagname)
{ return dav_validate_root_ns(doc, APR_XML_NS_DAV_ID, tagname);
}
/* find and return the next child with a tagname in the given namespace */
DAV_DECLARE(apr_xml_elem *) dav_find_next_ns(const apr_xml_elem *elem, int ns, constchar *tagname)
{
apr_xml_elem *child = elem->next;
for (; child; child = child->next) if (child->ns == ns && !strcmp(child->name, tagname)) return child; return NULL;
}
/* find and return the (unique) child with a tagname in the given namespace */
DAV_DECLARE(apr_xml_elem *) dav_find_child_ns(const apr_xml_elem *elem, int ns, constchar *tagname)
{
apr_xml_elem *child = elem->first_child;
for (; child; child = child->next) if (child->ns == ns && !strcmp(child->name, tagname)) return child; return NULL;
}
/* find and return the (unique) child with a given DAV: tagname */
DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem, constchar *tagname)
{ return dav_find_child_ns(elem, APR_XML_NS_DAV_ID, tagname);
}
/* find and return the attribute with a name in the given namespace */
DAV_DECLARE(apr_xml_attr *) dav_find_attr_ns(const apr_xml_elem *elem, int ns, constchar *attrname)
{
apr_xml_attr *attr = elem->attr;
for (; attr; attr = attr->next) if (attr->ns == ns && !strcmp(attr->name, attrname)) return attr; return NULL;
}
/* find and return the attribute with a given DAV: tagname */
DAV_DECLARE(apr_xml_attr *) dav_find_attr(const apr_xml_elem *elem, constchar *attrname)
{ return dav_find_attr_ns(elem, APR_XML_NS_DAV_ID, attrname);
}
/* gather up all the CDATA into a single string */
DAV_DECLARE(constchar *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool, int strip_white)
{
apr_size_t len = 0;
apr_text *scan; const apr_xml_elem *child; char *cdata; char *s;
apr_size_t tlen; constchar *found_text = NULL; /* initialize to avoid gcc warning */ int found_count = 0;
for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
found_text = scan->text;
++found_count;
len += strlen(found_text);
}
/* 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)
&& !apr_isspace(found_text[len - 1]))) return found_text;
}
DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi, constchar *prefix, constchar *uri)
{ /* this "should" not overwrite a prefix mapping */
apr_hash_set(xi->prefix_uri, prefix, APR_HASH_KEY_STRING, uri);
/* note: this may overwrite an existing URI->prefix mapping, but it
doesn't matter -- any prefix is usable to specify the URI. */
apr_hash_set(xi->uri_prefix, uri, APR_HASH_KEY_STRING, prefix);
}
/* 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.
*/
DAV_DECLARE(time_t) dav_get_timeout(request_rec *r)
{
time_t now, expires = DAV_TIMEOUT_INFINITE;
/* Use the first thing we understand, or infinity if * we don't understand anything.
*/
while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) { if (!strncmp(val, "Infinite", 8)) { return DAV_TIMEOUT_INFINITE;
}
if (!strncmp(val, "Second-", 7)) {
val += 7; /* ### We need to handle overflow better: * ### timeout will be <= 2^32 - 1
*/
expires = atol(val);
now = time(NULL); return now + expires;
}
}
return DAV_TIMEOUT_INFINITE;
}
/* --------------------------------------------------------------- ** ** If Header processing **
*/
/* add_if_resource returns a new if_header, linking it to next_ih.
*/ static dav_if_header *dav_add_if_resource(apr_pool_t *p, dav_if_header *next_ih, constchar *uri, apr_size_t uri_len)
{
dav_if_header *ih;
if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL) return NULL;
/* add_if_state adds a condition to an if_header.
*/ static dav_error * dav_add_if_state(apr_pool_t *p, dav_if_header *ih, constchar *state_token,
dav_if_state_type t, int condition, const dav_hooks_locks *locks_hooks)
{
dav_if_state_list *new_sl;
new_sl = apr_pcalloc(p, sizeof(*new_sl));
new_sl->condition = condition;
new_sl->type = t;
if (t == dav_if_opaquelock) {
dav_error *err;
if ((err = (*locks_hooks->parse_locktoken)(p, state_token,
&new_sl->locktoken)) != NULL) { /* If the state token cannot be parsed, treat it as an * unknown state; this will evaluate to "false" later
* during If header validation. */ if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) {
new_sl->type = dav_if_unknown;
} else { /* ### maybe add a higher-level description */ return err;
}
}
} else
new_sl->etag = state_token;
new_sl->next = ih->state;
ih->state = new_sl;
return NULL;
}
/* fetch_next_token returns the substring from str+1 * to the next occurrence of char term, or \0, whichever * occurs first. Leading whitespace is ignored.
*/ staticchar *dav_fetch_next_token(char **str, char term)
{ char *sp; char *token;
if ((sp = strchr(token, term)) == NULL) return NULL;
*sp = '\0';
*str = sp; 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.
*/ static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
{
dav_error *err; char *str; char *list; constchar *state_token; constchar *uri = NULL; /* scope of current production; NULL=no-tag */
apr_size_t uri_len = 0;
apr_status_t rv;
dav_if_header *ih = NULL;
apr_uri_t parsed_uri; const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); enum {no_tagged, tagged, unknown} list_type = unknown; int condition;
*p_ih = NULL;
if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL) return NULL;
while (*str) { switch(*str) { case'<': /* Tagged-list production - following states apply to this uri */ if (list_type == no_tagged
|| ((uri = dav_fetch_next_token(&str, '>')) == NULL)) { return dav_new_error(r->pool, HTTP_BAD_REQUEST,
DAV_ERR_IF_TAGGED, 0, "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 */ if ((rv = apr_uri_parse(r->pool, uri, &parsed_uri)) != APR_SUCCESS
|| !parsed_uri.path) { return dav_new_error(r->pool, HTTP_BAD_REQUEST,
DAV_ERR_IF_TAGGED, rv, "Invalid URI in tagged If-header.");
} /* note that parsed_uri.path is allocated; we can trash it */
/* clean up the URI a bit */ if (!ap_normalize_path(parsed_uri.path,
AP_NORMALIZE_NOT_ABOVE_ROOT |
AP_NORMALIZE_DECODE_UNRESERVED)) { return dav_new_error(r->pool, HTTP_BAD_REQUEST,
DAV_ERR_IF_TAGGED, rv, "Invalid URI path tagged If-header.");
}
/* the resources we will compare to have unencoded paths */ if (ap_unescape_url(parsed_uri.path) != OK) { return dav_new_error(r->pool, HTTP_BAD_REQUEST,
DAV_ERR_IF_TAGGED, rv, "Invalid percent encoded URI in " "tagged If-header.");
}
if (state_list->type == dav_if_opaquelock) { const dav_lock *lock;
/* 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)
*/ for (lock = lock_list; lock != NULL; lock = lock->next) {
/* ** 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.
*/
if (lockdb == NULL) { /* we're in State 1. no locks. */
lock_list = NULL;
} 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
*/ if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) { return dav_push_error(p,
HTTP_INTERNAL_SERVER_ERROR, 0, "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) { if (lock_list != NULL) { return dav_new_error(p, HTTP_LOCKED, 0, 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;
} elseif (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.
*/ for (lock = lock_list; lock != NULL; lock = lock->next) { if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) { return dav_new_error(p, HTTP_LOCKED, 0, 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 or if the resource is not being modified ** (per RFC 4918 the lock token is not required on resources ** we are not changing), 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.
*/
seen_locktoken = (lock_list == NULL
|| flags & DAV_VALIDATE_NO_MODIFY);
}
/* ** 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 (if_header == NULL) { if (seen_locktoken) return NULL;
return dav_new_error(p, HTTP_LOCKED, 0, 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 (lock_list == NULL && if_header->dummy_header) { if (flags & DAV_VALIDATE_IS_PARENT) return NULL; return dav_new_error(p, HTTP_BAD_REQUEST, 0, 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.
*/
uri = resource->uri;
uri_len = strlen(uri); if (uri[uri_len - 1] == '/') {
dav_set_bufsize(p, pbuf, uri_len);
memcpy(pbuf->buf, uri, uri_len);
pbuf->buf[--uri_len] = '\0';
uri = pbuf->buf;
}
/* get the resource's etag; we may need it during the checks */
etag = (*resource->hooks->getetag)(resource);
/* 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;
ifhdr_scan = ifhdr_scan->next) {
if (ifhdr_scan->uri != NULL
&& (uri_len != ifhdr_scan->uri_len
|| memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) { /* ** 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.
*/
++num_that_apply;
/* To succeed, resource must match *all* of the states * specified in the state_list.
*/ for (state_list = ifhdr_scan->state;
state_list != NULL;
state_list = state_list->next) {
switch(state_list->type) { case dav_if_etag:
{ constchar *given_etag, *current_etag; int mismatch;
/* Do a weak entity comparison function as defined in * RFC 2616 13.3.3.
*/ if (state_list->etag[0] == 'W' &&
state_list->etag[1] == '/') {
given_etag = state_list->etag + 2;
} else {
given_etag = state_list->etag;
} if (etag[0] == 'W' &&
etag[1] == '/') {
current_etag = etag + 2;
} else {
current_etag = etag;
}
mismatch = strcmp(given_etag, current_etag);
if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) { /* ** 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;
} elseif (state_list->condition == DAV_IF_COND_NOT
&& !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: if (lockdb == NULL) { if (state_list->condition == DAV_IF_COND_NOT) { /* 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; for (lock = lock_list; lock != NULL; lock = lock->next) {
/* nothing to do if the locktokens do not match. */ if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) { 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;
if (state_list->condition == DAV_IF_COND_NOT) { /* ** 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.
*/ if (lock->auth_user &&
(!r->user ||
strcmp(lock->auth_user, r->user))) { constchar *errmsg;
errmsg = apr_pstrcat(p, "User \"",
r->user ? r->user : "[none]", "\" submitted a locktoken created " "by user \"",
lock->auth_user, "\".", NULL); return dav_new_error(p, HTTP_FORBIDDEN, 0, 0, errmsg);
}
/* ** 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
&& state_list->condition == DAV_IF_COND_NORMAL) { /* ** 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;
case dav_if_unknown: /* Request is predicated on some unknown state token, * which must be presumed to *not* match, so fail
* unless this is a Not condition. */
if (state_list->condition == DAV_IF_COND_NORMAL) {
reason = "an unknown state token was supplied"; 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.
*/
state_list_failed:
;
} /* 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.
*/ if (dav_find_submitted_locktoken(if_header, lock_list,
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;
}
return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */, 0, "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, 0, "The locktoken specified in the " "\"Lock-Token:\" header did not specify one " "of this resource's locktoken(s).");
}
if (reason == NULL) { return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 0, "The preconditions specified by the \"If:\" " "header did not match this resource.");
}
return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, 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.
*/ if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) { /* ** 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, 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_LOCKED, 1 /* error_id */, 0, "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 */ static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
{
dav_walker_ctx *ctx = wres->walk_ctx;
dav_error *err;
if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
ctx->w.lockdb,
ctx->if_header, ctx->flags,
&ctx->work_buf, ctx->r)) == NULL) { /* 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).
*/ if (ap_is_HTTP_SERVER_ERROR(err->status)
|| (*wres->resource->hooks->is_same_resource)(wres->resource,
ctx->w.root)) { /* ### maybe push a higher-level description? */ return err;
}
/* associate the error with the current URI */
dav_add_response(wres, err->status, NULL);
return NULL;
}
/* If-* header checking */ staticint dav_meets_conditions(request_rec *r, int resource_state)
{ constchar *if_match, *if_none_match; int retVal;
/* If-Match '*' fix. Resource existence not checked by ap_meets_conditions.
* If-Match '*' request should succeed only if the resource exists. */ if ((if_match = apr_table_get(r->headers_in, "If-Match")) != NULL) { if (if_match[0] == '*' && resource_state != DAV_RESOURCE_EXISTS) return HTTP_PRECONDITION_FAILED;
}
retVal = ap_meets_conditions(r);
/* If-None-Match '*' fix. If-None-Match '*' request should succeed
* if the resource does not exist. */ if (retVal == HTTP_PRECONDITION_FAILED) { /* Note. If if_none_match != NULL, if_none_match is the culprit. * Since, in presence of If-None-Match,
* other If-* headers are undefined. */ if ((if_none_match =
apr_table_get(r->headers_in, "If-None-Match")) != NULL) { if (if_none_match[0] == '*'
&& resource_state != DAV_RESOURCE_EXISTS) { return OK;
}
}
}
return retVal;
}
/* ** 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.
*/
DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r,
dav_resource *resource, int depth,
dav_locktoken *locktoken,
dav_response **response, int flags,
dav_lockdb *lockdb)
{
dav_error *err; int result;
dav_if_header *if_header; int lock_db_opened_locally = 0; const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); const dav_hooks_repository *repos_hooks = resource->hooks;
dav_buffer work_buf = { 0 };
dav_response *new_response; int resource_state; constchar *etag; int set_etag = 0;
#if DAV_DEBUG if (depth && response == NULL) { /* ** ### bleck. we can't return errors for other URIs unless we have ** ### a "response" ptr.
*/ return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, "DESIGN ERROR: dav_validate_request called " "with depth>0, but no response ptr.");
} #endif
if (response != NULL)
*response = NULL;
/* Set the ETag header required by dav_meets_conditions() */
etag = apr_table_get(r->headers_out, "ETag"); if (!etag) {
etag = (*resource->hooks->getetag)(resource); if (etag && *etag) {
apr_table_set(r->headers_out, "ETag", etag);
set_etag = 1;
}
} /* Do the standard checks for conditional requests using
* If-..-Since, If-Match etc */
resource_state = dav_get_resource_state(r, resource);
result = dav_meets_conditions(r, resource_state); if (set_etag) { /* * If we have set an ETag to headers out above for * dav_meets_conditions() revert this here as we do not want to set * the ETag in responses to requests with methods where this might not * be desired.
*/
apr_table_unset(r->headers_out, "ETag");
} if (result != OK) { return dav_new_error(r->pool, result, 0, 0, NULL);
}
/* always parse (and later process) the If: header */ if ((err = dav_process_if_header(r, &if_header)) != NULL) { /* ### 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 (locktoken != NULL) {
dav_if_header *ifhdr_new;
/* ** 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 (lockdb == NULL) { if (locks_hooks != NULL) { if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) { /* ### maybe insert higher-level comment */ return err;
}
lock_db_opened_locally = 1;
}
}
/* (1) Validate the specified resource, at the specified depth. * Avoid the walk there is no if_header and we aren't planning
* to modify this resource. */ if (resource->exists && depth > 0 && !(!if_header && flags & DAV_VALIDATE_NO_MODIFY)) {
dav_walker_ctx ctx = { { 0 } };
dav_response *multi_status;
/* ** 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.
*/ if (err != NULL) {
new_response = apr_pcalloc(r->pool, sizeof(*new_response));
new_response->href = parent_resource->uri;
new_response->status = err->status;
new_response->desc = "A validation error has occurred on the parent resource, " "preventing the operation on the resource specified by " "the Request-URI."; if (err->desc != NULL) {
new_response->desc = apr_pstrcat(r->pool,
new_response->desc, " The error was: ",
err->desc, NULL);
}
if (lock_db_opened_locally)
(*locks_hooks->close_lockdb)(lockdb);
/* ** 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 (err == NULL && response != NULL && *response != NULL) {
apr_text *propstat = NULL;
if ((flags & DAV_VALIDATE_USE_424) != 0) { /* manufacture a 424 error to hold the multistatus response(s) */ return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0, 0, "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) {
propstat = apr_pcalloc(r->pool, sizeof(*propstat));
propstat->text = "" DEBUG_CR "" DEBUG_CR "HTTP/1.1 424 Failed Dependency" DEBUG_CR "" DEBUG_CR;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.