1 /* Copyright 2000-2004 The Apache Software Foundation
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 ** DAV extension module for Apache 2.0.*
18 ** - various utilities, repository-independent
21 #include "apr_strings.h"
24 #define APR_WANT_STRFUNC
29 #include "http_request.h"
30 #include "http_config.h"
31 #include "http_vhost.h"
33 #include "http_protocol.h"
35 DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status,
36 int error_id, const char *desc)
38 int save_errno = errno;
39 dav_error *err = apr_pcalloc(p, sizeof(*err));
41 /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */
44 err->error_id = error_id;
46 err->save_errno = save_errno;
51 DAV_DECLARE(dav_error*) dav_new_error_tag(apr_pool_t *p, int status,
52 int error_id, const char *desc,
53 const char *namespace,
56 dav_error *err = dav_new_error(p, status, error_id, desc);
58 err->tagname = tagname;
59 err->namespace = namespace;
65 DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status,
66 int error_id, const char *desc,
69 dav_error *err = apr_pcalloc(p, sizeof(*err));
72 err->error_id = error_id;
79 DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
80 apr_size_t extra_needed)
82 /* grow the buffer if necessary */
83 if (pbuf->cur_len + extra_needed > pbuf->alloc_len) {
86 pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD;
87 newbuf = apr_palloc(p, pbuf->alloc_len);
88 memcpy(newbuf, pbuf->buf, pbuf->cur_len);
93 DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf,
96 /* NOTE: this does not retain prior contents */
98 /* NOTE: this function is used to init the first pointer, too, since
99 the PAD will be larger than alloc_len (0) for zeroed structures */
101 /* grow if we don't have enough for the requested size plus padding */
102 if (size + DAV_BUFFER_PAD > pbuf->alloc_len) {
103 /* set the new length; min of MINSIZE */
104 pbuf->alloc_len = size + DAV_BUFFER_PAD;
105 if (pbuf->alloc_len < DAV_BUFFER_MINSIZE)
106 pbuf->alloc_len = DAV_BUFFER_MINSIZE;
108 pbuf->buf = apr_palloc(p, pbuf->alloc_len);
110 pbuf->cur_len = size;
114 /* initialize a buffer and copy the specified (null-term'd) string into it */
115 DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf,
118 dav_set_bufsize(p, pbuf, strlen(str));
119 memcpy(pbuf->buf, str, pbuf->cur_len + 1);
122 /* append a string to the end of the buffer, adjust length */
123 DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf,
126 apr_size_t len = strlen(str);
128 dav_check_bufsize(p, pbuf, len + 1);
129 memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
130 pbuf->cur_len += len;
133 /* place a string on the end of the buffer, do NOT adjust length */
134 DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf,
137 apr_size_t len = strlen(str);
139 dav_check_bufsize(p, pbuf, len + 1);
140 memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
143 /* place some memory on the end of a buffer; do NOT adjust length */
144 DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf,
145 const void *mem, apr_size_t amt,
148 dav_check_bufsize(p, pbuf, amt + pad);
149 memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
155 ** Extension for ap_sub_req_lookup_uri() which can't handle absolute
158 ** If NULL is returned, then an error occurred with parsing the URI or
159 ** the URI does not match the current server.
161 dav_lookup_result dav_lookup_uri(const char *uri, request_rec * r,
162 int must_be_absolute)
164 dav_lookup_result result = { 0 };
171 /* first thing to do is parse the URI into various components */
172 if (apr_uri_parse(r->pool, uri, &comp) != APR_SUCCESS) {
173 result.err.status = HTTP_BAD_REQUEST;
174 result.err.desc = "Invalid syntax in Destination URI.";
178 /* the URI must be an absoluteURI (WEBDAV S9.3) */
179 if (comp.scheme == NULL && must_be_absolute) {
180 result.err.status = HTTP_BAD_REQUEST;
181 result.err.desc = "Destination URI must be an absolute URI.";
185 /* the URI must not have a query (args) or a fragment */
186 if (comp.query != NULL || comp.fragment != NULL) {
187 result.err.status = HTTP_BAD_REQUEST;
189 "Destination URI contains invalid components "
190 "(a query or a fragment).";
194 /* If the scheme or port was provided, then make sure that it matches
195 the scheme/port of this request. If the request must be absolute,
196 then require the (explicit/implicit) scheme/port be matching.
198 ### hmm. if a port wasn't provided (does the parse return port==0?),
199 ### but we're on a non-standard port, then we won't detect that the
200 ### URI's port implies the wrong one.
202 if (comp.scheme != NULL || comp.port != 0 || must_be_absolute)
204 /* ### not sure this works if the current request came in via https: */
205 scheme = r->parsed_uri.scheme;
207 scheme = ap_http_method(r);
209 /* insert a port if the URI did not contain one */
211 comp.port = apr_uri_port_of_scheme(comp.scheme);
213 /* now, verify that the URI uses the same scheme as the current.
214 request. the port must match our port.
216 port = r->connection->local_addr->port;
217 if (strcasecmp(comp.scheme, scheme) != 0
218 #ifdef APACHE_PORT_HANDLING_IS_BUSTED
222 result.err.status = HTTP_BAD_GATEWAY;
223 result.err.desc = apr_psprintf(r->pool,
224 "Destination URI refers to "
225 "different scheme or port "
226 "(%s://hostname:%d)" APR_EOL_STR
227 "(want: %s://hostname:%d)",
228 comp.scheme ? comp.scheme : scheme,
229 comp.port ? comp.port : port,
235 /* we have verified the scheme, port, and general structure */
238 ** Hrm. IE5 will pass unqualified hostnames for both the
239 ** Host: and Destination: headers. This breaks the
240 ** http_vhost.c::matches_aliases function.
242 ** For now, qualify unqualified comp.hostnames with
243 ** r->server->server_hostname.
245 ** ### this is a big hack. Apache should provide a better way.
246 ** ### maybe the admin should list the unqualified hosts in a
247 ** ### <ServerAlias> block?
249 if (comp.hostname != NULL
250 && strrchr(comp.hostname, '.') == NULL
251 && (domain = strchr(r->server->server_hostname, '.')) != NULL) {
252 comp.hostname = apr_pstrcat(r->pool, comp.hostname, domain, NULL);
255 /* now, if a hostname was provided, then verify that it represents the
256 same server as the current connection. note that we just use our
257 port, since we've verified the URI matches ours */
258 #ifdef APACHE_PORT_HANDLING_IS_BUSTED
259 if (comp.hostname != NULL &&
260 !ap_matches_request_vhost(r, comp.hostname, port)) {
261 result.err.status = HTTP_BAD_GATEWAY;
262 result.err.desc = "Destination URI refers to a different server.";
267 /* we have verified that the requested URI denotes the same server as
268 the current request. Therefore, we can use ap_sub_req_lookup_uri() */
270 /* reconstruct a URI as just the path */
271 new_file = apr_uri_unparse(r->pool, &comp, APR_URI_UNP_OMITSITEPART);
274 * Lookup the URI and return the sub-request. Note that we use the
275 * same HTTP method on the destination. This allows the destination
276 * to apply appropriate restrictions (e.g. readonly).
278 result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
283 /* ---------------------------------------------------------------
285 ** XML UTILITY FUNCTIONS
288 /* validate that the root element uses a given DAV: tagname (TRUE==valid) */
289 int dav_validate_root(const apr_xml_doc *doc, const char *tagname)
292 doc->root->ns == APR_XML_NS_DAV_ID &&
293 strcmp(doc->root->name, tagname) == 0;
296 /* find and return the (unique) child with a given DAV: tagname */
297 apr_xml_elem *dav_find_child(const apr_xml_elem *elem, const char *tagname)
299 apr_xml_elem *child = elem->first_child;
301 for (; child; child = child->next)
302 if (child->ns == APR_XML_NS_DAV_ID && !strcmp(child->name, tagname))
307 /* gather up all the CDATA into a single string */
308 DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool,
313 const apr_xml_elem *child;
317 const char *found_text = NULL; /* initialize to avoid gcc warning */
320 for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
321 found_text = scan->text;
323 len += strlen(found_text);
326 for (child = elem->first_child; child != NULL; child = child->next) {
327 for (scan = child->following_cdata.first;
330 found_text = scan->text;
332 len += strlen(found_text);
336 /* some fast-path cases:
337 * 1) zero-length cdata
338 * 2) a single piece of cdata with no whitespace to strip
342 if (found_count == 1) {
344 || (!apr_isspace(*found_text)
345 && !apr_isspace(found_text[len - 1])))
349 cdata = s = apr_palloc(pool, len + 1);
351 for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
352 tlen = strlen(scan->text);
353 memcpy(s, scan->text, tlen);
357 for (child = elem->first_child; child != NULL; child = child->next) {
358 for (scan = child->following_cdata.first;
361 tlen = strlen(scan->text);
362 memcpy(s, scan->text, tlen);
370 /* trim leading whitespace */
371 while (apr_isspace(*cdata)) /* assume: return false for '\0' */
374 /* trim trailing whitespace */
375 while (len-- > 0 && apr_isspace(cdata[len]))
377 cdata[len + 1] = '\0';
383 DAV_DECLARE(dav_xmlns_info *) dav_xmlns_create(apr_pool_t *pool)
385 dav_xmlns_info *xi = apr_pcalloc(pool, sizeof(*xi));
388 xi->uri_prefix = apr_hash_make(pool);
389 xi->prefix_uri = apr_hash_make(pool);
394 DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi,
395 const char *prefix, const char *uri)
397 /* this "should" not overwrite a prefix mapping */
398 apr_hash_set(xi->prefix_uri, prefix, APR_HASH_KEY_STRING, uri);
400 /* note: this may overwrite an existing URI->prefix mapping, but it
401 doesn't matter -- any prefix is usuable to specify the URI. */
402 apr_hash_set(xi->uri_prefix, uri, APR_HASH_KEY_STRING, prefix);
405 DAV_DECLARE(const char *) dav_xmlns_add_uri(dav_xmlns_info *xi,
410 if ((prefix = apr_hash_get(xi->uri_prefix, uri,
411 APR_HASH_KEY_STRING)) != NULL)
414 prefix = apr_psprintf(xi->pool, "g%d", xi->count++);
415 dav_xmlns_add(xi, prefix, uri);
419 DAV_DECLARE(const char *) dav_xmlns_get_uri(dav_xmlns_info *xi,
422 return apr_hash_get(xi->prefix_uri, prefix, APR_HASH_KEY_STRING);
425 DAV_DECLARE(const char *) dav_xmlns_get_prefix(dav_xmlns_info *xi,
428 return apr_hash_get(xi->uri_prefix, uri, APR_HASH_KEY_STRING);
431 DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
432 apr_text_header *phdr)
434 apr_hash_index_t *hi = apr_hash_first(xi->pool, xi->prefix_uri);
436 for (; hi != NULL; hi = apr_hash_next(hi)) {
441 apr_hash_this(hi, &prefix, NULL, &uri);
443 s = apr_psprintf(xi->pool, " xmlns:%s=\"%s\"",
444 (const char *)prefix, (const char *)uri);
445 apr_text_append(xi->pool, phdr, s);
449 /* ---------------------------------------------------------------
451 ** Timeout header processing
455 /* dav_get_timeout: If the Timeout: header exists, return a time_t
456 * when this lock is expected to expire. Otherwise, return
457 * a time_t of DAV_TIMEOUT_INFINITE.
459 * It's unclear if DAV clients are required to understand
460 * Seconds-xxx and Infinity time values. We assume that they do.
461 * In addition, for now, that's all we understand, too.
463 time_t dav_get_timeout(request_rec *r)
465 time_t now, expires = DAV_TIMEOUT_INFINITE;
467 const char *timeout_const = apr_table_get(r->headers_in, "Timeout");
468 const char *timeout = apr_pstrdup(r->pool, timeout_const), *val;
471 return DAV_TIMEOUT_INFINITE;
473 /* Use the first thing we understand, or infinity if
474 * we don't understand anything.
477 while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) {
478 if (!strncmp(val, "Infinite", 8)) {
479 return DAV_TIMEOUT_INFINITE;
482 if (!strncmp(val, "Second-", 7)) {
484 /* ### We need to handle overflow better:
485 * ### timeout will be <= 2^32 - 1
489 return now + expires;
493 return DAV_TIMEOUT_INFINITE;
496 /* ---------------------------------------------------------------
498 ** If Header processing
502 /* add_if_resource returns a new if_header, linking it to next_ih.
504 static dav_if_header *dav_add_if_resource(apr_pool_t *p, dav_if_header *next_ih,
505 const char *uri, apr_size_t uri_len)
509 if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL)
513 ih->uri_len = uri_len;
519 /* add_if_state adds a condition to an if_header.
521 static dav_error * dav_add_if_state(apr_pool_t *p, dav_if_header *ih,
522 const char *state_token,
523 dav_if_state_type t, int condition,
524 const dav_hooks_locks *locks_hooks)
526 dav_if_state_list *new_sl;
528 new_sl = apr_pcalloc(p, sizeof(*new_sl));
530 new_sl->condition = condition;
533 if (t == dav_if_opaquelock) {
536 if ((err = (*locks_hooks->parse_locktoken)(p, state_token,
537 &new_sl->locktoken)) != NULL) {
538 /* In cases where the state token is invalid, we'll just skip
539 * it rather than return 400.
541 if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) {
545 /* ### maybe add a higher-level description */
551 new_sl->etag = state_token;
553 new_sl->next = ih->state;
559 /* fetch_next_token returns the substring from str+1
560 * to the next occurence of char term, or \0, whichever
561 * occurs first. Leading whitespace is ignored.
563 static char *dav_fetch_next_token(char **str, char term)
570 while (*token && (*token == ' ' || *token == '\t'))
573 if ((sp = strchr(token, term)) == NULL)
581 /* dav_process_if_header:
583 * If NULL (no error) is returned, then **if_header points to the
584 * "If" productions structure (or NULL if "If" is not present).
586 * ### this part is bogus:
587 * If an error is encountered, the error is logged. Parent should
588 * return err->status.
590 static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
595 const char *state_token;
596 const char *uri = NULL; /* scope of current production; NULL=no-tag */
597 apr_size_t uri_len = 0;
598 dav_if_header *ih = NULL;
599 apr_uri_t parsed_uri;
600 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
601 enum {no_tagged, tagged, unknown} list_type = unknown;
606 if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL)
612 /* Tagged-list production - following states apply to this uri */
613 if (list_type == no_tagged
614 || ((uri = dav_fetch_next_token(&str, '>')) == NULL)) {
615 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
617 "Invalid If-header: unclosed \"<\" or "
618 "unexpected tagged-list production.");
621 /* 2518 specifies this must be an absolute URI; just take the
622 * relative part for later comparison against r->uri */
623 if (apr_uri_parse(r->pool, uri, &parsed_uri) != APR_SUCCESS) {
624 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
626 "Invalid URI in tagged If-header.");
628 /* note that parsed_uri.path is allocated; we can trash it */
630 /* clean up the URI a bit */
631 ap_getparents(parsed_uri.path);
632 uri_len = strlen(parsed_uri.path);
633 if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/')
634 parsed_uri.path[--uri_len] = '\0';
636 uri = parsed_uri.path;
641 /* List production */
643 /* If a uri has not been encountered, this is a No-Tagged-List */
644 if (list_type == unknown)
645 list_type = no_tagged;
647 if ((list = dav_fetch_next_token(&str, ')')) == NULL) {
648 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
649 DAV_ERR_IF_UNCLOSED_PAREN,
650 "Invalid If-header: unclosed \"(\".");
653 if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) {
654 /* ### dav_add_if_resource() should return an error for us! */
655 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
657 "Internal server error parsing \"If:\" "
661 condition = DAV_IF_COND_NORMAL;
664 /* List is the entire production (in a uri scope) */
668 if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) {
669 /* ### add a description to this error */
670 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
671 DAV_ERR_IF_PARSE, NULL);
674 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock,
675 condition, locks_hooks)) != NULL) {
676 /* ### maybe add a higher level description */
679 condition = DAV_IF_COND_NORMAL;
683 if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) {
684 /* ### add a description to this error */
685 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
686 DAV_ERR_IF_PARSE, NULL);
689 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag,
690 condition, locks_hooks)) != NULL) {
691 /* ### maybe add a higher level description */
694 condition = DAV_IF_COND_NORMAL;
698 if (list[1] == 'o' && list[2] == 't') {
699 if (condition != DAV_IF_COND_NORMAL) {
700 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
701 DAV_ERR_IF_MULTIPLE_NOT,
702 "Invalid \"If:\" header: "
703 "Multiple \"not\" entries "
704 "for the same state.");
706 condition = DAV_IF_COND_NOT;
716 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
718 apr_psprintf(r->pool,
720 "header: Unexpected "
721 "character encountered "
735 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
737 apr_psprintf(r->pool,
738 "Invalid \"If:\" header: "
739 "Unexpected character "
740 "encountered (0x%02x, '%c').",
751 static int dav_find_submitted_locktoken(const dav_if_header *if_header,
752 const dav_lock *lock_list,
753 const dav_hooks_locks *locks_hooks)
755 for (; if_header != NULL; if_header = if_header->next) {
756 const dav_if_state_list *state_list;
758 for (state_list = if_header->state;
760 state_list = state_list->next) {
762 if (state_list->type == dav_if_opaquelock) {
763 const dav_lock *lock;
765 /* given state_list->locktoken, match it */
768 ** The resource will have one or more lock tokens. We only
769 ** need to match one of them against any token in the
772 ** One token case: It is an exclusive or shared lock. Either
773 ** way, we must find it.
775 ** N token case: They are shared locks. By policy, we need
776 ** to match only one. The resource's other
777 ** tokens may belong to somebody else (so we
778 ** shouldn't see them in the If: header anyway)
780 for (lock = lock_list; lock != NULL; lock = lock->next) {
782 if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
793 /* dav_validate_resource_state:
794 * Returns NULL if path/uri meets if-header and lock requirements
796 static dav_error * dav_validate_resource_state(apr_pool_t *p,
797 const dav_resource *resource,
799 const dav_if_header *if_header,
807 const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL);
808 const dav_if_header *ifhdr_scan;
809 dav_if_state_list *state_list;
816 const char *reason = NULL;
818 /* DBG1("validate: <%s>", resource->uri); */
821 ** The resource will have one of three states:
823 ** 1) No locks. We have no special requirements that the user supply
824 ** specific locktokens. One of the state lists must match, and
827 ** 2) One exclusive lock. The locktoken must appear *anywhere* in the
828 ** If: header. Of course, asserting the token in a "Not" term will
829 ** quickly fail that state list :-). If the locktoken appears in
830 ** one of the state lists *and* one state list matches, then we're
833 ** 3) One or more shared locks. One of the locktokens must appear
834 ** *anywhere* in the If: header. If one of the locktokens appears,
835 ** and we match one state list, then we are done.
837 ** The <seen_locktoken> variable determines whether we have seen one
838 ** of this resource's locktokens in the If: header.
842 ** If this is a new lock request, <flags> will contain the requested
843 ** lock scope. Three rules apply:
845 ** 1) Do not require a (shared) locktoken to be seen (when we are
846 ** applying another shared lock)
847 ** 2) If the scope is exclusive and we see any locks, fail.
848 ** 3) If the scope is shared and we see an exclusive lock, fail.
851 if (lockdb == NULL) {
852 /* we're in State 1. no locks. */
857 ** ### hrm... we don't need to have these fully
858 ** ### resolved since we're only looking at the
861 ** ### use get_locks w/ calltype=PARTIAL
863 if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) {
864 return dav_push_error(p,
865 HTTP_INTERNAL_SERVER_ERROR, 0,
866 "The locks could not be queried for "
867 "verification against a possible \"If:\" "
872 /* lock_list now determines whether we're in State 1, 2, or 3. */
876 ** For a new, exclusive lock: if any locks exist, fail.
877 ** For a new, shared lock: if an exclusive lock exists, fail.
878 ** else, do not require a token to be seen.
880 if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
881 if (lock_list != NULL) {
882 return dav_new_error(p, HTTP_LOCKED, 0,
883 "Existing lock(s) on the requested resource "
884 "prevent an exclusive lock.");
888 ** There are no locks, so we can pretend that we've already met
889 ** any requirement to find the resource's locks in an If: header.
893 else if (flags & DAV_LOCKSCOPE_SHARED) {
895 ** Strictly speaking, we don't need this loop. Either the first
896 ** (and only) lock will be EXCLUSIVE, or none of them will be.
898 for (lock = lock_list; lock != NULL; lock = lock->next) {
899 if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) {
900 return dav_new_error(p, HTTP_LOCKED, 0,
901 "The requested resource is already "
902 "locked exclusively.");
907 ** The locks on the resource (if any) are all shared. Set the
908 ** <seen_locktoken> flag to indicate that we do not need to find
909 ** the locks in an If: header.
915 ** For methods other than LOCK:
917 ** If we have no locks, then <seen_locktoken> can be set to true --
918 ** pretending that we've already met the requirement of seeing one
919 ** of the resource's locks in the If: header.
921 ** Otherwise, it must be cleared and we'll look for one.
923 seen_locktoken = (lock_list == NULL);
927 ** If there is no If: header, then we can shortcut some logic:
929 ** 1) if we do not need to find a locktoken in the (non-existent) If:
930 ** header, then we are successful.
932 ** 2) if we must find a locktoken in the (non-existent) If: header, then
935 if (if_header == NULL) {
939 return dav_new_error(p, HTTP_LOCKED, 0,
940 "This resource is locked and an \"If:\" header "
941 "was not supplied to allow access to the "
944 /* the If: header is present */
947 ** If a dummy header is present (because of a Lock-Token: header), then
948 ** we are required to find that token in this resource's set of locks.
949 ** If we have no locks, then we immediately fail.
951 ** This is a 400 (Bad Request) since they should only submit a locktoken
952 ** that actually exists.
954 ** Don't issue this response if we're talking about the parent resource.
955 ** It is okay for that resource to NOT have this locktoken.
956 ** (in fact, it certainly will not: a dummy_header only occurs for the
957 ** UNLOCK method, the parent is checked only for locknull resources,
958 ** and the parent certainly does not have the (locknull's) locktoken)
960 if (lock_list == NULL && if_header->dummy_header) {
961 if (flags & DAV_VALIDATE_IS_PARENT)
963 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
964 "The locktoken specified in the \"Lock-Token:\" "
965 "header is invalid because this resource has no "
966 "outstanding locks.");
970 ** Prepare the input URI. We want the URI to never have a trailing slash.
972 ** When URIs are placed into the dav_if_header structure, they are
973 ** guaranteed to never have a trailing slash. If the URIs are equivalent,
974 ** then it doesn't matter if they both lack a trailing slash -- they're
977 ** Note: we could also ensure that a trailing slash is present on both
978 ** URIs, but the majority of URIs provided to us via a resource walk
979 ** will not contain that trailing slash.
982 uri_len = strlen(uri);
983 if (uri[uri_len - 1] == '/') {
984 dav_set_bufsize(p, pbuf, uri_len);
985 memcpy(pbuf->buf, uri, uri_len);
986 pbuf->buf[--uri_len] = '\0';
990 /* get the resource's etag; we may need it during the checks */
991 etag = (*resource->hooks->getetag)(resource);
993 /* how many state_lists apply to this URI? */
996 /* If there are if-headers, fail if this resource
997 * does not match at least one state_list.
999 for (ifhdr_scan = if_header;
1001 ifhdr_scan = ifhdr_scan->next) {
1003 /* DBG2("uri=<%s> if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
1005 if (ifhdr_scan->uri != NULL
1006 && (uri_len != ifhdr_scan->uri_len
1007 || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
1009 ** A tagged-list's URI doesn't match this resource's URI.
1010 ** Skip to the next state_list to see if it will match.
1015 /* this state_list applies to this resource */
1018 ** ### only one state_list should ever apply! a no-tag, or a tagged
1019 ** ### where S9.4.2 states only one can match.
1021 ** ### revamp this code to loop thru ifhdr_scan until we find the
1022 ** ### matching state_list. process it. stop.
1026 /* To succeed, resource must match *all* of the states
1027 * specified in the state_list.
1029 for (state_list = ifhdr_scan->state;
1031 state_list = state_list->next) {
1033 switch(state_list->type) {
1036 const char *given_etag, *current_etag;
1039 /* Do a weak entity comparison function as defined in
1042 if (state_list->etag[0] == 'W' &&
1043 state_list->etag[1] == '/') {
1044 given_etag = state_list->etag + 2;
1047 given_etag = state_list->etag;
1049 if (etag[0] == 'W' &&
1051 current_etag = etag + 2;
1054 current_etag = etag;
1057 mismatch = strcmp(given_etag, current_etag);
1059 if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
1061 ** The specified entity-tag does not match the
1062 ** entity-tag on the resource. This state_list is
1063 ** not going to match. Bust outta here.
1066 "an entity-tag was specified, but the resource's "
1067 "actual ETag does not match.";
1068 goto state_list_failed;
1070 else if (state_list->condition == DAV_IF_COND_NOT
1073 ** The specified entity-tag DOES match the
1074 ** entity-tag on the resource. This state_list is
1075 ** not going to match. Bust outta here.
1078 "an entity-tag was specified using the \"Not\" form, "
1079 "but the resource's actual ETag matches the provided "
1081 goto state_list_failed;
1086 case dav_if_opaquelock:
1087 if (lockdb == NULL) {
1088 if (state_list->condition == DAV_IF_COND_NOT) {
1089 /* the locktoken is definitely not there! (success) */
1093 /* condition == DAV_IF_COND_NORMAL */
1096 ** If no lockdb is provided, then validation fails for
1097 ** this state_list (NORMAL means we were supposed to
1098 ** find the token, which we obviously cannot do without
1099 ** a lock database).
1101 ** Go and try the next state list.
1104 "a State-token was supplied, but a lock database "
1105 "is not available for to provide the required lock.";
1106 goto state_list_failed;
1109 /* Resource validation 'fails' if:
1110 * ANY of the lock->locktokens match
1111 * a NOT state_list->locktoken,
1113 * NONE of the lock->locktokens match
1114 * a NORMAL state_list->locktoken.
1117 for (lock = lock_list; lock != NULL; lock = lock->next) {
1120 DBG2("compare: rsrc=%s ifhdr=%s",
1121 (*locks_hooks->format_locktoken)(p, lock->locktoken),
1122 (*locks_hooks->format_locktoken)(p, state_list->locktoken));
1125 /* nothing to do if the locktokens do not match. */
1126 if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
1131 ** We have now matched up one of the resource's locktokens
1132 ** to a locktoken in a State-token in the If: header.
1133 ** Note this fact, so that we can pass the overall
1134 ** requirement of seeing at least one of the resource's
1139 if (state_list->condition == DAV_IF_COND_NOT) {
1141 ** This state requires that the specified locktoken
1142 ** is NOT present on the resource. But we just found
1143 ** it. There is no way this state-list can now
1144 ** succeed, so go try another one.
1147 "a State-token was supplied, which used a "
1148 "\"Not\" condition. The State-token was found "
1149 "in the locks on this resource";
1150 goto state_list_failed;
1153 /* condition == DAV_IF_COND_NORMAL */
1155 /* Validate auth_user: If an authenticated user created
1156 ** the lock, only the same user may submit that locktoken
1157 ** to manipulate a resource.
1159 if (lock->auth_user &&
1161 strcmp(lock->auth_user, r->user))) {
1164 errmsg = apr_pstrcat(p, "User \"",
1166 "\" submitted a locktoken created "
1168 lock->auth_user, "\".", NULL);
1169 return dav_new_error(p, HTTP_FORBIDDEN, 0, errmsg);
1173 ** We just matched a specified State-Token to one of the
1174 ** resource's locktokens.
1176 ** Break out of the lock scan -- we only needed to find
1177 ** one match (actually, there shouldn't be any other
1178 ** matches in the lock list).
1184 if (num_matched == 0
1185 && state_list->condition == DAV_IF_COND_NORMAL) {
1187 ** We had a NORMAL state, meaning that we should have
1188 ** found the State-Token within the locks on this
1189 ** resource. We didn't, so this state_list must fail.
1192 "a State-token was supplied, but it was not found "
1193 "in the locks on this resource.";
1194 goto state_list_failed;
1200 } /* foreach ( state_list ) */
1203 ** We've checked every state in this state_list and none of them
1204 ** have failed. Since all of them succeeded, then we have a matching
1205 ** state list and we may be done.
1207 ** The next requirement is that we have seen one of the resource's
1208 ** locktokens (if any). If we have, then we can just exit. If we
1209 ** haven't, then we need to keep looking.
1211 if (seen_locktoken) {
1217 ** Haven't seen one. Let's break out of the search and just look
1218 ** for a matching locktoken.
1223 ** This label is used when we detect that a state_list is not
1224 ** going to match this resource. We bust out and try the next
1230 } /* foreach ( ifhdr_scan ) */
1233 ** The above loop exits for one of two reasons:
1234 ** 1) a state_list matched and seen_locktoken is false.
1235 ** 2) all if_header structures were scanned, without (1) occurring
1238 if (ifhdr_scan == NULL) {
1240 ** We finished the loop without finding any matching state lists.
1244 ** If none of the state_lists apply to this resource, then we
1245 ** may have succeeded. Note that this scenario implies a
1246 ** tagged-list with no matching state_lists. If the If: header
1247 ** was a no-tag-list, then it would have applied to this resource.
1249 ** S9.4.2 states that when no state_lists apply, then the header
1250 ** should be ignored.
1252 ** If we saw one of the resource's locktokens, then we're done.
1253 ** If we did not see a locktoken, then we fail.
1255 if (num_that_apply == 0) {
1260 ** We may have aborted the scan before seeing the locktoken.
1261 ** Rescan the If: header to see if we can find the locktoken
1264 ** Note that seen_locktoken == 0 implies lock_list != NULL
1265 ** which implies locks_hooks != NULL.
1267 if (dav_find_submitted_locktoken(if_header, lock_list,
1270 ** We found a match! We're set... none of the If: header
1271 ** assertions apply (implicit success), and the If: header
1272 ** specified the locktoken somewhere. We're done.
1277 return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */,
1278 "This resource is locked and the \"If:\" "
1279 "header did not specify one of the "
1280 "locktokens for this resource's lock(s).");
1282 /* else: one or more state_lists were applicable, but failed. */
1285 ** If the dummy_header did not match, then they specified an
1286 ** incorrect token in the Lock-Token header. Forget whether the
1287 ** If: statement matched or not... we'll tell them about the
1288 ** bad Lock-Token first. That is considered a 400 (Bad Request).
1290 if (if_header->dummy_header) {
1291 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1292 "The locktoken specified in the "
1293 "\"Lock-Token:\" header did not specify one "
1294 "of this resource's locktoken(s).");
1297 if (reason == NULL) {
1298 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1299 "The preconditions specified by the \"If:\" "
1300 "header did not match this resource.");
1303 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1305 "The precondition(s) specified by "
1306 "the \"If:\" header did not match "
1307 "this resource. At least one "
1308 "failure is because: %s", reason));
1311 /* assert seen_locktoken == 0 */
1314 ** ifhdr_scan != NULL implies we found a matching state_list.
1316 ** Since we're still here, it also means that we have not yet found
1317 ** one the resource's locktokens in the If: header.
1319 ** Scan all the if_headers and states looking for one of this
1320 ** resource's locktokens. Note that we need to go back and scan them
1321 ** all -- we may have aborted a scan with a failure before we saw a
1324 ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
1325 ** locks_hooks != NULL.
1327 if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
1329 ** We found a match! We're set... we have a matching state list,
1330 ** and the If: header specified the locktoken somewhere. We're done.
1336 ** We had a matching state list, but the user agent did not specify one
1337 ** of this resource's locktokens. Tell them so.
1339 ** Note that we need to special-case the message on whether a "dummy"
1340 ** header exists. If it exists, yet we didn't see a needed locktoken,
1341 ** then that implies the dummy header (Lock-Token header) did NOT
1342 ** specify one of this resource's locktokens. (this implies something
1343 ** in the real If: header matched)
1345 ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
1347 if (if_header->dummy_header) {
1348 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1349 "The locktoken specified in the "
1350 "\"Lock-Token:\" header did not specify one "
1351 "of this resource's locktoken(s).");
1354 return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */,
1355 "This resource is locked and the \"If:\" header "
1356 "did not specify one of the "
1357 "locktokens for this resource's lock(s).");
1360 /* dav_validate_walker: Walker callback function to validate resource state */
1361 static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
1363 dav_walker_ctx *ctx = wres->walk_ctx;
1366 if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
1368 ctx->if_header, ctx->flags,
1369 &ctx->work_buf, ctx->r)) == NULL) {
1370 /* There was no error, so just bug out. */
1375 ** If we have a serious server error, or if the request itself failed,
1376 ** then just return error (not a multistatus).
1378 if (ap_is_HTTP_SERVER_ERROR(err->status)
1379 || (*wres->resource->hooks->is_same_resource)(wres->resource,
1381 /* ### maybe push a higher-level description? */
1385 /* associate the error with the current URI */
1386 dav_add_response(wres, err->status, NULL);
1392 ** dav_validate_request: Validate if-headers (and check for locks) on:
1393 ** (1) r->filename @ depth;
1394 ** (2) Parent of r->filename if check_parent == 1
1396 ** The check of parent should be done when it is necessary to verify that
1397 ** the parent collection will accept a new member (ie current resource
1400 ** Return OK on successful validation.
1401 ** On error, return appropriate HTTP_* code, and log error. If a multi-stat
1402 ** error is necessary, response will point to it, else NULL.
1404 dav_error * dav_validate_request(request_rec *r, dav_resource *resource,
1405 int depth, dav_locktoken *locktoken,
1406 dav_response **response, int flags,
1411 dav_if_header *if_header;
1412 int lock_db_opened_locally = 0;
1413 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1414 const dav_hooks_repository *repos_hooks = resource->hooks;
1415 dav_buffer work_buf = { 0 };
1416 dav_response *new_response;
1419 if (depth && response == NULL) {
1421 ** ### bleck. we can't return errors for other URIs unless we have
1422 ** ### a "response" ptr.
1424 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1425 "DESIGN ERROR: dav_validate_request called "
1426 "with depth>0, but no response ptr.");
1430 if (response != NULL)
1433 /* Do the standard checks for conditional requests using
1434 * If-..-Since, If-Match etc */
1435 if ((result = ap_meets_conditions(r)) != OK) {
1436 /* ### fix this up... how? */
1437 return dav_new_error(r->pool, result, 0, NULL);
1440 /* always parse (and later process) the If: header */
1441 if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1442 /* ### maybe add higher-level description */
1446 /* If a locktoken was specified, create a dummy if_header with which
1447 * to validate resources. In the interim, figure out why DAV uses
1448 * locktokens in an if-header without a Lock-Token header to refresh
1449 * locks, but a Lock-Token header without an if-header to remove them.
1451 if (locktoken != NULL) {
1452 dav_if_header *ifhdr_new;
1454 ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new));
1455 ifhdr_new->uri = resource->uri;
1456 ifhdr_new->uri_len = strlen(resource->uri);
1457 ifhdr_new->dummy_header = 1;
1459 ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state));
1460 ifhdr_new->state->type = dav_if_opaquelock;
1461 ifhdr_new->state->condition = DAV_IF_COND_NORMAL;
1462 ifhdr_new->state->locktoken = locktoken;
1464 ifhdr_new->next = if_header;
1465 if_header = ifhdr_new;
1469 ** If necessary, open the lock database (read-only, lazily);
1470 ** the validation process may need to retrieve or update lock info.
1471 ** Otherwise, assume provided lockdb is valid and opened rw.
1473 if (lockdb == NULL) {
1474 if (locks_hooks != NULL) {
1475 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1476 /* ### maybe insert higher-level comment */
1479 lock_db_opened_locally = 1;
1483 /* (1) Validate the specified resource, at the specified depth */
1484 if (resource->exists && depth > 0) {
1485 dav_walker_ctx ctx = { { 0 } };
1486 dav_response *multi_status;
1488 ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
1489 ctx.w.func = dav_validate_walker;
1490 ctx.w.walk_ctx = &ctx;
1491 ctx.w.pool = r->pool;
1492 ctx.w.root = resource;
1494 ctx.if_header = if_header;
1498 if (lockdb != NULL) {
1499 ctx.w.lockdb = lockdb;
1500 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1503 err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
1505 *response = multi_status;;
1507 /* else: implies a 5xx status code occurred. */
1510 err = dav_validate_resource_state(r->pool, resource, lockdb,
1511 if_header, flags, &work_buf, r);
1514 /* (2) Validate the parent resource if requested */
1515 if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
1516 dav_resource *parent_resource;
1518 err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
1520 if (err == NULL && parent_resource == NULL) {
1521 err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
1522 "Cannot access parent of repository root.");
1524 else if (err == NULL) {
1525 err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
1527 flags | DAV_VALIDATE_IS_PARENT,
1531 ** This error occurred on the parent resource. This implies that
1532 ** we have to create a multistatus response (to report the error
1533 ** against a URI other than the Request-URI). "Convert" this error
1534 ** into a multistatus response.
1537 new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1539 new_response->href = parent_resource->uri;
1540 new_response->status = err->status;
1541 new_response->desc =
1542 "A validation error has occurred on the parent resource, "
1543 "preventing the operation on the resource specified by "
1545 if (err->desc != NULL) {
1546 new_response->desc = apr_pstrcat(r->pool,
1552 /* assert: DAV_VALIDATE_PARENT implies response != NULL */
1553 new_response->next = *response;
1554 *response = new_response;
1561 if (lock_db_opened_locally)
1562 (*locks_hooks->close_lockdb)(lockdb);
1565 ** If we don't have a (serious) error, and we have multistatus responses,
1566 ** then we need to construct an "error". This error will be the overall
1567 ** status returned, and the multistatus responses will go into its body.
1569 ** For certain methods, the overall error will be a 424. The default is
1570 ** to construct a standard 207 response.
1572 if (err == NULL && response != NULL && *response != NULL) {
1573 apr_text *propstat = NULL;
1575 if ((flags & DAV_VALIDATE_USE_424) != 0) {
1576 /* manufacture a 424 error to hold the multistatus response(s) */
1577 return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0,
1578 "An error occurred on another resource, "
1579 "preventing the requested operation on "
1584 ** Whatever caused the error, the Request-URI should have a 424
1585 ** associated with it since we cannot complete the method.
1587 ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
1588 ** For other methods, return a simple 424.
1590 if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
1591 propstat = apr_pcalloc(r->pool, sizeof(*propstat));
1593 "<D:propstat>" DEBUG_CR
1594 "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
1595 "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
1596 "</D:propstat>" DEBUG_CR;
1599 /* create the 424 response */
1600 new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1601 new_response->href = resource->uri;
1602 new_response->status = HTTP_FAILED_DEPENDENCY;
1603 new_response->propresult.propstats = propstat;
1604 new_response->desc =
1605 "An error occurred on another resource, preventing the "
1606 "requested operation on this resource.";
1608 new_response->next = *response;
1609 *response = new_response;
1611 /* manufacture a 207 error for the multistatus response(s) */
1612 return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
1613 "Error(s) occurred on resources during the "
1614 "validation process.");
1620 /* dav_get_locktoken_list:
1622 * Sets ltl to a locktoken_list of all positive locktokens in header,
1623 * else NULL if no If-header, or no positive locktokens.
1625 dav_error * dav_get_locktoken_list(request_rec *r, dav_locktoken_list **ltl)
1628 dav_if_header *if_header;
1629 dav_if_state_list *if_state;
1630 dav_locktoken_list *lock_token = NULL;
1634 if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1635 /* ### add a higher-level description? */
1639 while (if_header != NULL) {
1640 if_state = if_header->state; /* Begining of the if_state linked list */
1641 while (if_state != NULL) {
1642 if (if_state->condition == DAV_IF_COND_NORMAL
1643 && if_state->type == dav_if_opaquelock) {
1644 lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list));
1645 lock_token->locktoken = if_state->locktoken;
1646 lock_token->next = *ltl;
1649 if_state = if_state->next;
1651 if_header = if_header->next;
1654 /* No nodes added */
1655 return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT,
1656 "No locktokens were specified in the \"If:\" "
1657 "header, so the refresh could not be performed.");
1663 #if 0 /* not needed right now... */
1665 static const char *strip_white(const char *s, apr_pool_t *pool)
1669 /* trim leading whitespace */
1670 while (apr_isspace(*s)) /* assume: return false for '\0' */
1673 /* trim trailing whitespace */
1674 idx = strlen(s) - 1;
1675 if (apr_isspace(s[idx])) {
1676 char *s2 = apr_pstrdup(pool, s);
1678 while (apr_isspace(s2[idx]) && idx > 0)
1688 #define DAV_LABEL_HDR "Label"
1690 /* dav_add_vary_header
1692 * If there were any headers in the request which require a Vary header
1693 * in the response, add it.
1695 void dav_add_vary_header(request_rec *in_req,
1696 request_rec *out_req,
1697 const dav_resource *resource)
1699 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
1701 /* ### this is probably all wrong... I think there is a function in
1702 ### the Apache API to add things to the Vary header. need to check */
1704 /* Only versioning headers require a Vary response header,
1705 * so only do this check if there is a versioning provider */
1706 if (vsn_hooks != NULL) {
1707 const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR);
1708 const char *vary = apr_table_get(out_req->headers_out, "Vary");
1710 /* If Target-Selector specified, add it to the Vary header */
1711 if (target != NULL) {
1713 vary = DAV_LABEL_HDR;
1715 vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
1718 apr_table_setn(out_req->headers_out, "Vary", vary);
1723 /* dav_can_auto_checkout
1725 * Determine whether auto-checkout is enabled for a resource.
1726 * r - the request_rec
1727 * resource - the resource
1728 * auto_version - the value of the auto_versionable hook for the resource
1729 * lockdb - pointer to lock database (opened if necessary)
1730 * auto_checkout - set to 1 if auto-checkout enabled
1732 static dav_error * dav_can_auto_checkout(
1734 dav_resource *resource,
1735 dav_auto_version auto_version,
1736 dav_lockdb **lockdb,
1740 dav_lock *lock_list;
1744 if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
1747 else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
1748 if (*lockdb == NULL) {
1749 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1751 if (locks_hooks == NULL) {
1752 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1753 "Auto-checkout is only enabled for locked resources, "
1754 "but there is no lock provider.");
1757 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) {
1758 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1759 "Cannot open lock database to determine "
1760 "auto-versioning behavior.",
1765 if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) {
1766 return dav_push_error(r->pool,
1767 HTTP_INTERNAL_SERVER_ERROR, 0,
1768 "The locks could not be queried for "
1769 "determining auto-versioning behavior.",
1773 if (lock_list != NULL)
1780 /* see mod_dav.h for docco */
1781 dav_error *dav_auto_checkout(
1783 dav_resource *resource,
1785 dav_auto_version_info *av_info)
1787 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1788 dav_lockdb *lockdb = NULL;
1789 dav_error *err = NULL;
1791 /* Initialize results */
1792 memset(av_info, 0, sizeof(*av_info));
1794 /* if no versioning provider, just return */
1795 if (vsn_hooks == NULL)
1798 /* check parent resource if requested or if resource must be created */
1799 if (!resource->exists || parent_only) {
1800 dav_resource *parent;
1802 if ((err = (*resource->hooks->get_parent_resource)(resource,
1806 if (parent == NULL || !parent->exists) {
1807 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1808 apr_psprintf(r->pool,
1809 "Missing one or more intermediate "
1810 "collections. Cannot create resource %s.",
1811 ap_escape_html(r->pool, resource->uri)));
1815 av_info->parent_resource = parent;
1817 /* if parent versioned and not checked out, see if it can be */
1818 if (parent->versioned && !parent->working) {
1819 int checkout_parent;
1821 if ((err = dav_can_auto_checkout(r, parent,
1822 (*vsn_hooks->auto_versionable)(parent),
1823 &lockdb, &checkout_parent))
1828 if (!checkout_parent) {
1829 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1830 "<DAV:cannot-modify-checked-in-parent>");
1834 /* Try to checkout the parent collection.
1835 * Note that auto-versioning can only be applied to a version selector,
1836 * so no separate working resource will be created.
1838 if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
1839 0, 0, 0, NULL, NULL))
1842 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1843 apr_psprintf(r->pool,
1844 "Unable to auto-checkout parent collection. "
1845 "Cannot create resource %s.",
1846 ap_escape_html(r->pool, resource->uri)),
1851 /* remember that parent was checked out */
1852 av_info->parent_checkedout = 1;
1856 /* if only checking parent, we're done */
1860 /* if creating a new resource, see if it should be version-controlled */
1861 if (!resource->exists
1862 && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) {
1864 if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
1865 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1866 apr_psprintf(r->pool,
1867 "Unable to create versioned resource %s.",
1868 ap_escape_html(r->pool, resource->uri)),
1873 /* remember that resource was created */
1874 av_info->resource_versioned = 1;
1877 /* if resource is versioned, make sure it is checked out */
1878 if (resource->versioned && !resource->working) {
1879 int checkout_resource;
1881 if ((err = dav_can_auto_checkout(r, resource,
1882 (*vsn_hooks->auto_versionable)(resource),
1883 &lockdb, &checkout_resource)) != NULL) {
1887 if (!checkout_resource) {
1888 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1889 "<DAV:cannot-modify-version-controlled-content>");
1893 /* Auto-versioning can only be applied to version selectors, so
1894 * no separate working resource will be created. */
1895 if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/,
1896 0, 0, 0, NULL, NULL))
1899 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1900 apr_psprintf(r->pool,
1901 "Unable to checkout resource %s.",
1902 ap_escape_html(r->pool, resource->uri)),
1907 /* remember that resource was checked out */
1908 av_info->resource_checkedout = 1;
1913 /* make sure lock database is closed */
1915 (*lockdb->hooks->close_lockdb)(lockdb);
1917 /* if an error occurred, undo any auto-versioning operations already done */
1919 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
1926 /* see mod_dav.h for docco */
1927 dav_error *dav_auto_checkin(
1929 dav_resource *resource,
1932 dav_auto_version_info *av_info)
1934 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1935 dav_error *err = NULL;
1936 dav_auto_version auto_version;
1938 /* If no versioning provider, this is a no-op */
1939 if (vsn_hooks == NULL)
1942 /* If undoing auto-checkouts, then do uncheckouts */
1944 if (resource != NULL) {
1945 if (av_info->resource_checkedout) {
1946 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
1947 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1948 apr_psprintf(r->pool,
1949 "Unable to undo auto-checkout "
1951 ap_escape_html(r->pool, resource->uri)),
1956 if (av_info->resource_versioned) {
1957 dav_response *response;
1959 /* ### should we do anything with the response? */
1960 if ((err = (*resource->hooks->remove_resource)(resource,
1961 &response)) != NULL) {
1962 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1963 apr_psprintf(r->pool,
1964 "Unable to undo auto-version-control "
1966 ap_escape_html(r->pool, resource->uri)),
1972 if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
1973 if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) {
1974 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1975 apr_psprintf(r->pool,
1976 "Unable to undo auto-checkout "
1977 "of parent collection %s.",
1978 ap_escape_html(r->pool, av_info->parent_resource->uri)),
1986 /* If the resource was checked out, and auto-checkin is enabled,
1989 if (resource != NULL && resource->working
1990 && (unlock || av_info->resource_checkedout)) {
1992 auto_version = (*vsn_hooks->auto_versionable)(resource);
1994 if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
1995 (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
1997 if ((err = (*vsn_hooks->checkin)(resource,
1998 0 /*keep_checked_out*/, NULL))
2000 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2001 apr_psprintf(r->pool,
2002 "Unable to auto-checkin resource %s.",
2003 ap_escape_html(r->pool, resource->uri)),
2009 /* If parent resource was checked out, and auto-checkin is enabled,
2013 && av_info->parent_checkedout
2014 && av_info->parent_resource != NULL
2015 && av_info->parent_resource->working) {
2017 auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
2019 if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
2020 if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
2021 0 /*keep_checked_out*/, NULL))
2023 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2024 apr_psprintf(r->pool,
2025 "Unable to auto-checkin parent collection %s.",
2026 ap_escape_html(r->pool, av_info->parent_resource->uri)),