util.c revision 866b521be8a30b2798ad3c3b73de5e965edd7c2f
* 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 * the documentation and/or other materials provided with the * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * 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 * ==================================================================== * 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 /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */ /* grow the buffer if necessary */ /* 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 */ ** Extension for ap_sub_req_lookup_uri() which can't handle absolute ** If NULL is returned, then an error occurred with parsing the URI or ** the URI does not match the current server. /* first thing to do is parse the URI into various components */ /* the URI must be an absoluteURI (WEBDAV S9.3) */ /* ### not sure this works if the current request came in via https: */ /* 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)" "Destination URI contains invalid components " "(a query or a fragment).";
/* 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 ** 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 */ result.
err.
desc =
"Destination URI refers to a different server.";
/* 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). /* --------------------------------------------------------------- /* validate that the root element uses a given DAV: tagname (TRUE==valid) */ /* find and return the (unique) child with a given DAV: tagname */ /* --------------------------------------------------------------- ** 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. /* Use the first thing we understand, or infinity if * we don't understand anything. /* ### We need to handle overflow better: * ### timeout will be <= 2^32 - 1 /* --------------------------------------------------------------- /* add_if_resource returns a new if_header, linking it to next_ih. /* add_if_state adds a condition to an if_header. /* ### maybe add a higher-level description */ /* 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. /* 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 const char *
uri =
NULL;
/* scope of current production; NULL=no-tag */ /* 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 */ /* 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:\" " /* List is the entire production (in a uri scope) */ /* ### add a description to this error */ /* ### maybe add a higher level description */ /* ### add a description to this error */ /* ### maybe add a higher level description */ "Invalid \"If:\" header: " "Multiple \"not\" entries " "Invalid \"If:\" header: " "encountered (0x%02x, '%c').",
/* 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 ** One token case: It is an exclusive or shared lock. Either ** 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) /* dav_validate_resource_state: * Returns NULL if path/uri meets if-header and lock requirements /* 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 ** 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 ** 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. */ ** ### hrm... we don't need to have these fully ** ### resolved since we're only looking at the ** ### use get_locks w/ calltype=PARTIAL "The locks could not be queried for " "verification against a possible \"If:\" " /* 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. "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. ** Strictly speaking, we don't need this loop. Either the first ** (and only) lock will be EXCLUSIVE, or none of them will be. "The requested resource is already " ** 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. ** 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 "This resource is locked and an \"If:\" header " "was not supplied to allow access to the " /* 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 ** 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) "The locktoken specified in the \"Lock-Token:\" " "header is invalid because this resource has no " ** 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 ** 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? */ /* If there are if-headers, fail if this resource * does not match at least one state_list. /* 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. /* 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. ** The specified entity-tag does not match the ** entity-tag on the resource. This state_list is ** not going to match. Bust outta here. "an entity-tag was specified, but the resource's " "actual ETag does not match.";
** The specified entity-tag DOES match the ** entity-tag on the resource. This state_list is ** not going to match. Bust outta here. "an entity-tag was specified using the \"Not\" form, " "but the resource's actual ETag matches the provided " /* the locktoken is definitely not there! (success) */ /* 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 ** Go and try the next state list. "a State-token was supplied, but a lock database " "is not available for to provide the required lock.";
/* Resource validation 'fails' if: * ANY of the lock->locktokens match * a NOT state_list->locktoken, * NONE of the lock->locktokens match * a NORMAL state_list->locktoken. 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. */ ** 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 ** 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. "a State-token was supplied, which used a " "\"Not\" condition. The State-token was found " "in the locks on this resource";
/* 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. "\" submitted a locktoken created " ** 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). ** 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. "a State-token was supplied, but it was not found " "in the locks on this resource.";
}
/* 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. ** Haven't seen one. Let's break out of the search and just look ** for a matching locktoken. ** 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 }
/* 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 ** 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 ** If we saw one of the resource's locktokens, then we're done. ** If we did not see a locktoken, then we fail. ** We may have aborted the scan before seeing the locktoken. ** Rescan the If: header to see if we can find the locktoken ** 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. "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). "The locktoken specified in the " "\"Lock-Token:\" header did not specify one " "of this resource's locktoken(s).");
"The preconditions specified by the \"If:\" " "header did not match this resource.");
"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 ** Note that seen_locktoken == 0 implies lock_list != NULL which implies ** We found a match! We're set... we have a matching state list, ** and the If: header specified the locktoken somewhere. We're done. ** 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). "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. */ ** 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? */ /* associate the error with the current URI */ ** 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 ** 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. ** ### bleck. we can't return errors for other URIs unless we have "DESIGN ERROR: dav_validate_request called " "with depth>0, but no response ptr.");
/* 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 */ /* 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. /* ### maybe insert higher-level comment */ "Resource validation failed because no " "lock hooks were found.");
/* (1) Validate the specified resource, at the specified depth */ /* else: implies a 5xx status code occurred. */ /* (2) Validate the parent resource if requested */ "Cannot access parent of repository root.");
** 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. "A validation error has occurred on the parent resource, " "preventing the operation on the resource specified by " /* assert: DAV_VALIDATE_PARENT implies response != NULL */ ** 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. /* manufacture a 424 error to hold the multistatus response(s) */ "An error occurred on another resource, " "preventing the requested operation on " ** 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. "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR /* create the 424 response */ "An error occurred on another resource, preventing the " "requested operation on this resource.";
/* manufacture a 207 error for the multistatus response(s) */ "Error(s) occurred on resources during the " /* 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? */ "No locktokens were specified in the \"If:\" " "header, so the refresh could not be performed.");
/* trim leading whitespace */ while (
apr_isspace(*s))
/* assume: return false for '\0' */ /* trim trailing whitespace */ /* Expect either <DAV:version><DAV:href>URI</DAV:href></DAV:version> * or <DAV:label-name>LABEL</DAV:label-name> */ "Missing DAV:href in DAV:version element");
/* return the contents of the DAV:href element */ /* ### this presumes no child elements */ /* return contents of the DAV:label-name element */ "Unknown version specifier (not DAV:version or DAV:label-name)");
/* no element. see if a Target-Selector header was provided * (which is always interpreted as a label) */ * If there were any headers in the request which require a Vary header * in the response, add it. /* 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 */ /* check parent resource if requested or if resource must be created */ "Missing one or more intermediate collections. " "Cannot create resource %s.",
/* if parent not versioned, assume child can be created */ /* if no versioning provider, something is terribly wrong */ "versioned resource with no versioning " /* parent must be checked out */ /* if parent cannot be automatically checked out, fail */ "Parent collection must be checked out. " "Cannot create resource %s.",
/* 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. "Unable to checkout parent collection. " "Cannot create resource %s.",
/* remember that parent was checked out */ /* if not just checking parent, create new child resource */ "Unable to create versioned resource %s.",
/* remember that resource was created */ /* resource exists and is not versioned; assume it is writable */ /* if not just checking parent, make sure child resource is checked out */ /* Auto-versioning can only be applied to version selectors, so * no separate working resource will be created. */ "Unable to checkout resource %s.",
/* remember that resource was checked out */ /* If a resource was provided, restore its writable state. * Otherwise, only the parent must have been modified */ "Unable to %s resource %s.",
undo ?
"uncheckout" :
"checkin",
/* If undoing because of an error, and the resource was created, /* ### should we do anything with the response? */ "Unable to undo creation of resource %s.",
/* If parent resource was made writable, restore its state */ "Unable to %s parent collection %s.",
undo ?
"uncheckout" :
"checkin",