1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 ** DAV extension module for Apache 2.0.*
19 ** - various utilities, repository-independent
22 #include "apr_strings.h"
25 #define APR_WANT_STRFUNC
30 #include "http_request.h"
31 #include "http_config.h"
32 #include "http_vhost.h"
34 #include "http_protocol.h"
36 DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status,
37 int error_id, const char *desc)
39 int save_errno = errno;
40 dav_error *err = apr_pcalloc(p, sizeof(*err));
42 /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */
45 err->error_id = error_id;
47 err->save_errno = save_errno;
52 DAV_DECLARE(dav_error*) dav_new_error_tag(apr_pool_t *p, int status,
53 int error_id, const char *desc,
54 const char *namespace,
57 dav_error *err = dav_new_error(p, status, error_id, desc);
59 err->tagname = tagname;
60 err->namespace = namespace;
66 DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status,
67 int error_id, const char *desc,
70 dav_error *err = apr_pcalloc(p, sizeof(*err));
73 err->error_id = error_id;
80 DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
81 apr_size_t extra_needed)
83 /* grow the buffer if necessary */
84 if (pbuf->cur_len + extra_needed > pbuf->alloc_len) {
87 pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD;
88 newbuf = apr_palloc(p, pbuf->alloc_len);
89 memcpy(newbuf, pbuf->buf, pbuf->cur_len);
94 DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf,
97 /* NOTE: this does not retain prior contents */
99 /* NOTE: this function is used to init the first pointer, too, since
100 the PAD will be larger than alloc_len (0) for zeroed structures */
102 /* grow if we don't have enough for the requested size plus padding */
103 if (size + DAV_BUFFER_PAD > pbuf->alloc_len) {
104 /* set the new length; min of MINSIZE */
105 pbuf->alloc_len = size + DAV_BUFFER_PAD;
106 if (pbuf->alloc_len < DAV_BUFFER_MINSIZE)
107 pbuf->alloc_len = DAV_BUFFER_MINSIZE;
109 pbuf->buf = apr_palloc(p, pbuf->alloc_len);
111 pbuf->cur_len = size;
115 /* initialize a buffer and copy the specified (null-term'd) string into it */
116 DAV_DECLARE(void) dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf,
119 dav_set_bufsize(p, pbuf, strlen(str));
120 memcpy(pbuf->buf, str, pbuf->cur_len + 1);
123 /* append a string to the end of the buffer, adjust length */
124 DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf,
127 apr_size_t len = strlen(str);
129 dav_check_bufsize(p, pbuf, len + 1);
130 memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
131 pbuf->cur_len += len;
134 /* place a string on the end of the buffer, do NOT adjust length */
135 DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf,
138 apr_size_t len = strlen(str);
140 dav_check_bufsize(p, pbuf, len + 1);
141 memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
144 /* place some memory on the end of a buffer; do NOT adjust length */
145 DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf,
146 const void *mem, apr_size_t amt,
149 dav_check_bufsize(p, pbuf, amt + pad);
150 memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
156 ** Extension for ap_sub_req_lookup_uri() which can't handle absolute
159 ** If NULL is returned, then an error occurred with parsing the URI or
160 ** the URI does not match the current server.
162 DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri,
164 int must_be_absolute)
166 dav_lookup_result result = { 0 };
173 /* first thing to do is parse the URI into various components */
174 if (apr_uri_parse(r->pool, uri, &comp) != APR_SUCCESS) {
175 result.err.status = HTTP_BAD_REQUEST;
176 result.err.desc = "Invalid syntax in Destination URI.";
180 /* the URI must be an absoluteURI (WEBDAV S9.3) */
181 if (comp.scheme == NULL && must_be_absolute) {
182 result.err.status = HTTP_BAD_REQUEST;
183 result.err.desc = "Destination URI must be an absolute URI.";
187 /* the URI must not have a query (args) or a fragment */
188 if (comp.query != NULL || comp.fragment != NULL) {
189 result.err.status = HTTP_BAD_REQUEST;
191 "Destination URI contains invalid components "
192 "(a query or a fragment).";
196 /* If the scheme or port was provided, then make sure that it matches
197 the scheme/port of this request. If the request must be absolute,
198 then require the (explicit/implicit) scheme/port be matching.
200 ### hmm. if a port wasn't provided (does the parse return port==0?),
201 ### but we're on a non-standard port, then we won't detect that the
202 ### URI's port implies the wrong one.
204 if (comp.scheme != NULL || comp.port != 0 || must_be_absolute)
206 /* ### not sure this works if the current request came in via https: */
207 scheme = r->parsed_uri.scheme;
209 scheme = ap_http_method(r);
211 /* insert a port if the URI did not contain one */
213 comp.port = apr_uri_port_of_scheme(comp.scheme);
215 /* now, verify that the URI uses the same scheme as the current.
216 request. the port must match our port.
218 apr_sockaddr_port_get(&port, r->connection->local_addr);
219 if (strcasecmp(comp.scheme, scheme) != 0
220 #ifdef APACHE_PORT_HANDLING_IS_BUSTED
224 result.err.status = HTTP_BAD_GATEWAY;
225 result.err.desc = apr_psprintf(r->pool,
226 "Destination URI refers to "
227 "different scheme or port "
228 "(%s://hostname:%d)" APR_EOL_STR
229 "(want: %s://hostname:%d)",
230 comp.scheme ? comp.scheme : scheme,
231 comp.port ? comp.port : port,
237 /* we have verified the scheme, port, and general structure */
240 ** Hrm. IE5 will pass unqualified hostnames for both the
241 ** Host: and Destination: headers. This breaks the
242 ** http_vhost.c::matches_aliases function.
244 ** For now, qualify unqualified comp.hostnames with
245 ** r->server->server_hostname.
247 ** ### this is a big hack. Apache should provide a better way.
248 ** ### maybe the admin should list the unqualified hosts in a
249 ** ### <ServerAlias> block?
251 if (comp.hostname != NULL
252 && strrchr(comp.hostname, '.') == NULL
253 && (domain = strchr(r->server->server_hostname, '.')) != NULL) {
254 comp.hostname = apr_pstrcat(r->pool, comp.hostname, domain, NULL);
257 /* now, if a hostname was provided, then verify that it represents the
258 same server as the current connection. note that we just use our
259 port, since we've verified the URI matches ours */
260 #ifdef APACHE_PORT_HANDLING_IS_BUSTED
261 if (comp.hostname != NULL &&
262 !ap_matches_request_vhost(r, comp.hostname, port)) {
263 result.err.status = HTTP_BAD_GATEWAY;
264 result.err.desc = "Destination URI refers to a different server.";
269 /* we have verified that the requested URI denotes the same server as
270 the current request. Therefore, we can use ap_sub_req_lookup_uri() */
272 /* reconstruct a URI as just the path */
273 new_file = apr_uri_unparse(r->pool, &comp, APR_URI_UNP_OMITSITEPART);
276 * Lookup the URI and return the sub-request. Note that we use the
277 * same HTTP method on the destination. This allows the destination
278 * to apply appropriate restrictions (e.g. readonly).
280 result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
285 /* ---------------------------------------------------------------
287 ** XML UTILITY FUNCTIONS
290 /* validate that the root element uses a given DAV: tagname (TRUE==valid) */
291 DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc,
295 doc->root->ns == APR_XML_NS_DAV_ID &&
296 strcmp(doc->root->name, tagname) == 0;
299 /* find and return the (unique) child with a given DAV: tagname */
300 DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem,
303 apr_xml_elem *child = elem->first_child;
305 for (; child; child = child->next)
306 if (child->ns == APR_XML_NS_DAV_ID && !strcmp(child->name, tagname))
311 /* gather up all the CDATA into a single string */
312 DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool,
317 const apr_xml_elem *child;
321 const char *found_text = NULL; /* initialize to avoid gcc warning */
324 for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
325 found_text = scan->text;
327 len += strlen(found_text);
330 for (child = elem->first_child; child != NULL; child = child->next) {
331 for (scan = child->following_cdata.first;
334 found_text = scan->text;
336 len += strlen(found_text);
340 /* some fast-path cases:
341 * 1) zero-length cdata
342 * 2) a single piece of cdata with no whitespace to strip
346 if (found_count == 1) {
348 || (!apr_isspace(*found_text)
349 && !apr_isspace(found_text[len - 1])))
353 cdata = s = apr_palloc(pool, len + 1);
355 for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
356 tlen = strlen(scan->text);
357 memcpy(s, scan->text, tlen);
361 for (child = elem->first_child; child != NULL; child = child->next) {
362 for (scan = child->following_cdata.first;
365 tlen = strlen(scan->text);
366 memcpy(s, scan->text, tlen);
374 /* trim leading whitespace */
375 while (apr_isspace(*cdata)) /* assume: return false for '\0' */
378 /* trim trailing whitespace */
379 while (len-- > 0 && apr_isspace(cdata[len]))
381 cdata[len + 1] = '\0';
387 DAV_DECLARE(dav_xmlns_info *) dav_xmlns_create(apr_pool_t *pool)
389 dav_xmlns_info *xi = apr_pcalloc(pool, sizeof(*xi));
392 xi->uri_prefix = apr_hash_make(pool);
393 xi->prefix_uri = apr_hash_make(pool);
398 DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi,
399 const char *prefix, const char *uri)
401 /* this "should" not overwrite a prefix mapping */
402 apr_hash_set(xi->prefix_uri, prefix, APR_HASH_KEY_STRING, uri);
404 /* note: this may overwrite an existing URI->prefix mapping, but it
405 doesn't matter -- any prefix is usuable to specify the URI. */
406 apr_hash_set(xi->uri_prefix, uri, APR_HASH_KEY_STRING, prefix);
409 DAV_DECLARE(const char *) dav_xmlns_add_uri(dav_xmlns_info *xi,
414 if ((prefix = apr_hash_get(xi->uri_prefix, uri,
415 APR_HASH_KEY_STRING)) != NULL)
418 prefix = apr_psprintf(xi->pool, "g%d", xi->count++);
419 dav_xmlns_add(xi, prefix, uri);
423 DAV_DECLARE(const char *) dav_xmlns_get_uri(dav_xmlns_info *xi,
426 return apr_hash_get(xi->prefix_uri, prefix, APR_HASH_KEY_STRING);
429 DAV_DECLARE(const char *) dav_xmlns_get_prefix(dav_xmlns_info *xi,
432 return apr_hash_get(xi->uri_prefix, uri, APR_HASH_KEY_STRING);
435 DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
436 apr_text_header *phdr)
438 apr_hash_index_t *hi = apr_hash_first(xi->pool, xi->prefix_uri);
440 for (; hi != NULL; hi = apr_hash_next(hi)) {
445 apr_hash_this(hi, &prefix, NULL, &uri);
447 s = apr_psprintf(xi->pool, " xmlns:%s=\"%s\"",
448 (const char *)prefix, (const char *)uri);
449 apr_text_append(xi->pool, phdr, s);
453 /* ---------------------------------------------------------------
455 ** Timeout header processing
459 /* dav_get_timeout: If the Timeout: header exists, return a time_t
460 * when this lock is expected to expire. Otherwise, return
461 * a time_t of DAV_TIMEOUT_INFINITE.
463 * It's unclear if DAV clients are required to understand
464 * Seconds-xxx and Infinity time values. We assume that they do.
465 * In addition, for now, that's all we understand, too.
467 DAV_DECLARE(time_t) dav_get_timeout(request_rec *r)
469 time_t now, expires = DAV_TIMEOUT_INFINITE;
471 const char *timeout_const = apr_table_get(r->headers_in, "Timeout");
472 const char *timeout = apr_pstrdup(r->pool, timeout_const), *val;
475 return DAV_TIMEOUT_INFINITE;
477 /* Use the first thing we understand, or infinity if
478 * we don't understand anything.
481 while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) {
482 if (!strncmp(val, "Infinite", 8)) {
483 return DAV_TIMEOUT_INFINITE;
486 if (!strncmp(val, "Second-", 7)) {
488 /* ### We need to handle overflow better:
489 * ### timeout will be <= 2^32 - 1
493 return now + expires;
497 return DAV_TIMEOUT_INFINITE;
500 /* ---------------------------------------------------------------
502 ** If Header processing
506 /* add_if_resource returns a new if_header, linking it to next_ih.
508 static dav_if_header *dav_add_if_resource(apr_pool_t *p, dav_if_header *next_ih,
509 const char *uri, apr_size_t uri_len)
513 if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL)
517 ih->uri_len = uri_len;
523 /* add_if_state adds a condition to an if_header.
525 static dav_error * dav_add_if_state(apr_pool_t *p, dav_if_header *ih,
526 const char *state_token,
527 dav_if_state_type t, int condition,
528 const dav_hooks_locks *locks_hooks)
530 dav_if_state_list *new_sl;
532 new_sl = apr_pcalloc(p, sizeof(*new_sl));
534 new_sl->condition = condition;
537 if (t == dav_if_opaquelock) {
540 if ((err = (*locks_hooks->parse_locktoken)(p, state_token,
541 &new_sl->locktoken)) != NULL) {
542 /* In cases where the state token is invalid, we'll just skip
543 * it rather than return 400.
545 if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) {
549 /* ### maybe add a higher-level description */
555 new_sl->etag = state_token;
557 new_sl->next = ih->state;
563 /* fetch_next_token returns the substring from str+1
564 * to the next occurence of char term, or \0, whichever
565 * occurs first. Leading whitespace is ignored.
567 static char *dav_fetch_next_token(char **str, char term)
574 while (*token && (*token == ' ' || *token == '\t'))
577 if ((sp = strchr(token, term)) == NULL)
585 /* dav_process_if_header:
587 * If NULL (no error) is returned, then **if_header points to the
588 * "If" productions structure (or NULL if "If" is not present).
590 * ### this part is bogus:
591 * If an error is encountered, the error is logged. Parent should
592 * return err->status.
594 static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
599 const char *state_token;
600 const char *uri = NULL; /* scope of current production; NULL=no-tag */
601 apr_size_t uri_len = 0;
602 dav_if_header *ih = NULL;
603 apr_uri_t parsed_uri;
604 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
605 enum {no_tagged, tagged, unknown} list_type = unknown;
610 if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL)
616 /* Tagged-list production - following states apply to this uri */
617 if (list_type == no_tagged
618 || ((uri = dav_fetch_next_token(&str, '>')) == NULL)) {
619 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
621 "Invalid If-header: unclosed \"<\" or "
622 "unexpected tagged-list production.");
625 /* 2518 specifies this must be an absolute URI; just take the
626 * relative part for later comparison against r->uri */
627 if (apr_uri_parse(r->pool, uri, &parsed_uri) != APR_SUCCESS
628 || !parsed_uri.path) {
629 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
631 "Invalid URI in tagged If-header.");
633 /* note that parsed_uri.path is allocated; we can trash it */
635 /* clean up the URI a bit */
636 ap_getparents(parsed_uri.path);
637 uri_len = strlen(parsed_uri.path);
638 if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/')
639 parsed_uri.path[--uri_len] = '\0';
641 uri = parsed_uri.path;
646 /* List production */
648 /* If a uri has not been encountered, this is a No-Tagged-List */
649 if (list_type == unknown)
650 list_type = no_tagged;
652 if ((list = dav_fetch_next_token(&str, ')')) == NULL) {
653 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
654 DAV_ERR_IF_UNCLOSED_PAREN,
655 "Invalid If-header: unclosed \"(\".");
658 if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) {
659 /* ### dav_add_if_resource() should return an error for us! */
660 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
662 "Internal server error parsing \"If:\" "
666 condition = DAV_IF_COND_NORMAL;
669 /* List is the entire production (in a uri scope) */
673 if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) {
674 /* ### add a description to this error */
675 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
676 DAV_ERR_IF_PARSE, NULL);
679 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock,
680 condition, locks_hooks)) != NULL) {
681 /* ### maybe add a higher level description */
684 condition = DAV_IF_COND_NORMAL;
688 if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) {
689 /* ### add a description to this error */
690 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
691 DAV_ERR_IF_PARSE, NULL);
694 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag,
695 condition, locks_hooks)) != NULL) {
696 /* ### maybe add a higher level description */
699 condition = DAV_IF_COND_NORMAL;
703 if (list[1] == 'o' && list[2] == 't') {
704 if (condition != DAV_IF_COND_NORMAL) {
705 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
706 DAV_ERR_IF_MULTIPLE_NOT,
707 "Invalid \"If:\" header: "
708 "Multiple \"not\" entries "
709 "for the same state.");
711 condition = DAV_IF_COND_NOT;
721 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
723 apr_psprintf(r->pool,
725 "header: Unexpected "
726 "character encountered "
740 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
742 apr_psprintf(r->pool,
743 "Invalid \"If:\" header: "
744 "Unexpected character "
745 "encountered (0x%02x, '%c').",
756 static int dav_find_submitted_locktoken(const dav_if_header *if_header,
757 const dav_lock *lock_list,
758 const dav_hooks_locks *locks_hooks)
760 for (; if_header != NULL; if_header = if_header->next) {
761 const dav_if_state_list *state_list;
763 for (state_list = if_header->state;
765 state_list = state_list->next) {
767 if (state_list->type == dav_if_opaquelock) {
768 const dav_lock *lock;
770 /* given state_list->locktoken, match it */
773 ** The resource will have one or more lock tokens. We only
774 ** need to match one of them against any token in the
777 ** One token case: It is an exclusive or shared lock. Either
778 ** way, we must find it.
780 ** N token case: They are shared locks. By policy, we need
781 ** to match only one. The resource's other
782 ** tokens may belong to somebody else (so we
783 ** shouldn't see them in the If: header anyway)
785 for (lock = lock_list; lock != NULL; lock = lock->next) {
787 if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
798 /* dav_validate_resource_state:
799 * Returns NULL if path/uri meets if-header and lock requirements
801 static dav_error * dav_validate_resource_state(apr_pool_t *p,
802 const dav_resource *resource,
804 const dav_if_header *if_header,
812 const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL);
813 const dav_if_header *ifhdr_scan;
814 dav_if_state_list *state_list;
821 const char *reason = NULL;
823 /* DBG1("validate: <%s>", resource->uri); */
826 ** The resource will have one of three states:
828 ** 1) No locks. We have no special requirements that the user supply
829 ** specific locktokens. One of the state lists must match, and
832 ** 2) One exclusive lock. The locktoken must appear *anywhere* in the
833 ** If: header. Of course, asserting the token in a "Not" term will
834 ** quickly fail that state list :-). If the locktoken appears in
835 ** one of the state lists *and* one state list matches, then we're
838 ** 3) One or more shared locks. One of the locktokens must appear
839 ** *anywhere* in the If: header. If one of the locktokens appears,
840 ** and we match one state list, then we are done.
842 ** The <seen_locktoken> variable determines whether we have seen one
843 ** of this resource's locktokens in the If: header.
847 ** If this is a new lock request, <flags> will contain the requested
848 ** lock scope. Three rules apply:
850 ** 1) Do not require a (shared) locktoken to be seen (when we are
851 ** applying another shared lock)
852 ** 2) If the scope is exclusive and we see any locks, fail.
853 ** 3) If the scope is shared and we see an exclusive lock, fail.
856 if (lockdb == NULL) {
857 /* we're in State 1. no locks. */
862 ** ### hrm... we don't need to have these fully
863 ** ### resolved since we're only looking at the
866 ** ### use get_locks w/ calltype=PARTIAL
868 if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) {
869 return dav_push_error(p,
870 HTTP_INTERNAL_SERVER_ERROR, 0,
871 "The locks could not be queried for "
872 "verification against a possible \"If:\" "
877 /* lock_list now determines whether we're in State 1, 2, or 3. */
881 ** For a new, exclusive lock: if any locks exist, fail.
882 ** For a new, shared lock: if an exclusive lock exists, fail.
883 ** else, do not require a token to be seen.
885 if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
886 if (lock_list != NULL) {
887 return dav_new_error(p, HTTP_LOCKED, 0,
888 "Existing lock(s) on the requested resource "
889 "prevent an exclusive lock.");
893 ** There are no locks, so we can pretend that we've already met
894 ** any requirement to find the resource's locks in an If: header.
898 else if (flags & DAV_LOCKSCOPE_SHARED) {
900 ** Strictly speaking, we don't need this loop. Either the first
901 ** (and only) lock will be EXCLUSIVE, or none of them will be.
903 for (lock = lock_list; lock != NULL; lock = lock->next) {
904 if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) {
905 return dav_new_error(p, HTTP_LOCKED, 0,
906 "The requested resource is already "
907 "locked exclusively.");
912 ** The locks on the resource (if any) are all shared. Set the
913 ** <seen_locktoken> flag to indicate that we do not need to find
914 ** the locks in an If: header.
920 ** For methods other than LOCK:
922 ** If we have no locks, then <seen_locktoken> can be set to true --
923 ** pretending that we've already met the requirement of seeing one
924 ** of the resource's locks in the If: header.
926 ** Otherwise, it must be cleared and we'll look for one.
928 seen_locktoken = (lock_list == NULL);
932 ** If there is no If: header, then we can shortcut some logic:
934 ** 1) if we do not need to find a locktoken in the (non-existent) If:
935 ** header, then we are successful.
937 ** 2) if we must find a locktoken in the (non-existent) If: header, then
940 if (if_header == NULL) {
944 return dav_new_error(p, HTTP_LOCKED, 0,
945 "This resource is locked and an \"If:\" header "
946 "was not supplied to allow access to the "
949 /* the If: header is present */
952 ** If a dummy header is present (because of a Lock-Token: header), then
953 ** we are required to find that token in this resource's set of locks.
954 ** If we have no locks, then we immediately fail.
956 ** This is a 400 (Bad Request) since they should only submit a locktoken
957 ** that actually exists.
959 ** Don't issue this response if we're talking about the parent resource.
960 ** It is okay for that resource to NOT have this locktoken.
961 ** (in fact, it certainly will not: a dummy_header only occurs for the
962 ** UNLOCK method, the parent is checked only for locknull resources,
963 ** and the parent certainly does not have the (locknull's) locktoken)
965 if (lock_list == NULL && if_header->dummy_header) {
966 if (flags & DAV_VALIDATE_IS_PARENT)
968 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
969 "The locktoken specified in the \"Lock-Token:\" "
970 "header is invalid because this resource has no "
971 "outstanding locks.");
975 ** Prepare the input URI. We want the URI to never have a trailing slash.
977 ** When URIs are placed into the dav_if_header structure, they are
978 ** guaranteed to never have a trailing slash. If the URIs are equivalent,
979 ** then it doesn't matter if they both lack a trailing slash -- they're
982 ** Note: we could also ensure that a trailing slash is present on both
983 ** URIs, but the majority of URIs provided to us via a resource walk
984 ** will not contain that trailing slash.
987 uri_len = strlen(uri);
988 if (uri[uri_len - 1] == '/') {
989 dav_set_bufsize(p, pbuf, uri_len);
990 memcpy(pbuf->buf, uri, uri_len);
991 pbuf->buf[--uri_len] = '\0';
995 /* get the resource's etag; we may need it during the checks */
996 etag = (*resource->hooks->getetag)(resource);
998 /* how many state_lists apply to this URI? */
1001 /* If there are if-headers, fail if this resource
1002 * does not match at least one state_list.
1004 for (ifhdr_scan = if_header;
1006 ifhdr_scan = ifhdr_scan->next) {
1008 /* DBG2("uri=<%s> if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
1010 if (ifhdr_scan->uri != NULL
1011 && (uri_len != ifhdr_scan->uri_len
1012 || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
1014 ** A tagged-list's URI doesn't match this resource's URI.
1015 ** Skip to the next state_list to see if it will match.
1020 /* this state_list applies to this resource */
1023 ** ### only one state_list should ever apply! a no-tag, or a tagged
1024 ** ### where S9.4.2 states only one can match.
1026 ** ### revamp this code to loop thru ifhdr_scan until we find the
1027 ** ### matching state_list. process it. stop.
1031 /* To succeed, resource must match *all* of the states
1032 * specified in the state_list.
1034 for (state_list = ifhdr_scan->state;
1036 state_list = state_list->next) {
1038 switch(state_list->type) {
1041 int mismatch = strcmp(state_list->etag, etag);
1043 if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
1045 ** The specified entity-tag does not match the
1046 ** entity-tag on the resource. This state_list is
1047 ** not going to match. Bust outta here.
1050 "an entity-tag was specified, but the resource's "
1051 "actual ETag does not match.";
1052 goto state_list_failed;
1054 else if (state_list->condition == DAV_IF_COND_NOT
1057 ** The specified entity-tag DOES match the
1058 ** entity-tag on the resource. This state_list is
1059 ** not going to match. Bust outta here.
1062 "an entity-tag was specified using the \"Not\" form, "
1063 "but the resource's actual ETag matches the provided "
1065 goto state_list_failed;
1070 case dav_if_opaquelock:
1071 if (lockdb == NULL) {
1072 if (state_list->condition == DAV_IF_COND_NOT) {
1073 /* the locktoken is definitely not there! (success) */
1077 /* condition == DAV_IF_COND_NORMAL */
1080 ** If no lockdb is provided, then validation fails for
1081 ** this state_list (NORMAL means we were supposed to
1082 ** find the token, which we obviously cannot do without
1083 ** a lock database).
1085 ** Go and try the next state list.
1088 "a State-token was supplied, but a lock database "
1089 "is not available for to provide the required lock.";
1090 goto state_list_failed;
1093 /* Resource validation 'fails' if:
1094 * ANY of the lock->locktokens match
1095 * a NOT state_list->locktoken,
1097 * NONE of the lock->locktokens match
1098 * a NORMAL state_list->locktoken.
1101 for (lock = lock_list; lock != NULL; lock = lock->next) {
1104 DBG2("compare: rsrc=%s ifhdr=%s",
1105 (*locks_hooks->format_locktoken)(p, lock->locktoken),
1106 (*locks_hooks->format_locktoken)(p, state_list->locktoken));
1109 /* nothing to do if the locktokens do not match. */
1110 if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
1115 ** We have now matched up one of the resource's locktokens
1116 ** to a locktoken in a State-token in the If: header.
1117 ** Note this fact, so that we can pass the overall
1118 ** requirement of seeing at least one of the resource's
1123 if (state_list->condition == DAV_IF_COND_NOT) {
1125 ** This state requires that the specified locktoken
1126 ** is NOT present on the resource. But we just found
1127 ** it. There is no way this state-list can now
1128 ** succeed, so go try another one.
1131 "a State-token was supplied, which used a "
1132 "\"Not\" condition. The State-token was found "
1133 "in the locks on this resource";
1134 goto state_list_failed;
1137 /* condition == DAV_IF_COND_NORMAL */
1139 /* Validate auth_user: If an authenticated user created
1140 ** the lock, only the same user may submit that locktoken
1141 ** to manipulate a resource.
1143 if (lock->auth_user &&
1145 strcmp(lock->auth_user, r->user))) {
1148 errmsg = apr_pstrcat(p, "User \"",
1150 "\" submitted a locktoken created "
1152 lock->auth_user, "\".", NULL);
1153 return dav_new_error(p, HTTP_FORBIDDEN, 0, errmsg);
1157 ** We just matched a specified State-Token to one of the
1158 ** resource's locktokens.
1160 ** Break out of the lock scan -- we only needed to find
1161 ** one match (actually, there shouldn't be any other
1162 ** matches in the lock list).
1168 if (num_matched == 0
1169 && state_list->condition == DAV_IF_COND_NORMAL) {
1171 ** We had a NORMAL state, meaning that we should have
1172 ** found the State-Token within the locks on this
1173 ** resource. We didn't, so this state_list must fail.
1176 "a State-token was supplied, but it was not found "
1177 "in the locks on this resource.";
1178 goto state_list_failed;
1184 } /* foreach ( state_list ) */
1187 ** We've checked every state in this state_list and none of them
1188 ** have failed. Since all of them succeeded, then we have a matching
1189 ** state list and we may be done.
1191 ** The next requirement is that we have seen one of the resource's
1192 ** locktokens (if any). If we have, then we can just exit. If we
1193 ** haven't, then we need to keep looking.
1195 if (seen_locktoken) {
1201 ** Haven't seen one. Let's break out of the search and just look
1202 ** for a matching locktoken.
1207 ** This label is used when we detect that a state_list is not
1208 ** going to match this resource. We bust out and try the next
1214 } /* foreach ( ifhdr_scan ) */
1217 ** The above loop exits for one of two reasons:
1218 ** 1) a state_list matched and seen_locktoken is false.
1219 ** 2) all if_header structures were scanned, without (1) occurring
1222 if (ifhdr_scan == NULL) {
1224 ** We finished the loop without finding any matching state lists.
1228 ** If none of the state_lists apply to this resource, then we
1229 ** may have succeeded. Note that this scenario implies a
1230 ** tagged-list with no matching state_lists. If the If: header
1231 ** was a no-tag-list, then it would have applied to this resource.
1233 ** S9.4.2 states that when no state_lists apply, then the header
1234 ** should be ignored.
1236 ** If we saw one of the resource's locktokens, then we're done.
1237 ** If we did not see a locktoken, then we fail.
1239 if (num_that_apply == 0) {
1244 ** We may have aborted the scan before seeing the locktoken.
1245 ** Rescan the If: header to see if we can find the locktoken
1248 ** Note that seen_locktoken == 0 implies lock_list != NULL
1249 ** which implies locks_hooks != NULL.
1251 if (dav_find_submitted_locktoken(if_header, lock_list,
1254 ** We found a match! We're set... none of the If: header
1255 ** assertions apply (implicit success), and the If: header
1256 ** specified the locktoken somewhere. We're done.
1261 return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */,
1262 "This resource is locked and the \"If:\" "
1263 "header did not specify one of the "
1264 "locktokens for this resource's lock(s).");
1266 /* else: one or more state_lists were applicable, but failed. */
1269 ** If the dummy_header did not match, then they specified an
1270 ** incorrect token in the Lock-Token header. Forget whether the
1271 ** If: statement matched or not... we'll tell them about the
1272 ** bad Lock-Token first. That is considered a 400 (Bad Request).
1274 if (if_header->dummy_header) {
1275 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1276 "The locktoken specified in the "
1277 "\"Lock-Token:\" header did not specify one "
1278 "of this resource's locktoken(s).");
1281 if (reason == NULL) {
1282 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1283 "The preconditions specified by the \"If:\" "
1284 "header did not match this resource.");
1287 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1289 "The precondition(s) specified by "
1290 "the \"If:\" header did not match "
1291 "this resource. At least one "
1292 "failure is because: %s", reason));
1295 /* assert seen_locktoken == 0 */
1298 ** ifhdr_scan != NULL implies we found a matching state_list.
1300 ** Since we're still here, it also means that we have not yet found
1301 ** one the resource's locktokens in the If: header.
1303 ** Scan all the if_headers and states looking for one of this
1304 ** resource's locktokens. Note that we need to go back and scan them
1305 ** all -- we may have aborted a scan with a failure before we saw a
1308 ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
1309 ** locks_hooks != NULL.
1311 if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
1313 ** We found a match! We're set... we have a matching state list,
1314 ** and the If: header specified the locktoken somewhere. We're done.
1320 ** We had a matching state list, but the user agent did not specify one
1321 ** of this resource's locktokens. Tell them so.
1323 ** Note that we need to special-case the message on whether a "dummy"
1324 ** header exists. If it exists, yet we didn't see a needed locktoken,
1325 ** then that implies the dummy header (Lock-Token header) did NOT
1326 ** specify one of this resource's locktokens. (this implies something
1327 ** in the real If: header matched)
1329 ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
1331 if (if_header->dummy_header) {
1332 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1333 "The locktoken specified in the "
1334 "\"Lock-Token:\" header did not specify one "
1335 "of this resource's locktoken(s).");
1338 return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */,
1339 "This resource is locked and the \"If:\" header "
1340 "did not specify one of the "
1341 "locktokens for this resource's lock(s).");
1344 /* dav_validate_walker: Walker callback function to validate resource state */
1345 static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
1347 dav_walker_ctx *ctx = wres->walk_ctx;
1350 if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
1352 ctx->if_header, ctx->flags,
1353 &ctx->work_buf, ctx->r)) == NULL) {
1354 /* There was no error, so just bug out. */
1359 ** If we have a serious server error, or if the request itself failed,
1360 ** then just return error (not a multistatus).
1362 if (ap_is_HTTP_SERVER_ERROR(err->status)
1363 || (*wres->resource->hooks->is_same_resource)(wres->resource,
1365 /* ### maybe push a higher-level description? */
1369 /* associate the error with the current URI */
1370 dav_add_response(wres, err->status, NULL);
1376 ** dav_validate_request: Validate if-headers (and check for locks) on:
1377 ** (1) r->filename @ depth;
1378 ** (2) Parent of r->filename if check_parent == 1
1380 ** The check of parent should be done when it is necessary to verify that
1381 ** the parent collection will accept a new member (ie current resource
1384 ** Return OK on successful validation.
1385 ** On error, return appropriate HTTP_* code, and log error. If a multi-stat
1386 ** error is necessary, response will point to it, else NULL.
1388 DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r,
1389 dav_resource *resource,
1391 dav_locktoken *locktoken,
1392 dav_response **response,
1398 dav_if_header *if_header;
1399 int lock_db_opened_locally = 0;
1400 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1401 const dav_hooks_repository *repos_hooks = resource->hooks;
1402 dav_buffer work_buf = { 0 };
1403 dav_response *new_response;
1406 if (depth && response == NULL) {
1408 ** ### bleck. we can't return errors for other URIs unless we have
1409 ** ### a "response" ptr.
1411 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1412 "DESIGN ERROR: dav_validate_request called "
1413 "with depth>0, but no response ptr.");
1417 if (response != NULL)
1420 /* Do the standard checks for conditional requests using
1421 * If-..-Since, If-Match etc */
1422 if ((result = ap_meets_conditions(r)) != OK) {
1423 /* ### fix this up... how? */
1424 return dav_new_error(r->pool, result, 0, NULL);
1427 /* always parse (and later process) the If: header */
1428 if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1429 /* ### maybe add higher-level description */
1433 /* If a locktoken was specified, create a dummy if_header with which
1434 * to validate resources. In the interim, figure out why DAV uses
1435 * locktokens in an if-header without a Lock-Token header to refresh
1436 * locks, but a Lock-Token header without an if-header to remove them.
1438 if (locktoken != NULL) {
1439 dav_if_header *ifhdr_new;
1441 ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new));
1442 ifhdr_new->uri = resource->uri;
1443 ifhdr_new->uri_len = strlen(resource->uri);
1444 ifhdr_new->dummy_header = 1;
1446 ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state));
1447 ifhdr_new->state->type = dav_if_opaquelock;
1448 ifhdr_new->state->condition = DAV_IF_COND_NORMAL;
1449 ifhdr_new->state->locktoken = locktoken;
1451 ifhdr_new->next = if_header;
1452 if_header = ifhdr_new;
1456 ** If necessary, open the lock database (read-only, lazily);
1457 ** the validation process may need to retrieve or update lock info.
1458 ** Otherwise, assume provided lockdb is valid and opened rw.
1460 if (lockdb == NULL) {
1461 if (locks_hooks != NULL) {
1462 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1463 /* ### maybe insert higher-level comment */
1466 lock_db_opened_locally = 1;
1470 /* (1) Validate the specified resource, at the specified depth */
1471 if (resource->exists && depth > 0) {
1472 dav_walker_ctx ctx = { { 0 } };
1473 dav_response *multi_status;
1475 ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
1476 ctx.w.func = dav_validate_walker;
1477 ctx.w.walk_ctx = &ctx;
1478 ctx.w.pool = r->pool;
1479 ctx.w.root = resource;
1481 ctx.if_header = if_header;
1485 if (lockdb != NULL) {
1486 ctx.w.lockdb = lockdb;
1487 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1490 err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
1492 *response = multi_status;;
1494 /* else: implies a 5xx status code occurred. */
1497 err = dav_validate_resource_state(r->pool, resource, lockdb,
1498 if_header, flags, &work_buf, r);
1501 /* (2) Validate the parent resource if requested */
1502 if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
1503 dav_resource *parent_resource;
1505 err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
1507 if (err == NULL && parent_resource == NULL) {
1508 err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
1509 "Cannot access parent of repository root.");
1511 else if (err == NULL) {
1512 err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
1514 flags | DAV_VALIDATE_IS_PARENT,
1518 ** This error occurred on the parent resource. This implies that
1519 ** we have to create a multistatus response (to report the error
1520 ** against a URI other than the Request-URI). "Convert" this error
1521 ** into a multistatus response.
1524 new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1526 new_response->href = parent_resource->uri;
1527 new_response->status = err->status;
1528 new_response->desc =
1529 "A validation error has occurred on the parent resource, "
1530 "preventing the operation on the resource specified by "
1532 if (err->desc != NULL) {
1533 new_response->desc = apr_pstrcat(r->pool,
1539 /* assert: DAV_VALIDATE_PARENT implies response != NULL */
1540 new_response->next = *response;
1541 *response = new_response;
1548 if (lock_db_opened_locally)
1549 (*locks_hooks->close_lockdb)(lockdb);
1552 ** If we don't have a (serious) error, and we have multistatus responses,
1553 ** then we need to construct an "error". This error will be the overall
1554 ** status returned, and the multistatus responses will go into its body.
1556 ** For certain methods, the overall error will be a 424. The default is
1557 ** to construct a standard 207 response.
1559 if (err == NULL && response != NULL && *response != NULL) {
1560 apr_text *propstat = NULL;
1562 if ((flags & DAV_VALIDATE_USE_424) != 0) {
1563 /* manufacture a 424 error to hold the multistatus response(s) */
1564 return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0,
1565 "An error occurred on another resource, "
1566 "preventing the requested operation on "
1571 ** Whatever caused the error, the Request-URI should have a 424
1572 ** associated with it since we cannot complete the method.
1574 ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
1575 ** For other methods, return a simple 424.
1577 if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
1578 propstat = apr_pcalloc(r->pool, sizeof(*propstat));
1580 "<D:propstat>" DEBUG_CR
1581 "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
1582 "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
1583 "</D:propstat>" DEBUG_CR;
1586 /* create the 424 response */
1587 new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1588 new_response->href = resource->uri;
1589 new_response->status = HTTP_FAILED_DEPENDENCY;
1590 new_response->propresult.propstats = propstat;
1591 new_response->desc =
1592 "An error occurred on another resource, preventing the "
1593 "requested operation on this resource.";
1595 new_response->next = *response;
1596 *response = new_response;
1598 /* manufacture a 207 error for the multistatus response(s) */
1599 return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
1600 "Error(s) occurred on resources during the "
1601 "validation process.");
1607 /* dav_get_locktoken_list:
1609 * Sets ltl to a locktoken_list of all positive locktokens in header,
1610 * else NULL if no If-header, or no positive locktokens.
1612 DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r,
1613 dav_locktoken_list **ltl)
1616 dav_if_header *if_header;
1617 dav_if_state_list *if_state;
1618 dav_locktoken_list *lock_token = NULL;
1622 if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1623 /* ### add a higher-level description? */
1627 while (if_header != NULL) {
1628 if_state = if_header->state; /* Begining of the if_state linked list */
1629 while (if_state != NULL) {
1630 if (if_state->condition == DAV_IF_COND_NORMAL
1631 && if_state->type == dav_if_opaquelock) {
1632 lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list));
1633 lock_token->locktoken = if_state->locktoken;
1634 lock_token->next = *ltl;
1637 if_state = if_state->next;
1639 if_header = if_header->next;
1642 /* No nodes added */
1643 return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT,
1644 "No locktokens were specified in the \"If:\" "
1645 "header, so the refresh could not be performed.");
1651 #if 0 /* not needed right now... */
1653 static const char *strip_white(const char *s, apr_pool_t *pool)
1657 /* trim leading whitespace */
1658 while (apr_isspace(*s)) /* assume: return false for '\0' */
1661 /* trim trailing whitespace */
1662 idx = strlen(s) - 1;
1663 if (apr_isspace(s[idx])) {
1664 char *s2 = apr_pstrdup(pool, s);
1666 while (apr_isspace(s2[idx]) && idx > 0)
1676 #define DAV_LABEL_HDR "Label"
1678 /* dav_add_vary_header
1680 * If there were any headers in the request which require a Vary header
1681 * in the response, add it.
1683 DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req,
1684 request_rec *out_req,
1685 const dav_resource *resource)
1687 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
1689 /* ### this is probably all wrong... I think there is a function in
1690 ### the Apache API to add things to the Vary header. need to check */
1692 /* Only versioning headers require a Vary response header,
1693 * so only do this check if there is a versioning provider */
1694 if (vsn_hooks != NULL) {
1695 const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR);
1696 const char *vary = apr_table_get(out_req->headers_out, "Vary");
1698 /* If Target-Selector specified, add it to the Vary header */
1699 if (target != NULL) {
1701 vary = DAV_LABEL_HDR;
1703 vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
1706 apr_table_setn(out_req->headers_out, "Vary", vary);
1711 /* dav_can_auto_checkout
1713 * Determine whether auto-checkout is enabled for a resource.
1714 * r - the request_rec
1715 * resource - the resource
1716 * auto_version - the value of the auto_versionable hook for the resource
1717 * lockdb - pointer to lock database (opened if necessary)
1718 * auto_checkout - set to 1 if auto-checkout enabled
1720 static dav_error * dav_can_auto_checkout(
1722 dav_resource *resource,
1723 dav_auto_version auto_version,
1724 dav_lockdb **lockdb,
1728 dav_lock *lock_list;
1732 if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
1735 else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
1736 if (*lockdb == NULL) {
1737 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1739 if (locks_hooks == NULL) {
1740 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1741 "Auto-checkout is only enabled for locked resources, "
1742 "but there is no lock provider.");
1745 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) {
1746 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1747 "Cannot open lock database to determine "
1748 "auto-versioning behavior.",
1753 if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) {
1754 return dav_push_error(r->pool,
1755 HTTP_INTERNAL_SERVER_ERROR, 0,
1756 "The locks could not be queried for "
1757 "determining auto-versioning behavior.",
1761 if (lock_list != NULL)
1768 /* see mod_dav.h for docco */
1769 DAV_DECLARE(dav_error *) dav_auto_checkout(
1771 dav_resource *resource,
1773 dav_auto_version_info *av_info)
1775 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1776 dav_lockdb *lockdb = NULL;
1777 dav_error *err = NULL;
1779 /* Initialize results */
1780 memset(av_info, 0, sizeof(*av_info));
1782 /* if no versioning provider, just return */
1783 if (vsn_hooks == NULL)
1786 /* check parent resource if requested or if resource must be created */
1787 if (!resource->exists || parent_only) {
1788 dav_resource *parent;
1790 if ((err = (*resource->hooks->get_parent_resource)(resource,
1794 if (parent == NULL || !parent->exists) {
1795 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1796 apr_psprintf(r->pool,
1797 "Missing one or more intermediate "
1798 "collections. Cannot create resource %s.",
1799 ap_escape_html(r->pool, resource->uri)));
1803 av_info->parent_resource = parent;
1805 /* if parent versioned and not checked out, see if it can be */
1806 if (parent->versioned && !parent->working) {
1807 int checkout_parent;
1809 if ((err = dav_can_auto_checkout(r, parent,
1810 (*vsn_hooks->auto_versionable)(parent),
1811 &lockdb, &checkout_parent))
1816 if (!checkout_parent) {
1817 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1818 "<DAV:cannot-modify-checked-in-parent>");
1822 /* Try to checkout the parent collection.
1823 * Note that auto-versioning can only be applied to a version selector,
1824 * so no separate working resource will be created.
1826 if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
1827 0, 0, 0, NULL, NULL))
1830 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1831 apr_psprintf(r->pool,
1832 "Unable to auto-checkout parent collection. "
1833 "Cannot create resource %s.",
1834 ap_escape_html(r->pool, resource->uri)),
1839 /* remember that parent was checked out */
1840 av_info->parent_checkedout = 1;
1844 /* if only checking parent, we're done */
1848 /* if creating a new resource, see if it should be version-controlled */
1849 if (!resource->exists
1850 && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) {
1852 if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
1853 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1854 apr_psprintf(r->pool,
1855 "Unable to create versioned resource %s.",
1856 ap_escape_html(r->pool, resource->uri)),
1861 /* remember that resource was created */
1862 av_info->resource_versioned = 1;
1865 /* if resource is versioned, make sure it is checked out */
1866 if (resource->versioned && !resource->working) {
1867 int checkout_resource;
1869 if ((err = dav_can_auto_checkout(r, resource,
1870 (*vsn_hooks->auto_versionable)(resource),
1871 &lockdb, &checkout_resource)) != NULL) {
1875 if (!checkout_resource) {
1876 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1877 "<DAV:cannot-modify-version-controlled-content>");
1881 /* Auto-versioning can only be applied to version selectors, so
1882 * no separate working resource will be created. */
1883 if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/,
1884 0, 0, 0, NULL, NULL))
1887 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1888 apr_psprintf(r->pool,
1889 "Unable to checkout resource %s.",
1890 ap_escape_html(r->pool, resource->uri)),
1895 /* remember that resource was checked out */
1896 av_info->resource_checkedout = 1;
1901 /* make sure lock database is closed */
1903 (*lockdb->hooks->close_lockdb)(lockdb);
1905 /* if an error occurred, undo any auto-versioning operations already done */
1907 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
1914 /* see mod_dav.h for docco */
1915 DAV_DECLARE(dav_error *) dav_auto_checkin(
1917 dav_resource *resource,
1920 dav_auto_version_info *av_info)
1922 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1923 dav_error *err = NULL;
1924 dav_auto_version auto_version;
1926 /* If no versioning provider, this is a no-op */
1927 if (vsn_hooks == NULL)
1930 /* If undoing auto-checkouts, then do uncheckouts */
1932 if (resource != NULL) {
1933 if (av_info->resource_checkedout) {
1934 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
1935 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1936 apr_psprintf(r->pool,
1937 "Unable to undo auto-checkout "
1939 ap_escape_html(r->pool, resource->uri)),
1944 if (av_info->resource_versioned) {
1945 dav_response *response;
1947 /* ### should we do anything with the response? */
1948 if ((err = (*resource->hooks->remove_resource)(resource,
1949 &response)) != NULL) {
1950 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1951 apr_psprintf(r->pool,
1952 "Unable to undo auto-version-control "
1954 ap_escape_html(r->pool, resource->uri)),
1960 if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
1961 if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) {
1962 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1963 apr_psprintf(r->pool,
1964 "Unable to undo auto-checkout "
1965 "of parent collection %s.",
1966 ap_escape_html(r->pool, av_info->parent_resource->uri)),
1974 /* If the resource was checked out, and auto-checkin is enabled,
1977 if (resource != NULL && resource->working
1978 && (unlock || av_info->resource_checkedout)) {
1980 auto_version = (*vsn_hooks->auto_versionable)(resource);
1982 if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
1983 (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
1985 if ((err = (*vsn_hooks->checkin)(resource,
1986 0 /*keep_checked_out*/, NULL))
1988 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1989 apr_psprintf(r->pool,
1990 "Unable to auto-checkin resource %s.",
1991 ap_escape_html(r->pool, resource->uri)),
1997 /* If parent resource was checked out, and auto-checkin is enabled,
2001 && av_info->parent_checkedout
2002 && av_info->parent_resource != NULL
2003 && av_info->parent_resource->working) {
2005 auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
2007 if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
2008 if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
2009 0 /*keep_checked_out*/, NULL))
2011 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2012 apr_psprintf(r->pool,
2013 "Unable to auto-checkin parent collection %s.",
2014 ap_escape_html(r->pool, av_info->parent_resource->uri)),