diff options
Diffstat (limited to 'modules/dav/main/util.c')
-rw-r--r-- | modules/dav/main/util.c | 1958 |
1 files changed, 0 insertions, 1958 deletions
diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c deleted file mode 100644 index 5a8d4728e0..0000000000 --- a/modules/dav/main/util.c +++ /dev/null @@ -1,1958 +0,0 @@ -/* ==================================================================== - * The Apache Software License, Version 1.1 - * - * Copyright (c) 2000-2001 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 - * the documentation and/or other materials provided with the - * 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 - * <http://www.apache.org/>. - */ - -/* -** DAV extension module for Apache 2.0.* -** - various utilities, repository-independent -*/ - -#include "apr_strings.h" -#include "apr_lib.h" - -#define APR_WANT_STRFUNC -#include "apr_want.h" - -#include "mod_dav.h" - -#include "http_request.h" -#include "http_config.h" -#include "http_vhost.h" -#include "http_log.h" -#include "http_protocol.h" - -DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status, - int error_id, const char *desc) -{ - int save_errno = errno; - dav_error *err = apr_pcalloc(p, sizeof(*err)); - - /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */ - - err->status = status; - err->error_id = error_id; - err->desc = desc; - err->save_errno = save_errno; - - return err; -} - -DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status, - int error_id, const char *desc, - dav_error *prev) -{ - dav_error *err = apr_pcalloc(p, sizeof(*err)); - - err->status = status; - err->error_id = error_id; - err->desc = desc; - err->prev = prev; - - return err; -} - -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; - - pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD; - newbuf = apr_palloc(p, pbuf->alloc_len); - memcpy(newbuf, pbuf->buf, pbuf->cur_len); - pbuf->buf = 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; - - pbuf->buf = apr_palloc(p, pbuf->alloc_len); - } - pbuf->cur_len = size; -} - - -/* 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, - const char *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, - const char *str) -{ - 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, - const char *str) -{ - 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, - const void *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_lookup_result dav_lookup_uri(const char *uri, request_rec * r, - int must_be_absolute) -{ - dav_lookup_result result = { 0 }; - const char *scheme; - apr_port_t port; - uri_components comp; - char *new_file; - const char *domain; - - /* first thing to do is parse the URI into various components */ - if (ap_parse_uri_components(r->pool, uri, &comp) != HTTP_OK) { - 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_method(r); - - /* insert a port if the URI did not contain one */ - if (comp.port == 0) - comp.port = ap_default_port_for_scheme(comp.scheme); - - /* now, verify that the URI uses the same scheme as the current. - request. the port must match our port. - */ - apr_sockaddr_port_get(&port, r->connection->local_addr); - if (strcasecmp(comp.scheme, scheme) != 0 || - comp.port != port) { - 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 */ - 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; - } - - /* 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 = ap_unparse_uri_components(r->pool, &comp, 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) */ -int dav_validate_root(const ap_xml_doc *doc, const char *tagname) -{ - return doc->root && - doc->root->ns == AP_XML_NS_DAV_ID && - strcmp(doc->root->name, tagname) == 0; -} - -/* find and return the (unique) child with a given DAV: tagname */ -ap_xml_elem *dav_find_child(const ap_xml_elem *elem, const char *tagname) -{ - ap_xml_elem *child = elem->first_child; - - for (; child; child = child->next) - if (child->ns == AP_XML_NS_DAV_ID && !strcmp(child->name, tagname)) - return child; - return NULL; -} - -/* gather up all the CDATA into a single string */ -const char *dav_xml_get_cdata(const ap_xml_elem *elem, apr_pool_t *pool, - int strip_white) -{ - apr_size_t len = 0; - ap_text *scan; - const ap_xml_elem *child; - char *cdata; - char *s; - apr_size_t tlen; - const char *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); - } - - for (child = elem->first_child; child != NULL; child = child->next) { - for (scan = child->following_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; - } - - cdata = s = apr_palloc(pool, len + 1); - - for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) { - tlen = strlen(scan->text); - memcpy(s, scan->text, tlen); - s += tlen; - } - - for (child = elem->first_child; child != NULL; child = child->next) { - for (scan = child->following_cdata.first; - scan != NULL; - scan = scan->next) { - tlen = strlen(scan->text); - memcpy(s, scan->text, tlen); - s += tlen; - } - } - - *s = '\0'; - - if (strip_white) { - /* trim leading whitespace */ - while (apr_isspace(*cdata)) /* assume: return false for '\0' */ - ++cdata; - - /* trim trailing whitespace */ - while (len-- > 0 && apr_isspace(cdata[len])) - continue; - cdata[len + 1] = '\0'; - } - - return cdata; -} - -/* --------------------------------------------------------------- -** -** 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. - */ -time_t dav_get_timeout(request_rec *r) -{ - time_t now, expires = DAV_TIMEOUT_INFINITE; - - const char *timeout_const = apr_table_get(r->headers_in, "Timeout"); - const char *timeout = apr_pstrdup(r->pool, timeout_const), *val; - - if (timeout == NULL) - return 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, - const char *uri, size_t uri_len) -{ - dav_if_header *ih; - - if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL) - return NULL; - - ih->uri = uri; - ih->uri_len = uri_len; - ih->next = next_ih; - - return ih; -} - -/* 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, - const char *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) { - /* ### 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 occurence of char term, or \0, whichever - * occurs first. Leading whitespace is ignored. - */ -static char *dav_fetch_next_token(char **str, char term) -{ - char *sp; - char *token; - - token = *str + 1; - - while (*token && (*token == ' ' || *token == '\t')) - 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; - const char *state_token; - const char *uri = NULL; /* scope of current production; NULL=no-tag */ - size_t uri_len = 0; - dav_if_header *ih = NULL; - uri_components 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, - "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 (ap_parse_uri_components(r->pool, uri, &parsed_uri) != HTTP_OK) { - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_TAGGED, - "Invalid URI in tagged If-header."); - } - /* note that parsed_uri.path is allocated; we can trash it */ - - /* clean up the URI a bit */ - ap_getparents(parsed_uri.path); - uri_len = strlen(parsed_uri.path); - if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/') - parsed_uri.path[--uri_len] = '\0'; - - uri = parsed_uri.path; - list_type = tagged; - break; - - case '(': - /* List production */ - - /* If a uri has not been encountered, this is a No-Tagged-List */ - if (list_type == unknown) - list_type = no_tagged; - - if ((list = dav_fetch_next_token(&str, ')')) == NULL) { - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_UNCLOSED_PAREN, - "Invalid If-header: unclosed \"(\"."); - } - - if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) { - /* ### dav_add_if_resource() should return an error for us! */ - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_PARSE, - "Internal server error parsing \"If:\" " - "header."); - } - - condition = DAV_IF_COND_NORMAL; - - while (*list) { - /* List is the entire production (in a uri scope) */ - - switch (*list) { - case '<': - if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) { - /* ### add a description to this error */ - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_PARSE, NULL); - } - - if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock, - condition, locks_hooks)) != NULL) { - /* ### maybe add a higher level description */ - return err; - } - condition = DAV_IF_COND_NORMAL; - break; - - case '[': - if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) { - /* ### add a description to this error */ - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_PARSE, NULL); - } - - if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag, - condition, locks_hooks)) != NULL) { - /* ### maybe add a higher level description */ - return err; - } - condition = DAV_IF_COND_NORMAL; - break; - - case 'N': - if (list[1] == 'o' && list[2] == 't') { - if (condition != DAV_IF_COND_NORMAL) { - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_MULTIPLE_NOT, - "Invalid \"If:\" header: " - "Multiple \"not\" entries " - "for the same state."); - } - condition = DAV_IF_COND_NOT; - } - list += 2; - break; - - case ' ': - case '\t': - break; - - default: - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_UNK_CHAR, - apr_psprintf(r->pool, - "Invalid \"If:\" " - "header: Unexpected " - "character encountered " - "(0x%02x, '%c').", - *list, *list)); - } - - list++; - } - break; - - case ' ': - case '\t': - break; - - default: - return dav_new_error(r->pool, HTTP_BAD_REQUEST, - DAV_ERR_IF_UNK_CHAR, - apr_psprintf(r->pool, - "Invalid \"If:\" header: " - "Unexpected character " - "encountered (0x%02x, '%c').", - *str, *str)); - } - - str++; - } - - *p_ih = ih; - return NULL; -} - -static int dav_find_submitted_locktoken(const dav_if_header *if_header, - const dav_lock *lock_list, - const dav_hooks_locks *locks_hooks) -{ - for (; if_header != NULL; if_header = if_header->next) { - const dav_if_state_list *state_list; - - for (state_list = if_header->state; - state_list != NULL; - state_list = state_list->next) { - - 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) { - - if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) { - return 1; - } - } - } - } - } - - return 0; -} - -/* dav_validate_resource_state: - * Returns NULL if path/uri meets if-header and lock requirements - */ -static dav_error * dav_validate_resource_state(apr_pool_t *p, - const dav_resource *resource, - dav_lockdb *lockdb, - const dav_if_header *if_header, - int flags, - dav_buffer *pbuf, - request_rec *r) -{ - dav_error *err; - const char *uri; - const char *etag; - const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL); - const dav_if_header *ifhdr_scan; - dav_if_state_list *state_list; - dav_lock *lock_list; - dav_lock *lock; - int num_matched; - int num_that_apply; - int seen_locktoken; - apr_size_t uri_len; - const char *reason = NULL; - - /* 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. - */ - - 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, - "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. - */ - for (lock = lock_list; lock != NULL; lock = lock->next) { - if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) { - 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. - */ - seen_locktoken = (lock_list == NULL); - } - - /* - ** 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, - "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, - "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) { - - /* DBG2("uri=<%s> if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */ - - 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: - { - int mismatch = strcmp(state_list->etag, 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; - } - else if (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) { - - /* - 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. */ - 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))) { - const char *errmsg; - - errmsg = apr_pstrcat(p, "User \"", - r->user, - "\" submitted a locktoken created " - "by user \"", - lock->auth_user, "\".", NULL); - return dav_new_error(p, HTTP_UNAUTHORIZED, 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; - - } /* 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 */, - "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)."); - } - - if (reason == NULL) { - return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, - "The preconditions specified by the \"If:\" " - "header did not match this resource."); - } - - return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0, - apr_psprintf(p, - "The precondition(s) specified by " - "the \"If:\" header did not match " - "this resource. At least one " - "failure is because: %s", reason)); - } - - /* assert seen_locktoken == 0 */ - - /* - ** ifhdr_scan != NULL implies we found a matching state_list. - ** - ** Since we're still here, it also means that we have not yet found - ** one the resource's locktokens in the If: header. - ** - ** Scan all the if_headers and states looking for one of this - ** resource's locktokens. Note that we need to go back and scan them - ** all -- we may have aborted a scan with a failure before we saw a - ** matching token. - ** - ** Note that seen_locktoken == 0 implies lock_list != NULL which implies - ** locks_hooks != NULL. - */ - 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, - "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 */, - "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; -} - -/* -** 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_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; - -#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, - "DESIGN ERROR: dav_validate_request called " - "with depth>0, but no response ptr."); - } -#endif - - if (response != NULL) - *response = NULL; - - /* Do the standard checks for conditional requests using - * If-..-Since, If-Match etc */ - if ((result = ap_meets_conditions(r)) != OK) { - /* ### fix this up... how? */ - return dav_new_error(r->pool, result, 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; - - ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new)); - ifhdr_new->uri = resource->uri; - ifhdr_new->uri_len = strlen(resource->uri); - ifhdr_new->dummy_header = 1; - - ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state)); - ifhdr_new->state->type = dav_if_opaquelock; - ifhdr_new->state->condition = DAV_IF_COND_NORMAL; - ifhdr_new->state->locktoken = locktoken; - - ifhdr_new->next = if_header; - 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 */ - if (resource->exists && depth > 0) { - dav_walker_ctx ctx = { { 0 } }; - dav_response *multi_status; - - ctx.w.walk_type = DAV_WALKTYPE_NORMAL; - ctx.w.func = dav_validate_walker; - ctx.w.walk_ctx = &ctx; - ctx.w.pool = r->pool; - ctx.w.root = resource; - - ctx.if_header = if_header; - ctx.r = r; - ctx.flags = flags; - - if (lockdb != NULL) { - ctx.w.lockdb = lockdb; - ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL; - } - - err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status); - if (err == NULL) { - *response = multi_status;; - } - /* else: implies a 5xx status code occurred. */ - } - else { - err = dav_validate_resource_state(r->pool, resource, lockdb, - if_header, flags, &work_buf, r); - } - - /* (2) Validate the parent resource if requested */ - if (err == NULL && (flags & DAV_VALIDATE_PARENT)) { - dav_resource *parent_resource; - - err = (*repos_hooks->get_parent_resource)(resource, &parent_resource); - - if (err == NULL && parent_resource == NULL) { - err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0, - "Cannot access parent of repository root."); - } - else if (err == NULL) { - err = dav_validate_resource_state(r->pool, parent_resource, lockdb, - if_header, - flags | DAV_VALIDATE_IS_PARENT, - &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. - */ - 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); - } - - /* assert: DAV_VALIDATE_PARENT implies response != NULL */ - new_response->next = *response; - *response = new_response; - - err = 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) { - ap_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, - "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 = - "<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 = apr_pcalloc(r->pool, sizeof(*new_response)); - new_response->href = resource->uri; - new_response->status = HTTP_FAILED_DEPENDENCY; - new_response->propresult.propstats = propstat; - new_response->desc = - "An error occurred on another resource, preventing the " - "requested operation on this resource."; - - new_response->next = *response; - *response = new_response; - - /* manufacture a 207 error for the multistatus response(s) */ - return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0, - "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. - */ -dav_error * dav_get_locktoken_list(request_rec *r, dav_locktoken_list **ltl) -{ - dav_error *err; - dav_if_header *if_header; - dav_if_state_list *if_state; - dav_locktoken_list *lock_token = NULL; - - *ltl = NULL; - - if ((err = dav_process_if_header(r, &if_header)) != NULL) { - /* ### add a higher-level description? */ - return err; - } - - while (if_header != NULL) { - if_state = if_header->state; /* Begining of the if_state linked list */ - while (if_state != NULL) { - if (if_state->condition == DAV_IF_COND_NORMAL - && if_state->type == dav_if_opaquelock) { - lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list)); - lock_token->locktoken = if_state->locktoken; - lock_token->next = *ltl; - *ltl = lock_token; - } - if_state = if_state->next; - } - if_header = if_header->next; - } - if (*ltl == NULL) { - /* No nodes added */ - return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT, - "No locktokens were specified in the \"If:\" " - "header, so the refresh could not be performed."); - } - - return NULL; -} - -#if 0 /* not needed right now... */ - -static const char *strip_white(const char *s, apr_pool_t *pool) -{ - apr_size_t idx; - - /* trim leading whitespace */ - while (apr_isspace(*s)) /* assume: return false for '\0' */ - ++s; - - /* trim trailing whitespace */ - idx = strlen(s) - 1; - if (apr_isspace(s[idx])) { - char *s2 = apr_pstrdup(pool, s); - - while (apr_isspace(s2[idx]) && idx > 0) - --idx; - s2[idx + 1] = '\0'; - return s2; - } - - return s; -} -#endif - -#define DAV_LABEL_HDR "Label" - -/* dav_add_vary_header - * - * If there were any headers in the request which require a Vary header - * in the response, add it. - */ -void dav_add_vary_header(request_rec *in_req, - request_rec *out_req, - const dav_resource *resource) -{ - const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req); - - /* ### this is probably all wrong... I think there is a function in - ### the Apache API to add things to the Vary header. need to check */ - - /* Only versioning headers require a Vary response header, - * so only do this check if there is a versioning provider */ - if (vsn_hooks != NULL) { - const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR); - const char *vary = apr_table_get(out_req->headers_out, "Vary"); - - /* If Target-Selector specified, add it to the Vary header */ - if (target != NULL) { - if (vary == NULL) - vary = DAV_LABEL_HDR; - else - vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR, - NULL); - - apr_table_setn(out_req->headers_out, "Vary", vary); - } - } -} - -/* dav_can_auto_checkout - * - * Determine whether auto-checkout is enabled for a resource. - * r - the request_rec - * resource - the resource - * auto_version - the value of the auto_versionable hook for the resource - * lockdb - pointer to lock database (opened if necessary) - * auto_checkout - set to 1 if auto-checkout enabled - */ -static dav_error * dav_can_auto_checkout( - request_rec *r, - dav_resource *resource, - dav_auto_version auto_version, - dav_lockdb **lockdb, - int *auto_checkout) -{ - dav_error *err; - dav_lock *lock_list; - - *auto_checkout = 0; - - if (auto_version == DAV_AUTO_VERSION_ALWAYS) { - *auto_checkout = 1; - } - else if (auto_version == DAV_AUTO_VERSION_LOCKED) { - if (*lockdb == NULL) { - const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); - - if (locks_hooks == NULL) { - return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - "Auto-checkout is only enabled for locked resources, " - "but there is no lock provider."); - } - - if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) { - return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - "Cannot open lock database to determine " - "auto-versioning behavior.", - err); - } - } - - if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) { - return dav_push_error(r->pool, - HTTP_INTERNAL_SERVER_ERROR, 0, - "The locks could not be queried for " - "determining auto-versioning behavior.", - err); - } - - if (lock_list != NULL) - *auto_checkout = 1; - } - - return NULL; -} - -/* see mod_dav.h for docco */ -dav_error *dav_auto_checkout( - request_rec *r, - dav_resource *resource, - int parent_only, - dav_auto_version_info *av_info) -{ - const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); - dav_lockdb *lockdb = NULL; - dav_error *err = NULL; - - /* Initialize results */ - memset(av_info, 0, sizeof(*av_info)); - - /* if no versioning provider, just return */ - if (vsn_hooks == NULL) - return NULL; - - /* check parent resource if requested or if resource must be created */ - if (!resource->exists || parent_only) { - dav_resource *parent; - - if ((err = (*resource->hooks->get_parent_resource)(resource, - &parent)) != NULL) - goto done; - - if (parent == NULL || !parent->exists) { - err = dav_new_error(r->pool, HTTP_CONFLICT, 0, - apr_psprintf(r->pool, - "Missing one or more intermediate " - "collections. Cannot create resource %s.", - ap_escape_html(r->pool, resource->uri))); - goto done; - } - - av_info->parent_resource = parent; - - /* if parent versioned and not checked out, see if it can be */ - if (parent->versioned && !parent->working) { - int checkout_parent; - - if ((err = dav_can_auto_checkout(r, parent, - (*vsn_hooks->auto_versionable)(parent), - &lockdb, &checkout_parent)) - != NULL) { - goto done; - } - - if (!checkout_parent) { - err = dav_new_error(r->pool, HTTP_CONFLICT, 0, - "<DAV:cannot-modify-checked-in-parent>"); - goto done; - } - - /* Try to checkout the parent collection. - * Note that auto-versioning can only be applied to a version selector, - * so no separate working resource will be created. - */ - if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/, - 0, 0, 0, NULL, NULL)) - != NULL) - { - err = dav_push_error(r->pool, HTTP_CONFLICT, 0, - apr_psprintf(r->pool, - "Unable to auto-checkout parent collection. " - "Cannot create resource %s.", - ap_escape_html(r->pool, resource->uri)), - err); - goto done; - } - - /* remember that parent was checked out */ - av_info->parent_checkedout = 1; - } - } - - /* if only checking parent, we're done */ - if (parent_only) - goto done; - - /* if creating a new resource, see if it should be version-controlled */ - if (!resource->exists - && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) { - - if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) { - err = dav_push_error(r->pool, HTTP_CONFLICT, 0, - apr_psprintf(r->pool, - "Unable to create versioned resource %s.", - ap_escape_html(r->pool, resource->uri)), - err); - goto done; - } - - /* remember that resource was created */ - av_info->resource_versioned = 1; - } - - /* if resource is versioned, make sure it is checked out */ - if (resource->versioned && !resource->working) { - int checkout_resource; - - if ((err = dav_can_auto_checkout(r, resource, - (*vsn_hooks->auto_versionable)(resource), - &lockdb, &checkout_resource)) != NULL) { - goto done; - } - - if (!checkout_resource) { - err = dav_new_error(r->pool, HTTP_CONFLICT, 0, - "<DAV:cannot-modify-version-controlled-content>"); - goto done; - } - - /* Auto-versioning can only be applied to version selectors, so - * no separate working resource will be created. */ - if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/, - 0, 0, 0, NULL, NULL)) - != NULL) - { - err = dav_push_error(r->pool, HTTP_CONFLICT, 0, - apr_psprintf(r->pool, - "Unable to checkout resource %s.", - ap_escape_html(r->pool, resource->uri)), - err); - goto done; - } - - /* remember that resource was checked out */ - av_info->resource_checkedout = 1; - } - -done: - - /* make sure lock database is closed */ - if (lockdb != NULL) - (*lockdb->hooks->close_lockdb)(lockdb); - - /* if an error occurred, undo any auto-versioning operations already done */ - if (err != NULL) { - dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info); - return err; - } - - return NULL; -} - -/* see mod_dav.h for docco */ -dav_error *dav_auto_checkin( - request_rec *r, - dav_resource *resource, - int undo, - int unlock, - dav_auto_version_info *av_info) -{ - const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); - dav_error *err = NULL; - dav_auto_version auto_version; - - /* If no versioning provider, this is a no-op */ - if (vsn_hooks == NULL) - return NULL; - - /* If undoing auto-checkouts, then do uncheckouts */ - if (undo) { - if (resource != NULL) { - if (av_info->resource_checkedout) { - if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) { - return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - apr_psprintf(r->pool, - "Unable to undo auto-checkout " - "of resource %s.", - ap_escape_html(r->pool, resource->uri)), - err); - } - } - - if (av_info->resource_versioned) { - dav_response *response; - - /* ### should we do anything with the response? */ - if ((err = (*resource->hooks->remove_resource)(resource, - &response)) != NULL) { - return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - apr_psprintf(r->pool, - "Unable to undo auto-version-control " - "of resource %s.", - ap_escape_html(r->pool, resource->uri)), - err); - } - } - } - - if (av_info->parent_resource != NULL && av_info->parent_checkedout) { - if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) { - return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - apr_psprintf(r->pool, - "Unable to undo auto-checkout " - "of parent collection %s.", - ap_escape_html(r->pool, av_info->parent_resource->uri)), - err); - } - } - - return NULL; - } - - /* If the resource was checked out, and auto-checkin is enabled, - * then check it in. - */ - if (resource != NULL && resource->working - && (unlock || av_info->resource_checkedout)) { - - auto_version = (*vsn_hooks->auto_versionable)(resource); - - if (auto_version == DAV_AUTO_VERSION_ALWAYS || - (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) { - - if ((err = (*vsn_hooks->checkin)(resource, - 0 /*keep_checked_out*/, NULL)) - != NULL) { - return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - apr_psprintf(r->pool, - "Unable to auto-checkin resource %s.", - ap_escape_html(r->pool, resource->uri)), - err); - } - } - } - - /* If parent resource was checked out, and auto-checkin is enabled, - * then check it in. - */ - if (av_info->parent_resource != NULL && av_info->parent_resource->working - && (unlock || av_info->parent_checkedout)) { - - auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource); - - if (auto_version == DAV_AUTO_VERSION_ALWAYS || - (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) { - - if ((err = (*vsn_hooks->checkin)(av_info->parent_resource, - 0 /*keep_checked_out*/, NULL)) - != NULL) { - return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, - apr_psprintf(r->pool, - "Unable to auto-checkin parent collection %s.", - ap_escape_html(r->pool, av_info->parent_resource->uri)), - err); - } - } - } - - return NULL; -} |