1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2001 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
56 ** DAV extension module for Apache 2.0.*
57 ** - various utilities, repository-independent
60 #include "apr_strings.h"
63 #define APR_WANT_STRFUNC
68 #include "http_request.h"
69 #include "http_config.h"
70 #include "http_vhost.h"
72 #include "http_protocol.h"
74 DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status,
75 int error_id, const char *desc)
77 int save_errno = errno;
78 dav_error *err = apr_pcalloc(p, sizeof(*err));
80 /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */
83 err->error_id = error_id;
85 err->save_errno = save_errno;
90 DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status,
91 int error_id, const char *desc,
94 dav_error *err = apr_pcalloc(p, sizeof(*err));
97 err->error_id = error_id;
104 DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
105 apr_size_t extra_needed)
107 /* grow the buffer if necessary */
108 if (pbuf->cur_len + extra_needed > pbuf->alloc_len) {
111 pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD;
112 newbuf = apr_palloc(p, pbuf->alloc_len);
113 memcpy(newbuf, pbuf->buf, pbuf->cur_len);
118 DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf,
121 /* NOTE: this does not retain prior contents */
123 /* NOTE: this function is used to init the first pointer, too, since
124 the PAD will be larger than alloc_len (0) for zeroed structures */
126 /* grow if we don't have enough for the requested size plus padding */
127 if (size + DAV_BUFFER_PAD > pbuf->alloc_len) {
128 /* set the new length; min of MINSIZE */
129 pbuf->alloc_len = size + DAV_BUFFER_PAD;
130 if (pbuf->alloc_len < DAV_BUFFER_MINSIZE)
131 pbuf->alloc_len = DAV_BUFFER_MINSIZE;
133 pbuf->buf = apr_palloc(p, pbuf->alloc_len);
135 pbuf->cur_len = size;
139 /* initialize a buffer and copy the specified (null-term'd) string into it */
140 DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf,
143 dav_set_bufsize(p, pbuf, strlen(str));
144 memcpy(pbuf->buf, str, pbuf->cur_len + 1);
147 /* append a string to the end of the buffer, adjust length */
148 DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf,
151 size_t len = strlen(str);
153 dav_check_bufsize(p, pbuf, len + 1);
154 memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
155 pbuf->cur_len += len;
158 /* place a string on the end of the buffer, do NOT adjust length */
159 DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf,
162 size_t len = strlen(str);
164 dav_check_bufsize(p, pbuf, len + 1);
165 memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
168 /* place some memory on the end of a buffer; do NOT adjust length */
169 DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf,
170 const void *mem, apr_size_t amt,
173 dav_check_bufsize(p, pbuf, amt + pad);
174 memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
180 ** Extension for ap_sub_req_lookup_uri() which can't handle absolute
183 ** If NULL is returned, then an error occurred with parsing the URI or
184 ** the URI does not match the current server.
186 dav_lookup_result dav_lookup_uri(const char *uri, request_rec * r,
187 int must_be_absolute)
189 dav_lookup_result result = { 0 };
196 /* first thing to do is parse the URI into various components */
197 if (ap_parse_uri_components(r->pool, uri, &comp) != HTTP_OK) {
198 result.err.status = HTTP_BAD_REQUEST;
199 result.err.desc = "Invalid syntax in Destination URI.";
203 /* the URI must be an absoluteURI (WEBDAV S9.3) */
204 if (comp.scheme == NULL && must_be_absolute) {
205 result.err.status = HTTP_BAD_REQUEST;
206 result.err.desc = "Destination URI must be an absolute URI.";
210 /* the URI must not have a query (args) or a fragment */
211 if (comp.query != NULL || comp.fragment != NULL) {
212 result.err.status = HTTP_BAD_REQUEST;
214 "Destination URI contains invalid components "
215 "(a query or a fragment).";
219 /* If the scheme or port was provided, then make sure that it matches
220 the scheme/port of this request. If the request must be absolute,
221 then require the (explicit/implicit) scheme/port be matching.
223 ### hmm. if a port wasn't provided (does the parse return port==0?),
224 ### but we're on a non-standard port, then we won't detect that the
225 ### URI's port implies the wrong one.
227 if (comp.scheme != NULL || comp.port != 0 || must_be_absolute)
229 /* ### not sure this works if the current request came in via https: */
230 scheme = r->parsed_uri.scheme;
232 scheme = ap_http_method(r);
234 /* insert a port if the URI did not contain one */
236 comp.port = ap_default_port_for_scheme(comp.scheme);
238 /* now, verify that the URI uses the same scheme as the current.
239 request. the port must match our port.
241 apr_sockaddr_port_get(&port, r->connection->local_addr);
242 if (strcasecmp(comp.scheme, scheme) != 0 ||
244 result.err.status = HTTP_BAD_GATEWAY;
245 result.err.desc = apr_psprintf(r->pool,
246 "Destination URI refers to "
247 "different scheme or port "
248 "(%s://hostname:%d)" APR_EOL_STR
249 "(want: %s://hostname:%d)",
250 comp.scheme ? comp.scheme : scheme,
251 comp.port ? comp.port : port,
257 /* we have verified the scheme, port, and general structure */
260 ** Hrm. IE5 will pass unqualified hostnames for both the
261 ** Host: and Destination: headers. This breaks the
262 ** http_vhost.c::matches_aliases function.
264 ** For now, qualify unqualified comp.hostnames with
265 ** r->server->server_hostname.
267 ** ### this is a big hack. Apache should provide a better way.
268 ** ### maybe the admin should list the unqualified hosts in a
269 ** ### <ServerAlias> block?
271 if (comp.hostname != NULL
272 && strrchr(comp.hostname, '.') == NULL
273 && (domain = strchr(r->server->server_hostname, '.')) != NULL) {
274 comp.hostname = apr_pstrcat(r->pool, comp.hostname, domain, NULL);
277 /* now, if a hostname was provided, then verify that it represents the
278 same server as the current connection. note that we just use our
279 port, since we've verified the URI matches ours */
280 if (comp.hostname != NULL &&
281 !ap_matches_request_vhost(r, comp.hostname, port)) {
282 result.err.status = HTTP_BAD_GATEWAY;
283 result.err.desc = "Destination URI refers to a different server.";
287 /* we have verified that the requested URI denotes the same server as
288 the current request. Therefore, we can use ap_sub_req_lookup_uri() */
290 /* reconstruct a URI as just the path */
291 new_file = ap_unparse_uri_components(r->pool, &comp, UNP_OMITSITEPART);
294 * Lookup the URI and return the sub-request. Note that we use the
295 * same HTTP method on the destination. This allows the destination
296 * to apply appropriate restrictions (e.g. readonly).
298 result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
303 /* ---------------------------------------------------------------
305 ** XML UTILITY FUNCTIONS
308 /* validate that the root element uses a given DAV: tagname (TRUE==valid) */
309 int dav_validate_root(const ap_xml_doc *doc, const char *tagname)
312 doc->root->ns == AP_XML_NS_DAV_ID &&
313 strcmp(doc->root->name, tagname) == 0;
316 /* find and return the (unique) child with a given DAV: tagname */
317 ap_xml_elem *dav_find_child(const ap_xml_elem *elem, const char *tagname)
319 ap_xml_elem *child = elem->first_child;
321 for (; child; child = child->next)
322 if (child->ns == AP_XML_NS_DAV_ID && !strcmp(child->name, tagname))
327 /* gather up all the CDATA into a single string */
328 const char *dav_xml_get_cdata(const ap_xml_elem *elem, apr_pool_t *pool,
333 const ap_xml_elem *child;
337 const char *found_text = NULL; /* initialize to avoid gcc warning */
340 for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
341 found_text = scan->text;
343 len += strlen(found_text);
346 for (child = elem->first_child; child != NULL; child = child->next) {
347 for (scan = child->following_cdata.first;
350 found_text = scan->text;
352 len += strlen(found_text);
356 /* some fast-path cases:
357 * 1) zero-length cdata
358 * 2) a single piece of cdata with no whitespace to strip
362 if (found_count == 1) {
364 || (!apr_isspace(*found_text)
365 && !apr_isspace(found_text[len - 1])))
369 cdata = s = apr_palloc(pool, len + 1);
371 for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
372 tlen = strlen(scan->text);
373 memcpy(s, scan->text, tlen);
377 for (child = elem->first_child; child != NULL; child = child->next) {
378 for (scan = child->following_cdata.first;
381 tlen = strlen(scan->text);
382 memcpy(s, scan->text, tlen);
390 /* trim leading whitespace */
391 while (apr_isspace(*cdata)) /* assume: return false for '\0' */
394 /* trim trailing whitespace */
395 while (len-- > 0 && apr_isspace(cdata[len]))
397 cdata[len + 1] = '\0';
403 /* ---------------------------------------------------------------
405 ** Timeout header processing
409 /* dav_get_timeout: If the Timeout: header exists, return a time_t
410 * when this lock is expected to expire. Otherwise, return
411 * a time_t of DAV_TIMEOUT_INFINITE.
413 * It's unclear if DAV clients are required to understand
414 * Seconds-xxx and Infinity time values. We assume that they do.
415 * In addition, for now, that's all we understand, too.
417 time_t dav_get_timeout(request_rec *r)
419 time_t now, expires = DAV_TIMEOUT_INFINITE;
421 const char *timeout_const = apr_table_get(r->headers_in, "Timeout");
422 const char *timeout = apr_pstrdup(r->pool, timeout_const), *val;
425 return DAV_TIMEOUT_INFINITE;
427 /* Use the first thing we understand, or infinity if
428 * we don't understand anything.
431 while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) {
432 if (!strncmp(val, "Infinite", 8)) {
433 return DAV_TIMEOUT_INFINITE;
436 if (!strncmp(val, "Second-", 7)) {
438 /* ### We need to handle overflow better:
439 * ### timeout will be <= 2^32 - 1
443 return now + expires;
447 return DAV_TIMEOUT_INFINITE;
450 /* ---------------------------------------------------------------
452 ** If Header processing
456 /* add_if_resource returns a new if_header, linking it to next_ih.
458 static dav_if_header *dav_add_if_resource(apr_pool_t *p, dav_if_header *next_ih,
459 const char *uri, size_t uri_len)
463 if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL)
467 ih->uri_len = uri_len;
473 /* add_if_state adds a condition to an if_header.
475 static dav_error * dav_add_if_state(apr_pool_t *p, dav_if_header *ih,
476 const char *state_token,
477 dav_if_state_type t, int condition,
478 const dav_hooks_locks *locks_hooks)
480 dav_if_state_list *new_sl;
482 new_sl = apr_pcalloc(p, sizeof(*new_sl));
484 new_sl->condition = condition;
487 if (t == dav_if_opaquelock) {
490 if ((err = (*locks_hooks->parse_locktoken)(p, state_token,
491 &new_sl->locktoken)) != NULL) {
492 /* ### maybe add a higher-level description */
497 new_sl->etag = state_token;
499 new_sl->next = ih->state;
505 /* fetch_next_token returns the substring from str+1
506 * to the next occurence of char term, or \0, whichever
507 * occurs first. Leading whitespace is ignored.
509 static char *dav_fetch_next_token(char **str, char term)
516 while (*token && (*token == ' ' || *token == '\t'))
519 if ((sp = strchr(token, term)) == NULL)
527 /* dav_process_if_header:
529 * If NULL (no error) is returned, then **if_header points to the
530 * "If" productions structure (or NULL if "If" is not present).
532 * ### this part is bogus:
533 * If an error is encountered, the error is logged. Parent should
534 * return err->status.
536 static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
541 const char *state_token;
542 const char *uri = NULL; /* scope of current production; NULL=no-tag */
544 dav_if_header *ih = NULL;
545 uri_components parsed_uri;
546 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
547 enum {no_tagged, tagged, unknown} list_type = unknown;
552 if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL)
558 /* Tagged-list production - following states apply to this uri */
559 if (list_type == no_tagged
560 || ((uri = dav_fetch_next_token(&str, '>')) == NULL)) {
561 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
563 "Invalid If-header: unclosed \"<\" or "
564 "unexpected tagged-list production.");
567 /* 2518 specifies this must be an absolute URI; just take the
568 * relative part for later comparison against r->uri */
569 if (ap_parse_uri_components(r->pool, uri, &parsed_uri) != HTTP_OK) {
570 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
572 "Invalid URI in tagged If-header.");
574 /* note that parsed_uri.path is allocated; we can trash it */
576 /* clean up the URI a bit */
577 ap_getparents(parsed_uri.path);
578 uri_len = strlen(parsed_uri.path);
579 if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/')
580 parsed_uri.path[--uri_len] = '\0';
582 uri = parsed_uri.path;
587 /* List production */
589 /* If a uri has not been encountered, this is a No-Tagged-List */
590 if (list_type == unknown)
591 list_type = no_tagged;
593 if ((list = dav_fetch_next_token(&str, ')')) == NULL) {
594 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
595 DAV_ERR_IF_UNCLOSED_PAREN,
596 "Invalid If-header: unclosed \"(\".");
599 if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) {
600 /* ### dav_add_if_resource() should return an error for us! */
601 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
603 "Internal server error parsing \"If:\" "
607 condition = DAV_IF_COND_NORMAL;
610 /* List is the entire production (in a uri scope) */
614 if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) {
615 /* ### add a description to this error */
616 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
617 DAV_ERR_IF_PARSE, NULL);
620 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock,
621 condition, locks_hooks)) != NULL) {
622 /* ### maybe add a higher level description */
625 condition = DAV_IF_COND_NORMAL;
629 if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) {
630 /* ### add a description to this error */
631 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
632 DAV_ERR_IF_PARSE, NULL);
635 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag,
636 condition, locks_hooks)) != NULL) {
637 /* ### maybe add a higher level description */
640 condition = DAV_IF_COND_NORMAL;
644 if (list[1] == 'o' && list[2] == 't') {
645 if (condition != DAV_IF_COND_NORMAL) {
646 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
647 DAV_ERR_IF_MULTIPLE_NOT,
648 "Invalid \"If:\" header: "
649 "Multiple \"not\" entries "
650 "for the same state.");
652 condition = DAV_IF_COND_NOT;
662 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
664 apr_psprintf(r->pool,
666 "header: Unexpected "
667 "character encountered "
681 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
683 apr_psprintf(r->pool,
684 "Invalid \"If:\" header: "
685 "Unexpected character "
686 "encountered (0x%02x, '%c').",
697 static int dav_find_submitted_locktoken(const dav_if_header *if_header,
698 const dav_lock *lock_list,
699 const dav_hooks_locks *locks_hooks)
701 for (; if_header != NULL; if_header = if_header->next) {
702 const dav_if_state_list *state_list;
704 for (state_list = if_header->state;
706 state_list = state_list->next) {
708 if (state_list->type == dav_if_opaquelock) {
709 const dav_lock *lock;
711 /* given state_list->locktoken, match it */
714 ** The resource will have one or more lock tokens. We only
715 ** need to match one of them against any token in the
718 ** One token case: It is an exclusive or shared lock. Either
719 ** way, we must find it.
721 ** N token case: They are shared locks. By policy, we need
722 ** to match only one. The resource's other
723 ** tokens may belong to somebody else (so we
724 ** shouldn't see them in the If: header anyway)
726 for (lock = lock_list; lock != NULL; lock = lock->next) {
728 if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
739 /* dav_validate_resource_state:
740 * Returns NULL if path/uri meets if-header and lock requirements
742 static dav_error * dav_validate_resource_state(apr_pool_t *p,
743 const dav_resource *resource,
745 const dav_if_header *if_header,
753 const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL);
754 const dav_if_header *ifhdr_scan;
755 dav_if_state_list *state_list;
762 const char *reason = NULL;
764 /* DBG1("validate: <%s>", resource->uri); */
767 ** The resource will have one of three states:
769 ** 1) No locks. We have no special requirements that the user supply
770 ** specific locktokens. One of the state lists must match, and
773 ** 2) One exclusive lock. The locktoken must appear *anywhere* in the
774 ** If: header. Of course, asserting the token in a "Not" term will
775 ** quickly fail that state list :-). If the locktoken appears in
776 ** one of the state lists *and* one state list matches, then we're
779 ** 3) One or more shared locks. One of the locktokens must appear
780 ** *anywhere* in the If: header. If one of the locktokens appears,
781 ** and we match one state list, then we are done.
783 ** The <seen_locktoken> variable determines whether we have seen one
784 ** of this resource's locktokens in the If: header.
788 ** If this is a new lock request, <flags> will contain the requested
789 ** lock scope. Three rules apply:
791 ** 1) Do not require a (shared) locktoken to be seen (when we are
792 ** applying another shared lock)
793 ** 2) If the scope is exclusive and we see any locks, fail.
794 ** 3) If the scope is shared and we see an exclusive lock, fail.
797 if (lockdb == NULL) {
798 /* we're in State 1. no locks. */
803 ** ### hrm... we don't need to have these fully
804 ** ### resolved since we're only looking at the
807 ** ### use get_locks w/ calltype=PARTIAL
809 if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) {
810 return dav_push_error(p,
811 HTTP_INTERNAL_SERVER_ERROR, 0,
812 "The locks could not be queried for "
813 "verification against a possible \"If:\" "
818 /* lock_list now determines whether we're in State 1, 2, or 3. */
822 ** For a new, exclusive lock: if any locks exist, fail.
823 ** For a new, shared lock: if an exclusive lock exists, fail.
824 ** else, do not require a token to be seen.
826 if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
827 if (lock_list != NULL) {
828 return dav_new_error(p, HTTP_LOCKED, 0,
829 "Existing lock(s) on the requested resource "
830 "prevent an exclusive lock.");
834 ** There are no locks, so we can pretend that we've already met
835 ** any requirement to find the resource's locks in an If: header.
839 else if (flags & DAV_LOCKSCOPE_SHARED) {
841 ** Strictly speaking, we don't need this loop. Either the first
842 ** (and only) lock will be EXCLUSIVE, or none of them will be.
844 for (lock = lock_list; lock != NULL; lock = lock->next) {
845 if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) {
846 return dav_new_error(p, HTTP_LOCKED, 0,
847 "The requested resource is already "
848 "locked exclusively.");
853 ** The locks on the resource (if any) are all shared. Set the
854 ** <seen_locktoken> flag to indicate that we do not need to find
855 ** the locks in an If: header.
861 ** For methods other than LOCK:
863 ** If we have no locks, then <seen_locktoken> can be set to true --
864 ** pretending that we've already met the requirement of seeing one
865 ** of the resource's locks in the If: header.
867 ** Otherwise, it must be cleared and we'll look for one.
869 seen_locktoken = (lock_list == NULL);
873 ** If there is no If: header, then we can shortcut some logic:
875 ** 1) if we do not need to find a locktoken in the (non-existent) If:
876 ** header, then we are successful.
878 ** 2) if we must find a locktoken in the (non-existent) If: header, then
881 if (if_header == NULL) {
885 return dav_new_error(p, HTTP_LOCKED, 0,
886 "This resource is locked and an \"If:\" header "
887 "was not supplied to allow access to the "
890 /* the If: header is present */
893 ** If a dummy header is present (because of a Lock-Token: header), then
894 ** we are required to find that token in this resource's set of locks.
895 ** If we have no locks, then we immediately fail.
897 ** This is a 400 (Bad Request) since they should only submit a locktoken
898 ** that actually exists.
900 ** Don't issue this response if we're talking about the parent resource.
901 ** It is okay for that resource to NOT have this locktoken.
902 ** (in fact, it certainly will not: a dummy_header only occurs for the
903 ** UNLOCK method, the parent is checked only for locknull resources,
904 ** and the parent certainly does not have the (locknull's) locktoken)
906 if (lock_list == NULL && if_header->dummy_header) {
907 if (flags & DAV_VALIDATE_IS_PARENT)
909 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
910 "The locktoken specified in the \"Lock-Token:\" "
911 "header is invalid because this resource has no "
912 "outstanding locks.");
916 ** Prepare the input URI. We want the URI to never have a trailing slash.
918 ** When URIs are placed into the dav_if_header structure, they are
919 ** guaranteed to never have a trailing slash. If the URIs are equivalent,
920 ** then it doesn't matter if they both lack a trailing slash -- they're
923 ** Note: we could also ensure that a trailing slash is present on both
924 ** URIs, but the majority of URIs provided to us via a resource walk
925 ** will not contain that trailing slash.
928 uri_len = strlen(uri);
929 if (uri[uri_len - 1] == '/') {
930 dav_set_bufsize(p, pbuf, uri_len);
931 memcpy(pbuf->buf, uri, uri_len);
932 pbuf->buf[--uri_len] = '\0';
936 /* get the resource's etag; we may need it during the checks */
937 etag = (*resource->hooks->getetag)(resource);
939 /* how many state_lists apply to this URI? */
942 /* If there are if-headers, fail if this resource
943 * does not match at least one state_list.
945 for (ifhdr_scan = if_header;
947 ifhdr_scan = ifhdr_scan->next) {
949 /* DBG2("uri=<%s> if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
951 if (ifhdr_scan->uri != NULL
952 && (uri_len != ifhdr_scan->uri_len
953 || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
955 ** A tagged-list's URI doesn't match this resource's URI.
956 ** Skip to the next state_list to see if it will match.
961 /* this state_list applies to this resource */
964 ** ### only one state_list should ever apply! a no-tag, or a tagged
965 ** ### where S9.4.2 states only one can match.
967 ** ### revamp this code to loop thru ifhdr_scan until we find the
968 ** ### matching state_list. process it. stop.
972 /* To succeed, resource must match *all* of the states
973 * specified in the state_list.
975 for (state_list = ifhdr_scan->state;
977 state_list = state_list->next) {
979 switch(state_list->type) {
982 int mismatch = strcmp(state_list->etag, etag);
984 if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
986 ** The specified entity-tag does not match the
987 ** entity-tag on the resource. This state_list is
988 ** not going to match. Bust outta here.
991 "an entity-tag was specified, but the resource's "
992 "actual ETag does not match.";
993 goto state_list_failed;
995 else if (state_list->condition == DAV_IF_COND_NOT
998 ** The specified entity-tag DOES match the
999 ** entity-tag on the resource. This state_list is
1000 ** not going to match. Bust outta here.
1003 "an entity-tag was specified using the \"Not\" form, "
1004 "but the resource's actual ETag matches the provided "
1006 goto state_list_failed;
1011 case dav_if_opaquelock:
1012 if (lockdb == NULL) {
1013 if (state_list->condition == DAV_IF_COND_NOT) {
1014 /* the locktoken is definitely not there! (success) */
1018 /* condition == DAV_IF_COND_NORMAL */
1021 ** If no lockdb is provided, then validation fails for
1022 ** this state_list (NORMAL means we were supposed to
1023 ** find the token, which we obviously cannot do without
1024 ** a lock database).
1026 ** Go and try the next state list.
1029 "a State-token was supplied, but a lock database "
1030 "is not available for to provide the required lock.";
1031 goto state_list_failed;
1034 /* Resource validation 'fails' if:
1035 * ANY of the lock->locktokens match
1036 * a NOT state_list->locktoken,
1038 * NONE of the lock->locktokens match
1039 * a NORMAL state_list->locktoken.
1042 for (lock = lock_list; lock != NULL; lock = lock->next) {
1045 DBG2("compare: rsrc=%s ifhdr=%s",
1046 (*locks_hooks->format_locktoken)(p, lock->locktoken),
1047 (*locks_hooks->format_locktoken)(p, state_list->locktoken));
1050 /* nothing to do if the locktokens do not match. */
1051 if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
1056 ** We have now matched up one of the resource's locktokens
1057 ** to a locktoken in a State-token in the If: header.
1058 ** Note this fact, so that we can pass the overall
1059 ** requirement of seeing at least one of the resource's
1064 if (state_list->condition == DAV_IF_COND_NOT) {
1066 ** This state requires that the specified locktoken
1067 ** is NOT present on the resource. But we just found
1068 ** it. There is no way this state-list can now
1069 ** succeed, so go try another one.
1072 "a State-token was supplied, which used a "
1073 "\"Not\" condition. The State-token was found "
1074 "in the locks on this resource";
1075 goto state_list_failed;
1078 /* condition == DAV_IF_COND_NORMAL */
1080 /* Validate auth_user: If an authenticated user created
1081 ** the lock, only the same user may submit that locktoken
1082 ** to manipulate a resource.
1084 if (lock->auth_user &&
1086 strcmp(lock->auth_user, r->user))) {
1089 errmsg = apr_pstrcat(p, "User \"",
1091 "\" submitted a locktoken created "
1093 lock->auth_user, "\".", NULL);
1094 return dav_new_error(p, HTTP_UNAUTHORIZED, 0, errmsg);
1098 ** We just matched a specified State-Token to one of the
1099 ** resource's locktokens.
1101 ** Break out of the lock scan -- we only needed to find
1102 ** one match (actually, there shouldn't be any other
1103 ** matches in the lock list).
1109 if (num_matched == 0
1110 && state_list->condition == DAV_IF_COND_NORMAL) {
1112 ** We had a NORMAL state, meaning that we should have
1113 ** found the State-Token within the locks on this
1114 ** resource. We didn't, so this state_list must fail.
1117 "a State-token was supplied, but it was not found "
1118 "in the locks on this resource.";
1119 goto state_list_failed;
1125 } /* foreach ( state_list ) */
1128 ** We've checked every state in this state_list and none of them
1129 ** have failed. Since all of them succeeded, then we have a matching
1130 ** state list and we may be done.
1132 ** The next requirement is that we have seen one of the resource's
1133 ** locktokens (if any). If we have, then we can just exit. If we
1134 ** haven't, then we need to keep looking.
1136 if (seen_locktoken) {
1142 ** Haven't seen one. Let's break out of the search and just look
1143 ** for a matching locktoken.
1148 ** This label is used when we detect that a state_list is not
1149 ** going to match this resource. We bust out and try the next
1155 } /* foreach ( ifhdr_scan ) */
1158 ** The above loop exits for one of two reasons:
1159 ** 1) a state_list matched and seen_locktoken is false.
1160 ** 2) all if_header structures were scanned, without (1) occurring
1163 if (ifhdr_scan == NULL) {
1165 ** We finished the loop without finding any matching state lists.
1169 ** If none of the state_lists apply to this resource, then we
1170 ** may have succeeded. Note that this scenario implies a
1171 ** tagged-list with no matching state_lists. If the If: header
1172 ** was a no-tag-list, then it would have applied to this resource.
1174 ** S9.4.2 states that when no state_lists apply, then the header
1175 ** should be ignored.
1177 ** If we saw one of the resource's locktokens, then we're done.
1178 ** If we did not see a locktoken, then we fail.
1180 if (num_that_apply == 0) {
1185 ** We may have aborted the scan before seeing the locktoken.
1186 ** Rescan the If: header to see if we can find the locktoken
1189 ** Note that seen_locktoken == 0 implies lock_list != NULL
1190 ** which implies locks_hooks != NULL.
1192 if (dav_find_submitted_locktoken(if_header, lock_list,
1195 ** We found a match! We're set... none of the If: header
1196 ** assertions apply (implicit success), and the If: header
1197 ** specified the locktoken somewhere. We're done.
1202 return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */,
1203 "This resource is locked and the \"If:\" "
1204 "header did not specify one of the "
1205 "locktokens for this resource's lock(s).");
1207 /* else: one or more state_lists were applicable, but failed. */
1210 ** If the dummy_header did not match, then they specified an
1211 ** incorrect token in the Lock-Token header. Forget whether the
1212 ** If: statement matched or not... we'll tell them about the
1213 ** bad Lock-Token first. That is considered a 400 (Bad Request).
1215 if (if_header->dummy_header) {
1216 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1217 "The locktoken specified in the "
1218 "\"Lock-Token:\" header did not specify one "
1219 "of this resource's locktoken(s).");
1222 if (reason == NULL) {
1223 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1224 "The preconditions specified by the \"If:\" "
1225 "header did not match this resource.");
1228 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1230 "The precondition(s) specified by "
1231 "the \"If:\" header did not match "
1232 "this resource. At least one "
1233 "failure is because: %s", reason));
1236 /* assert seen_locktoken == 0 */
1239 ** ifhdr_scan != NULL implies we found a matching state_list.
1241 ** Since we're still here, it also means that we have not yet found
1242 ** one the resource's locktokens in the If: header.
1244 ** Scan all the if_headers and states looking for one of this
1245 ** resource's locktokens. Note that we need to go back and scan them
1246 ** all -- we may have aborted a scan with a failure before we saw a
1249 ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
1250 ** locks_hooks != NULL.
1252 if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
1254 ** We found a match! We're set... we have a matching state list,
1255 ** and the If: header specified the locktoken somewhere. We're done.
1261 ** We had a matching state list, but the user agent did not specify one
1262 ** of this resource's locktokens. Tell them so.
1264 ** Note that we need to special-case the message on whether a "dummy"
1265 ** header exists. If it exists, yet we didn't see a needed locktoken,
1266 ** then that implies the dummy header (Lock-Token header) did NOT
1267 ** specify one of this resource's locktokens. (this implies something
1268 ** in the real If: header matched)
1270 ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
1272 if (if_header->dummy_header) {
1273 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1274 "The locktoken specified in the "
1275 "\"Lock-Token:\" header did not specify one "
1276 "of this resource's locktoken(s).");
1279 return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */,
1280 "This resource is locked and the \"If:\" header "
1281 "did not specify one of the "
1282 "locktokens for this resource's lock(s).");
1285 /* dav_validate_walker: Walker callback function to validate resource state */
1286 static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
1288 dav_walker_ctx *ctx = wres->walk_ctx;
1291 if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
1293 ctx->if_header, ctx->flags,
1294 &ctx->work_buf, ctx->r)) == NULL) {
1295 /* There was no error, so just bug out. */
1300 ** If we have a serious server error, or if the request itself failed,
1301 ** then just return error (not a multistatus).
1303 if (ap_is_HTTP_SERVER_ERROR(err->status)
1304 || (*wres->resource->hooks->is_same_resource)(wres->resource,
1306 /* ### maybe push a higher-level description? */
1310 /* associate the error with the current URI */
1311 dav_add_response(wres, err->status, NULL);
1317 ** dav_validate_request: Validate if-headers (and check for locks) on:
1318 ** (1) r->filename @ depth;
1319 ** (2) Parent of r->filename if check_parent == 1
1321 ** The check of parent should be done when it is necessary to verify that
1322 ** the parent collection will accept a new member (ie current resource
1325 ** Return OK on successful validation.
1326 ** On error, return appropriate HTTP_* code, and log error. If a multi-stat
1327 ** error is necessary, response will point to it, else NULL.
1329 dav_error * dav_validate_request(request_rec *r, dav_resource *resource,
1330 int depth, dav_locktoken *locktoken,
1331 dav_response **response, int flags,
1336 dav_if_header *if_header;
1337 int lock_db_opened_locally = 0;
1338 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1339 const dav_hooks_repository *repos_hooks = resource->hooks;
1340 dav_buffer work_buf = { 0 };
1341 dav_response *new_response;
1344 if (depth && response == NULL) {
1346 ** ### bleck. we can't return errors for other URIs unless we have
1347 ** ### a "response" ptr.
1349 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1350 "DESIGN ERROR: dav_validate_request called "
1351 "with depth>0, but no response ptr.");
1355 if (response != NULL)
1358 /* Do the standard checks for conditional requests using
1359 * If-..-Since, If-Match etc */
1360 if ((result = ap_meets_conditions(r)) != OK) {
1361 /* ### fix this up... how? */
1362 return dav_new_error(r->pool, result, 0, NULL);
1365 /* always parse (and later process) the If: header */
1366 if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1367 /* ### maybe add higher-level description */
1371 /* If a locktoken was specified, create a dummy if_header with which
1372 * to validate resources. In the interim, figure out why DAV uses
1373 * locktokens in an if-header without a Lock-Token header to refresh
1374 * locks, but a Lock-Token header without an if-header to remove them.
1376 if (locktoken != NULL) {
1377 dav_if_header *ifhdr_new;
1379 ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new));
1380 ifhdr_new->uri = resource->uri;
1381 ifhdr_new->uri_len = strlen(resource->uri);
1382 ifhdr_new->dummy_header = 1;
1384 ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state));
1385 ifhdr_new->state->type = dav_if_opaquelock;
1386 ifhdr_new->state->condition = DAV_IF_COND_NORMAL;
1387 ifhdr_new->state->locktoken = locktoken;
1389 ifhdr_new->next = if_header;
1390 if_header = ifhdr_new;
1394 ** If necessary, open the lock database (read-only, lazily);
1395 ** the validation process may need to retrieve or update lock info.
1396 ** Otherwise, assume provided lockdb is valid and opened rw.
1398 if (lockdb == NULL) {
1399 if (locks_hooks != NULL) {
1400 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1401 /* ### maybe insert higher-level comment */
1404 lock_db_opened_locally = 1;
1408 /* (1) Validate the specified resource, at the specified depth */
1409 if (resource->exists && depth > 0) {
1410 dav_walker_ctx ctx = { { 0 } };
1411 dav_response *multi_status;
1413 ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
1414 ctx.w.func = dav_validate_walker;
1415 ctx.w.walk_ctx = &ctx;
1416 ctx.w.pool = r->pool;
1417 ctx.w.root = resource;
1419 ctx.if_header = if_header;
1423 if (lockdb != NULL) {
1424 ctx.w.lockdb = lockdb;
1425 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1428 err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
1430 *response = multi_status;;
1432 /* else: implies a 5xx status code occurred. */
1435 err = dav_validate_resource_state(r->pool, resource, lockdb,
1436 if_header, flags, &work_buf, r);
1439 /* (2) Validate the parent resource if requested */
1440 if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
1441 dav_resource *parent_resource;
1443 err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
1445 if (err == NULL && parent_resource == NULL) {
1446 err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
1447 "Cannot access parent of repository root.");
1449 else if (err == NULL) {
1450 err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
1452 flags | DAV_VALIDATE_IS_PARENT,
1456 ** This error occurred on the parent resource. This implies that
1457 ** we have to create a multistatus response (to report the error
1458 ** against a URI other than the Request-URI). "Convert" this error
1459 ** into a multistatus response.
1462 new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1464 new_response->href = parent_resource->uri;
1465 new_response->status = err->status;
1466 new_response->desc =
1467 "A validation error has occurred on the parent resource, "
1468 "preventing the operation on the resource specified by "
1470 if (err->desc != NULL) {
1471 new_response->desc = apr_pstrcat(r->pool,
1477 /* assert: DAV_VALIDATE_PARENT implies response != NULL */
1478 new_response->next = *response;
1479 *response = new_response;
1486 if (lock_db_opened_locally)
1487 (*locks_hooks->close_lockdb)(lockdb);
1490 ** If we don't have a (serious) error, and we have multistatus responses,
1491 ** then we need to construct an "error". This error will be the overall
1492 ** status returned, and the multistatus responses will go into its body.
1494 ** For certain methods, the overall error will be a 424. The default is
1495 ** to construct a standard 207 response.
1497 if (err == NULL && response != NULL && *response != NULL) {
1498 ap_text *propstat = NULL;
1500 if ((flags & DAV_VALIDATE_USE_424) != 0) {
1501 /* manufacture a 424 error to hold the multistatus response(s) */
1502 return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0,
1503 "An error occurred on another resource, "
1504 "preventing the requested operation on "
1509 ** Whatever caused the error, the Request-URI should have a 424
1510 ** associated with it since we cannot complete the method.
1512 ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
1513 ** For other methods, return a simple 424.
1515 if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
1516 propstat = apr_pcalloc(r->pool, sizeof(*propstat));
1518 "<D:propstat>" DEBUG_CR
1519 "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
1520 "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
1521 "</D:propstat>" DEBUG_CR;
1524 /* create the 424 response */
1525 new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1526 new_response->href = resource->uri;
1527 new_response->status = HTTP_FAILED_DEPENDENCY;
1528 new_response->propresult.propstats = propstat;
1529 new_response->desc =
1530 "An error occurred on another resource, preventing the "
1531 "requested operation on this resource.";
1533 new_response->next = *response;
1534 *response = new_response;
1536 /* manufacture a 207 error for the multistatus response(s) */
1537 return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
1538 "Error(s) occurred on resources during the "
1539 "validation process.");
1545 /* dav_get_locktoken_list:
1547 * Sets ltl to a locktoken_list of all positive locktokens in header,
1548 * else NULL if no If-header, or no positive locktokens.
1550 dav_error * dav_get_locktoken_list(request_rec *r, dav_locktoken_list **ltl)
1553 dav_if_header *if_header;
1554 dav_if_state_list *if_state;
1555 dav_locktoken_list *lock_token = NULL;
1559 if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1560 /* ### add a higher-level description? */
1564 while (if_header != NULL) {
1565 if_state = if_header->state; /* Begining of the if_state linked list */
1566 while (if_state != NULL) {
1567 if (if_state->condition == DAV_IF_COND_NORMAL
1568 && if_state->type == dav_if_opaquelock) {
1569 lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list));
1570 lock_token->locktoken = if_state->locktoken;
1571 lock_token->next = *ltl;
1574 if_state = if_state->next;
1576 if_header = if_header->next;
1579 /* No nodes added */
1580 return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT,
1581 "No locktokens were specified in the \"If:\" "
1582 "header, so the refresh could not be performed.");
1588 #if 0 /* not needed right now... */
1590 static const char *strip_white(const char *s, apr_pool_t *pool)
1594 /* trim leading whitespace */
1595 while (apr_isspace(*s)) /* assume: return false for '\0' */
1598 /* trim trailing whitespace */
1599 idx = strlen(s) - 1;
1600 if (apr_isspace(s[idx])) {
1601 char *s2 = apr_pstrdup(pool, s);
1603 while (apr_isspace(s2[idx]) && idx > 0)
1613 #define DAV_LABEL_HDR "Label"
1615 /* dav_add_vary_header
1617 * If there were any headers in the request which require a Vary header
1618 * in the response, add it.
1620 void dav_add_vary_header(request_rec *in_req,
1621 request_rec *out_req,
1622 const dav_resource *resource)
1624 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
1626 /* ### this is probably all wrong... I think there is a function in
1627 ### the Apache API to add things to the Vary header. need to check */
1629 /* Only versioning headers require a Vary response header,
1630 * so only do this check if there is a versioning provider */
1631 if (vsn_hooks != NULL) {
1632 const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR);
1633 const char *vary = apr_table_get(out_req->headers_out, "Vary");
1635 /* If Target-Selector specified, add it to the Vary header */
1636 if (target != NULL) {
1638 vary = DAV_LABEL_HDR;
1640 vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
1643 apr_table_setn(out_req->headers_out, "Vary", vary);
1648 /* dav_can_auto_checkout
1650 * Determine whether auto-checkout is enabled for a resource.
1651 * r - the request_rec
1652 * resource - the resource
1653 * auto_version - the value of the auto_versionable hook for the resource
1654 * lockdb - pointer to lock database (opened if necessary)
1655 * auto_checkout - set to 1 if auto-checkout enabled
1657 static dav_error * dav_can_auto_checkout(
1659 dav_resource *resource,
1660 dav_auto_version auto_version,
1661 dav_lockdb **lockdb,
1665 dav_lock *lock_list;
1669 if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
1672 else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
1673 if (*lockdb == NULL) {
1674 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1676 if (locks_hooks == NULL) {
1677 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1678 "Auto-checkout is only enabled for locked resources, "
1679 "but there is no lock provider.");
1682 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) {
1683 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1684 "Cannot open lock database to determine "
1685 "auto-versioning behavior.",
1690 if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) {
1691 return dav_push_error(r->pool,
1692 HTTP_INTERNAL_SERVER_ERROR, 0,
1693 "The locks could not be queried for "
1694 "determining auto-versioning behavior.",
1698 if (lock_list != NULL)
1705 /* see mod_dav.h for docco */
1706 dav_error *dav_auto_checkout(
1708 dav_resource *resource,
1710 dav_auto_version_info *av_info)
1712 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1713 dav_lockdb *lockdb = NULL;
1714 dav_error *err = NULL;
1716 /* Initialize results */
1717 memset(av_info, 0, sizeof(*av_info));
1719 /* if no versioning provider, just return */
1720 if (vsn_hooks == NULL)
1723 /* check parent resource if requested or if resource must be created */
1724 if (!resource->exists || parent_only) {
1725 dav_resource *parent;
1727 if ((err = (*resource->hooks->get_parent_resource)(resource,
1731 if (parent == NULL || !parent->exists) {
1732 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1733 apr_psprintf(r->pool,
1734 "Missing one or more intermediate "
1735 "collections. Cannot create resource %s.",
1736 ap_escape_html(r->pool, resource->uri)));
1740 av_info->parent_resource = parent;
1742 /* if parent versioned and not checked out, see if it can be */
1743 if (parent->versioned && !parent->working) {
1744 int checkout_parent;
1746 if ((err = dav_can_auto_checkout(r, parent,
1747 (*vsn_hooks->auto_versionable)(parent),
1748 &lockdb, &checkout_parent))
1753 if (!checkout_parent) {
1754 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1755 "<DAV:cannot-modify-checked-in-parent>");
1759 /* Try to checkout the parent collection.
1760 * Note that auto-versioning can only be applied to a version selector,
1761 * so no separate working resource will be created.
1763 if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
1764 0, 0, 0, NULL, NULL))
1767 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1768 apr_psprintf(r->pool,
1769 "Unable to auto-checkout parent collection. "
1770 "Cannot create resource %s.",
1771 ap_escape_html(r->pool, resource->uri)),
1776 /* remember that parent was checked out */
1777 av_info->parent_checkedout = 1;
1781 /* if only checking parent, we're done */
1785 /* if creating a new resource, see if it should be version-controlled */
1786 if (!resource->exists
1787 && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) {
1789 if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
1790 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1791 apr_psprintf(r->pool,
1792 "Unable to create versioned resource %s.",
1793 ap_escape_html(r->pool, resource->uri)),
1798 /* remember that resource was created */
1799 av_info->resource_versioned = 1;
1802 /* if resource is versioned, make sure it is checked out */
1803 if (resource->versioned && !resource->working) {
1804 int checkout_resource;
1806 if ((err = dav_can_auto_checkout(r, resource,
1807 (*vsn_hooks->auto_versionable)(resource),
1808 &lockdb, &checkout_resource)) != NULL) {
1812 if (!checkout_resource) {
1813 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1814 "<DAV:cannot-modify-version-controlled-content>");
1818 /* Auto-versioning can only be applied to version selectors, so
1819 * no separate working resource will be created. */
1820 if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/,
1821 0, 0, 0, NULL, NULL))
1824 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1825 apr_psprintf(r->pool,
1826 "Unable to checkout resource %s.",
1827 ap_escape_html(r->pool, resource->uri)),
1832 /* remember that resource was checked out */
1833 av_info->resource_checkedout = 1;
1838 /* make sure lock database is closed */
1840 (*lockdb->hooks->close_lockdb)(lockdb);
1842 /* if an error occurred, undo any auto-versioning operations already done */
1844 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
1851 /* see mod_dav.h for docco */
1852 dav_error *dav_auto_checkin(
1854 dav_resource *resource,
1857 dav_auto_version_info *av_info)
1859 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1860 dav_error *err = NULL;
1861 dav_auto_version auto_version;
1863 /* If no versioning provider, this is a no-op */
1864 if (vsn_hooks == NULL)
1867 /* If undoing auto-checkouts, then do uncheckouts */
1869 if (resource != NULL) {
1870 if (av_info->resource_checkedout) {
1871 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
1872 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1873 apr_psprintf(r->pool,
1874 "Unable to undo auto-checkout "
1876 ap_escape_html(r->pool, resource->uri)),
1881 if (av_info->resource_versioned) {
1882 dav_response *response;
1884 /* ### should we do anything with the response? */
1885 if ((err = (*resource->hooks->remove_resource)(resource,
1886 &response)) != NULL) {
1887 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1888 apr_psprintf(r->pool,
1889 "Unable to undo auto-version-control "
1891 ap_escape_html(r->pool, resource->uri)),
1897 if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
1898 if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) {
1899 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1900 apr_psprintf(r->pool,
1901 "Unable to undo auto-checkout "
1902 "of parent collection %s.",
1903 ap_escape_html(r->pool, av_info->parent_resource->uri)),
1911 /* If the resource was checked out, and auto-checkin is enabled,
1914 if (resource != NULL && resource->working
1915 && (unlock || av_info->resource_checkedout)) {
1917 auto_version = (*vsn_hooks->auto_versionable)(resource);
1919 if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
1920 (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
1922 if ((err = (*vsn_hooks->checkin)(resource,
1923 0 /*keep_checked_out*/, NULL))
1925 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1926 apr_psprintf(r->pool,
1927 "Unable to auto-checkin resource %s.",
1928 ap_escape_html(r->pool, resource->uri)),
1934 /* If parent resource was checked out, and auto-checkin is enabled,
1937 if (av_info->parent_resource != NULL && av_info->parent_resource->working
1938 && (unlock || av_info->parent_checkedout)) {
1940 auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
1942 if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
1943 (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
1945 if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
1946 0 /*keep_checked_out*/, NULL))
1948 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1949 apr_psprintf(r->pool,
1950 "Unable to auto-checkin parent collection %s.",
1951 ap_escape_html(r->pool, av_info->parent_resource->uri)),