1 /* Copyright 2000-2005 The Apache Software Foundation or its licensors, as
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * 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_scheme(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 port = r->connection->local_addr->port;
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 /* If the state token cannot be parsed, treat it as an
543 * unknown state; this will evaluate to "false" later
544 * during If header validation. */
545 if (err->error_id == DAV_ERR_LOCK_UNK_STATE_TOKEN) {
546 new_sl->type = dav_if_unknown;
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 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
630 "Invalid URI in tagged If-header.");
632 /* note that parsed_uri.path is allocated; we can trash it */
634 /* clean up the URI a bit */
635 ap_getparents(parsed_uri.path);
636 uri_len = strlen(parsed_uri.path);
637 if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/')
638 parsed_uri.path[--uri_len] = '\0';
640 uri = parsed_uri.path;
645 /* List production */
647 /* If a uri has not been encountered, this is a No-Tagged-List */
648 if (list_type == unknown)
649 list_type = no_tagged;
651 if ((list = dav_fetch_next_token(&str, ')')) == NULL) {
652 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
653 DAV_ERR_IF_UNCLOSED_PAREN,
654 "Invalid If-header: unclosed \"(\".");
657 if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) {
658 /* ### dav_add_if_resource() should return an error for us! */
659 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
661 "Internal server error parsing \"If:\" "
665 condition = DAV_IF_COND_NORMAL;
668 /* List is the entire production (in a uri scope) */
672 if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) {
673 /* ### add a description to this error */
674 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
675 DAV_ERR_IF_PARSE, NULL);
678 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock,
679 condition, locks_hooks)) != NULL) {
680 /* ### maybe add a higher level description */
683 condition = DAV_IF_COND_NORMAL;
687 if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) {
688 /* ### add a description to this error */
689 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
690 DAV_ERR_IF_PARSE, NULL);
693 if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag,
694 condition, locks_hooks)) != NULL) {
695 /* ### maybe add a higher level description */
698 condition = DAV_IF_COND_NORMAL;
702 if (list[1] == 'o' && list[2] == 't') {
703 if (condition != DAV_IF_COND_NORMAL) {
704 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
705 DAV_ERR_IF_MULTIPLE_NOT,
706 "Invalid \"If:\" header: "
707 "Multiple \"not\" entries "
708 "for the same state.");
710 condition = DAV_IF_COND_NOT;
720 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
722 apr_psprintf(r->pool,
724 "header: Unexpected "
725 "character encountered "
739 return dav_new_error(r->pool, HTTP_BAD_REQUEST,
741 apr_psprintf(r->pool,
742 "Invalid \"If:\" header: "
743 "Unexpected character "
744 "encountered (0x%02x, '%c').",
755 static int dav_find_submitted_locktoken(const dav_if_header *if_header,
756 const dav_lock *lock_list,
757 const dav_hooks_locks *locks_hooks)
759 for (; if_header != NULL; if_header = if_header->next) {
760 const dav_if_state_list *state_list;
762 for (state_list = if_header->state;
764 state_list = state_list->next) {
766 if (state_list->type == dav_if_opaquelock) {
767 const dav_lock *lock;
769 /* given state_list->locktoken, match it */
772 ** The resource will have one or more lock tokens. We only
773 ** need to match one of them against any token in the
776 ** One token case: It is an exclusive or shared lock. Either
777 ** way, we must find it.
779 ** N token case: They are shared locks. By policy, we need
780 ** to match only one. The resource's other
781 ** tokens may belong to somebody else (so we
782 ** shouldn't see them in the If: header anyway)
784 for (lock = lock_list; lock != NULL; lock = lock->next) {
786 if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
797 /* dav_validate_resource_state:
798 * Returns NULL if path/uri meets if-header and lock requirements
800 static dav_error * dav_validate_resource_state(apr_pool_t *p,
801 const dav_resource *resource,
803 const dav_if_header *if_header,
811 const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL);
812 const dav_if_header *ifhdr_scan;
813 dav_if_state_list *state_list;
820 const char *reason = NULL;
822 /* DBG1("validate: <%s>", resource->uri); */
825 ** The resource will have one of three states:
827 ** 1) No locks. We have no special requirements that the user supply
828 ** specific locktokens. One of the state lists must match, and
831 ** 2) One exclusive lock. The locktoken must appear *anywhere* in the
832 ** If: header. Of course, asserting the token in a "Not" term will
833 ** quickly fail that state list :-). If the locktoken appears in
834 ** one of the state lists *and* one state list matches, then we're
837 ** 3) One or more shared locks. One of the locktokens must appear
838 ** *anywhere* in the If: header. If one of the locktokens appears,
839 ** and we match one state list, then we are done.
841 ** The <seen_locktoken> variable determines whether we have seen one
842 ** of this resource's locktokens in the If: header.
846 ** If this is a new lock request, <flags> will contain the requested
847 ** lock scope. Three rules apply:
849 ** 1) Do not require a (shared) locktoken to be seen (when we are
850 ** applying another shared lock)
851 ** 2) If the scope is exclusive and we see any locks, fail.
852 ** 3) If the scope is shared and we see an exclusive lock, fail.
855 if (lockdb == NULL) {
856 /* we're in State 1. no locks. */
861 ** ### hrm... we don't need to have these fully
862 ** ### resolved since we're only looking at the
865 ** ### use get_locks w/ calltype=PARTIAL
867 if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) {
868 return dav_push_error(p,
869 HTTP_INTERNAL_SERVER_ERROR, 0,
870 "The locks could not be queried for "
871 "verification against a possible \"If:\" "
876 /* lock_list now determines whether we're in State 1, 2, or 3. */
880 ** For a new, exclusive lock: if any locks exist, fail.
881 ** For a new, shared lock: if an exclusive lock exists, fail.
882 ** else, do not require a token to be seen.
884 if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
885 if (lock_list != NULL) {
886 return dav_new_error(p, HTTP_LOCKED, 0,
887 "Existing lock(s) on the requested resource "
888 "prevent an exclusive lock.");
892 ** There are no locks, so we can pretend that we've already met
893 ** any requirement to find the resource's locks in an If: header.
897 else if (flags & DAV_LOCKSCOPE_SHARED) {
899 ** Strictly speaking, we don't need this loop. Either the first
900 ** (and only) lock will be EXCLUSIVE, or none of them will be.
902 for (lock = lock_list; lock != NULL; lock = lock->next) {
903 if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) {
904 return dav_new_error(p, HTTP_LOCKED, 0,
905 "The requested resource is already "
906 "locked exclusively.");
911 ** The locks on the resource (if any) are all shared. Set the
912 ** <seen_locktoken> flag to indicate that we do not need to find
913 ** the locks in an If: header.
919 ** For methods other than LOCK:
921 ** If we have no locks, then <seen_locktoken> can be set to true --
922 ** pretending that we've already met the requirement of seeing one
923 ** of the resource's locks in the If: header.
925 ** Otherwise, it must be cleared and we'll look for one.
927 seen_locktoken = (lock_list == NULL);
931 ** If there is no If: header, then we can shortcut some logic:
933 ** 1) if we do not need to find a locktoken in the (non-existent) If:
934 ** header, then we are successful.
936 ** 2) if we must find a locktoken in the (non-existent) If: header, then
939 if (if_header == NULL) {
943 return dav_new_error(p, HTTP_LOCKED, 0,
944 "This resource is locked and an \"If:\" header "
945 "was not supplied to allow access to the "
948 /* the If: header is present */
951 ** If a dummy header is present (because of a Lock-Token: header), then
952 ** we are required to find that token in this resource's set of locks.
953 ** If we have no locks, then we immediately fail.
955 ** This is a 400 (Bad Request) since they should only submit a locktoken
956 ** that actually exists.
958 ** Don't issue this response if we're talking about the parent resource.
959 ** It is okay for that resource to NOT have this locktoken.
960 ** (in fact, it certainly will not: a dummy_header only occurs for the
961 ** UNLOCK method, the parent is checked only for locknull resources,
962 ** and the parent certainly does not have the (locknull's) locktoken)
964 if (lock_list == NULL && if_header->dummy_header) {
965 if (flags & DAV_VALIDATE_IS_PARENT)
967 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
968 "The locktoken specified in the \"Lock-Token:\" "
969 "header is invalid because this resource has no "
970 "outstanding locks.");
974 ** Prepare the input URI. We want the URI to never have a trailing slash.
976 ** When URIs are placed into the dav_if_header structure, they are
977 ** guaranteed to never have a trailing slash. If the URIs are equivalent,
978 ** then it doesn't matter if they both lack a trailing slash -- they're
981 ** Note: we could also ensure that a trailing slash is present on both
982 ** URIs, but the majority of URIs provided to us via a resource walk
983 ** will not contain that trailing slash.
986 uri_len = strlen(uri);
987 if (uri[uri_len - 1] == '/') {
988 dav_set_bufsize(p, pbuf, uri_len);
989 memcpy(pbuf->buf, uri, uri_len);
990 pbuf->buf[--uri_len] = '\0';
994 /* get the resource's etag; we may need it during the checks */
995 etag = (*resource->hooks->getetag)(resource);
997 /* how many state_lists apply to this URI? */
1000 /* If there are if-headers, fail if this resource
1001 * does not match at least one state_list.
1003 for (ifhdr_scan = if_header;
1005 ifhdr_scan = ifhdr_scan->next) {
1007 /* DBG2("uri=<%s> if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
1009 if (ifhdr_scan->uri != NULL
1010 && (uri_len != ifhdr_scan->uri_len
1011 || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
1013 ** A tagged-list's URI doesn't match this resource's URI.
1014 ** Skip to the next state_list to see if it will match.
1019 /* this state_list applies to this resource */
1022 ** ### only one state_list should ever apply! a no-tag, or a tagged
1023 ** ### where S9.4.2 states only one can match.
1025 ** ### revamp this code to loop thru ifhdr_scan until we find the
1026 ** ### matching state_list. process it. stop.
1030 /* To succeed, resource must match *all* of the states
1031 * specified in the state_list.
1033 for (state_list = ifhdr_scan->state;
1035 state_list = state_list->next) {
1037 switch(state_list->type) {
1040 const char *given_etag, *current_etag;
1043 /* Do a weak entity comparison function as defined in
1046 if (state_list->etag[0] == 'W' &&
1047 state_list->etag[1] == '/') {
1048 given_etag = state_list->etag + 2;
1051 given_etag = state_list->etag;
1053 if (etag[0] == 'W' &&
1055 current_etag = etag + 2;
1058 current_etag = etag;
1061 mismatch = strcmp(given_etag, current_etag);
1063 if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
1065 ** The specified entity-tag does not match the
1066 ** entity-tag on the resource. This state_list is
1067 ** not going to match. Bust outta here.
1070 "an entity-tag was specified, but the resource's "
1071 "actual ETag does not match.";
1072 goto state_list_failed;
1074 else if (state_list->condition == DAV_IF_COND_NOT
1077 ** The specified entity-tag DOES match the
1078 ** entity-tag on the resource. This state_list is
1079 ** not going to match. Bust outta here.
1082 "an entity-tag was specified using the \"Not\" form, "
1083 "but the resource's actual ETag matches the provided "
1085 goto state_list_failed;
1090 case dav_if_opaquelock:
1091 if (lockdb == NULL) {
1092 if (state_list->condition == DAV_IF_COND_NOT) {
1093 /* the locktoken is definitely not there! (success) */
1097 /* condition == DAV_IF_COND_NORMAL */
1100 ** If no lockdb is provided, then validation fails for
1101 ** this state_list (NORMAL means we were supposed to
1102 ** find the token, which we obviously cannot do without
1103 ** a lock database).
1105 ** Go and try the next state list.
1108 "a State-token was supplied, but a lock database "
1109 "is not available for to provide the required lock.";
1110 goto state_list_failed;
1113 /* Resource validation 'fails' if:
1114 * ANY of the lock->locktokens match
1115 * a NOT state_list->locktoken,
1117 * NONE of the lock->locktokens match
1118 * a NORMAL state_list->locktoken.
1121 for (lock = lock_list; lock != NULL; lock = lock->next) {
1124 DBG2("compare: rsrc=%s ifhdr=%s",
1125 (*locks_hooks->format_locktoken)(p, lock->locktoken),
1126 (*locks_hooks->format_locktoken)(p, state_list->locktoken));
1129 /* nothing to do if the locktokens do not match. */
1130 if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
1135 ** We have now matched up one of the resource's locktokens
1136 ** to a locktoken in a State-token in the If: header.
1137 ** Note this fact, so that we can pass the overall
1138 ** requirement of seeing at least one of the resource's
1143 if (state_list->condition == DAV_IF_COND_NOT) {
1145 ** This state requires that the specified locktoken
1146 ** is NOT present on the resource. But we just found
1147 ** it. There is no way this state-list can now
1148 ** succeed, so go try another one.
1151 "a State-token was supplied, which used a "
1152 "\"Not\" condition. The State-token was found "
1153 "in the locks on this resource";
1154 goto state_list_failed;
1157 /* condition == DAV_IF_COND_NORMAL */
1159 /* Validate auth_user: If an authenticated user created
1160 ** the lock, only the same user may submit that locktoken
1161 ** to manipulate a resource.
1163 if (lock->auth_user &&
1165 strcmp(lock->auth_user, r->user))) {
1168 errmsg = apr_pstrcat(p, "User \"",
1170 "\" submitted a locktoken created "
1172 lock->auth_user, "\".", NULL);
1173 return dav_new_error(p, HTTP_FORBIDDEN, 0, errmsg);
1177 ** We just matched a specified State-Token to one of the
1178 ** resource's locktokens.
1180 ** Break out of the lock scan -- we only needed to find
1181 ** one match (actually, there shouldn't be any other
1182 ** matches in the lock list).
1188 if (num_matched == 0
1189 && state_list->condition == DAV_IF_COND_NORMAL) {
1191 ** We had a NORMAL state, meaning that we should have
1192 ** found the State-Token within the locks on this
1193 ** resource. We didn't, so this state_list must fail.
1196 "a State-token was supplied, but it was not found "
1197 "in the locks on this resource.";
1198 goto state_list_failed;
1203 case dav_if_unknown:
1204 /* Request is predicated on some unknown state token,
1205 * which must be presumed to *not* match, so fail
1206 * unless this is a Not condition. */
1208 if (state_list->condition == DAV_IF_COND_NORMAL) {
1210 "an unknown state token was supplied";
1211 goto state_list_failed;
1216 } /* foreach ( state_list ) */
1219 ** We've checked every state in this state_list and none of them
1220 ** have failed. Since all of them succeeded, then we have a matching
1221 ** state list and we may be done.
1223 ** The next requirement is that we have seen one of the resource's
1224 ** locktokens (if any). If we have, then we can just exit. If we
1225 ** haven't, then we need to keep looking.
1227 if (seen_locktoken) {
1233 ** Haven't seen one. Let's break out of the search and just look
1234 ** for a matching locktoken.
1239 ** This label is used when we detect that a state_list is not
1240 ** going to match this resource. We bust out and try the next
1246 } /* foreach ( ifhdr_scan ) */
1249 ** The above loop exits for one of two reasons:
1250 ** 1) a state_list matched and seen_locktoken is false.
1251 ** 2) all if_header structures were scanned, without (1) occurring
1254 if (ifhdr_scan == NULL) {
1256 ** We finished the loop without finding any matching state lists.
1260 ** If none of the state_lists apply to this resource, then we
1261 ** may have succeeded. Note that this scenario implies a
1262 ** tagged-list with no matching state_lists. If the If: header
1263 ** was a no-tag-list, then it would have applied to this resource.
1265 ** S9.4.2 states that when no state_lists apply, then the header
1266 ** should be ignored.
1268 ** If we saw one of the resource's locktokens, then we're done.
1269 ** If we did not see a locktoken, then we fail.
1271 if (num_that_apply == 0) {
1276 ** We may have aborted the scan before seeing the locktoken.
1277 ** Rescan the If: header to see if we can find the locktoken
1280 ** Note that seen_locktoken == 0 implies lock_list != NULL
1281 ** which implies locks_hooks != NULL.
1283 if (dav_find_submitted_locktoken(if_header, lock_list,
1286 ** We found a match! We're set... none of the If: header
1287 ** assertions apply (implicit success), and the If: header
1288 ** specified the locktoken somewhere. We're done.
1293 return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */,
1294 "This resource is locked and the \"If:\" "
1295 "header did not specify one of the "
1296 "locktokens for this resource's lock(s).");
1298 /* else: one or more state_lists were applicable, but failed. */
1301 ** If the dummy_header did not match, then they specified an
1302 ** incorrect token in the Lock-Token header. Forget whether the
1303 ** If: statement matched or not... we'll tell them about the
1304 ** bad Lock-Token first. That is considered a 400 (Bad Request).
1306 if (if_header->dummy_header) {
1307 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1308 "The locktoken specified in the "
1309 "\"Lock-Token:\" header did not specify one "
1310 "of this resource's locktoken(s).");
1313 if (reason == NULL) {
1314 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1315 "The preconditions specified by the \"If:\" "
1316 "header did not match this resource.");
1319 return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
1321 "The precondition(s) specified by "
1322 "the \"If:\" header did not match "
1323 "this resource. At least one "
1324 "failure is because: %s", reason));
1327 /* assert seen_locktoken == 0 */
1330 ** ifhdr_scan != NULL implies we found a matching state_list.
1332 ** Since we're still here, it also means that we have not yet found
1333 ** one the resource's locktokens in the If: header.
1335 ** Scan all the if_headers and states looking for one of this
1336 ** resource's locktokens. Note that we need to go back and scan them
1337 ** all -- we may have aborted a scan with a failure before we saw a
1340 ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
1341 ** locks_hooks != NULL.
1343 if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
1345 ** We found a match! We're set... we have a matching state list,
1346 ** and the If: header specified the locktoken somewhere. We're done.
1352 ** We had a matching state list, but the user agent did not specify one
1353 ** of this resource's locktokens. Tell them so.
1355 ** Note that we need to special-case the message on whether a "dummy"
1356 ** header exists. If it exists, yet we didn't see a needed locktoken,
1357 ** then that implies the dummy header (Lock-Token header) did NOT
1358 ** specify one of this resource's locktokens. (this implies something
1359 ** in the real If: header matched)
1361 ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
1363 if (if_header->dummy_header) {
1364 return dav_new_error(p, HTTP_BAD_REQUEST, 0,
1365 "The locktoken specified in the "
1366 "\"Lock-Token:\" header did not specify one "
1367 "of this resource's locktoken(s).");
1370 return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */,
1371 "This resource is locked and the \"If:\" header "
1372 "did not specify one of the "
1373 "locktokens for this resource's lock(s).");
1376 /* dav_validate_walker: Walker callback function to validate resource state */
1377 static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
1379 dav_walker_ctx *ctx = wres->walk_ctx;
1382 if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
1384 ctx->if_header, ctx->flags,
1385 &ctx->work_buf, ctx->r)) == NULL) {
1386 /* There was no error, so just bug out. */
1391 ** If we have a serious server error, or if the request itself failed,
1392 ** then just return error (not a multistatus).
1394 if (ap_is_HTTP_SERVER_ERROR(err->status)
1395 || (*wres->resource->hooks->is_same_resource)(wres->resource,
1397 /* ### maybe push a higher-level description? */
1401 /* associate the error with the current URI */
1402 dav_add_response(wres, err->status, NULL);
1408 ** dav_validate_request: Validate if-headers (and check for locks) on:
1409 ** (1) r->filename @ depth;
1410 ** (2) Parent of r->filename if check_parent == 1
1412 ** The check of parent should be done when it is necessary to verify that
1413 ** the parent collection will accept a new member (ie current resource
1416 ** Return OK on successful validation.
1417 ** On error, return appropriate HTTP_* code, and log error. If a multi-stat
1418 ** error is necessary, response will point to it, else NULL.
1420 DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r,
1421 dav_resource *resource,
1423 dav_locktoken *locktoken,
1424 dav_response **response,
1430 dav_if_header *if_header;
1431 int lock_db_opened_locally = 0;
1432 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1433 const dav_hooks_repository *repos_hooks = resource->hooks;
1434 dav_buffer work_buf = { 0 };
1435 dav_response *new_response;
1438 if (depth && response == NULL) {
1440 ** ### bleck. we can't return errors for other URIs unless we have
1441 ** ### a "response" ptr.
1443 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1444 "DESIGN ERROR: dav_validate_request called "
1445 "with depth>0, but no response ptr.");
1449 if (response != NULL)
1452 /* Do the standard checks for conditional requests using
1453 * If-..-Since, If-Match etc */
1454 if ((result = ap_meets_conditions(r)) != OK) {
1455 /* ### fix this up... how? */
1456 return dav_new_error(r->pool, result, 0, NULL);
1459 /* always parse (and later process) the If: header */
1460 if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1461 /* ### maybe add higher-level description */
1465 /* If a locktoken was specified, create a dummy if_header with which
1466 * to validate resources. In the interim, figure out why DAV uses
1467 * locktokens in an if-header without a Lock-Token header to refresh
1468 * locks, but a Lock-Token header without an if-header to remove them.
1470 if (locktoken != NULL) {
1471 dav_if_header *ifhdr_new;
1473 ifhdr_new = apr_pcalloc(r->pool, sizeof(*ifhdr_new));
1474 ifhdr_new->uri = resource->uri;
1475 ifhdr_new->uri_len = strlen(resource->uri);
1476 ifhdr_new->dummy_header = 1;
1478 ifhdr_new->state = apr_pcalloc(r->pool, sizeof(*ifhdr_new->state));
1479 ifhdr_new->state->type = dav_if_opaquelock;
1480 ifhdr_new->state->condition = DAV_IF_COND_NORMAL;
1481 ifhdr_new->state->locktoken = locktoken;
1483 ifhdr_new->next = if_header;
1484 if_header = ifhdr_new;
1488 ** If necessary, open the lock database (read-only, lazily);
1489 ** the validation process may need to retrieve or update lock info.
1490 ** Otherwise, assume provided lockdb is valid and opened rw.
1492 if (lockdb == NULL) {
1493 if (locks_hooks != NULL) {
1494 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
1495 /* ### maybe insert higher-level comment */
1498 lock_db_opened_locally = 1;
1502 /* (1) Validate the specified resource, at the specified depth */
1503 if (resource->exists && depth > 0) {
1504 dav_walker_ctx ctx = { { 0 } };
1505 dav_response *multi_status;
1507 ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
1508 ctx.w.func = dav_validate_walker;
1509 ctx.w.walk_ctx = &ctx;
1510 ctx.w.pool = r->pool;
1511 ctx.w.root = resource;
1513 ctx.if_header = if_header;
1517 if (lockdb != NULL) {
1518 ctx.w.lockdb = lockdb;
1519 ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
1522 err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
1524 *response = multi_status;;
1526 /* else: implies a 5xx status code occurred. */
1529 err = dav_validate_resource_state(r->pool, resource, lockdb,
1530 if_header, flags, &work_buf, r);
1533 /* (2) Validate the parent resource if requested */
1534 if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
1535 dav_resource *parent_resource;
1537 err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
1539 if (err == NULL && parent_resource == NULL) {
1540 err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
1541 "Cannot access parent of repository root.");
1543 else if (err == NULL) {
1544 err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
1546 flags | DAV_VALIDATE_IS_PARENT,
1550 ** This error occurred on the parent resource. This implies that
1551 ** we have to create a multistatus response (to report the error
1552 ** against a URI other than the Request-URI). "Convert" this error
1553 ** into a multistatus response.
1556 new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1558 new_response->href = parent_resource->uri;
1559 new_response->status = err->status;
1560 new_response->desc =
1561 "A validation error has occurred on the parent resource, "
1562 "preventing the operation on the resource specified by "
1564 if (err->desc != NULL) {
1565 new_response->desc = apr_pstrcat(r->pool,
1571 /* assert: DAV_VALIDATE_PARENT implies response != NULL */
1572 new_response->next = *response;
1573 *response = new_response;
1580 if (lock_db_opened_locally)
1581 (*locks_hooks->close_lockdb)(lockdb);
1584 ** If we don't have a (serious) error, and we have multistatus responses,
1585 ** then we need to construct an "error". This error will be the overall
1586 ** status returned, and the multistatus responses will go into its body.
1588 ** For certain methods, the overall error will be a 424. The default is
1589 ** to construct a standard 207 response.
1591 if (err == NULL && response != NULL && *response != NULL) {
1592 apr_text *propstat = NULL;
1594 if ((flags & DAV_VALIDATE_USE_424) != 0) {
1595 /* manufacture a 424 error to hold the multistatus response(s) */
1596 return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0,
1597 "An error occurred on another resource, "
1598 "preventing the requested operation on "
1603 ** Whatever caused the error, the Request-URI should have a 424
1604 ** associated with it since we cannot complete the method.
1606 ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
1607 ** For other methods, return a simple 424.
1609 if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
1610 propstat = apr_pcalloc(r->pool, sizeof(*propstat));
1612 "<D:propstat>" DEBUG_CR
1613 "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
1614 "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
1615 "</D:propstat>" DEBUG_CR;
1618 /* create the 424 response */
1619 new_response = apr_pcalloc(r->pool, sizeof(*new_response));
1620 new_response->href = resource->uri;
1621 new_response->status = HTTP_FAILED_DEPENDENCY;
1622 new_response->propresult.propstats = propstat;
1623 new_response->desc =
1624 "An error occurred on another resource, preventing the "
1625 "requested operation on this resource.";
1627 new_response->next = *response;
1628 *response = new_response;
1630 /* manufacture a 207 error for the multistatus response(s) */
1631 return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
1632 "Error(s) occurred on resources during the "
1633 "validation process.");
1639 /* dav_get_locktoken_list:
1641 * Sets ltl to a locktoken_list of all positive locktokens in header,
1642 * else NULL if no If-header, or no positive locktokens.
1644 DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r,
1645 dav_locktoken_list **ltl)
1648 dav_if_header *if_header;
1649 dav_if_state_list *if_state;
1650 dav_locktoken_list *lock_token = NULL;
1654 if ((err = dav_process_if_header(r, &if_header)) != NULL) {
1655 /* ### add a higher-level description? */
1659 while (if_header != NULL) {
1660 if_state = if_header->state; /* Begining of the if_state linked list */
1661 while (if_state != NULL) {
1662 if (if_state->condition == DAV_IF_COND_NORMAL
1663 && if_state->type == dav_if_opaquelock) {
1664 lock_token = apr_pcalloc(r->pool, sizeof(dav_locktoken_list));
1665 lock_token->locktoken = if_state->locktoken;
1666 lock_token->next = *ltl;
1669 if_state = if_state->next;
1671 if_header = if_header->next;
1674 /* No nodes added */
1675 return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT,
1676 "No locktokens were specified in the \"If:\" "
1677 "header, so the refresh could not be performed.");
1683 #if 0 /* not needed right now... */
1685 static const char *strip_white(const char *s, apr_pool_t *pool)
1689 /* trim leading whitespace */
1690 while (apr_isspace(*s)) /* assume: return false for '\0' */
1693 /* trim trailing whitespace */
1694 idx = strlen(s) - 1;
1695 if (apr_isspace(s[idx])) {
1696 char *s2 = apr_pstrdup(pool, s);
1698 while (apr_isspace(s2[idx]) && idx > 0)
1708 #define DAV_LABEL_HDR "Label"
1710 /* dav_add_vary_header
1712 * If there were any headers in the request which require a Vary header
1713 * in the response, add it.
1715 DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req,
1716 request_rec *out_req,
1717 const dav_resource *resource)
1719 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
1721 /* ### this is probably all wrong... I think there is a function in
1722 ### the Apache API to add things to the Vary header. need to check */
1724 /* Only versioning headers require a Vary response header,
1725 * so only do this check if there is a versioning provider */
1726 if (vsn_hooks != NULL) {
1727 const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR);
1728 const char *vary = apr_table_get(out_req->headers_out, "Vary");
1730 /* If Target-Selector specified, add it to the Vary header */
1731 if (target != NULL) {
1733 vary = DAV_LABEL_HDR;
1735 vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
1738 apr_table_setn(out_req->headers_out, "Vary", vary);
1743 /* dav_can_auto_checkout
1745 * Determine whether auto-checkout is enabled for a resource.
1746 * r - the request_rec
1747 * resource - the resource
1748 * auto_version - the value of the auto_versionable hook for the resource
1749 * lockdb - pointer to lock database (opened if necessary)
1750 * auto_checkout - set to 1 if auto-checkout enabled
1752 static dav_error * dav_can_auto_checkout(
1754 dav_resource *resource,
1755 dav_auto_version auto_version,
1756 dav_lockdb **lockdb,
1760 dav_lock *lock_list;
1764 if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
1767 else if (auto_version == DAV_AUTO_VERSION_LOCKED) {
1768 if (*lockdb == NULL) {
1769 const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
1771 if (locks_hooks == NULL) {
1772 return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1773 "Auto-checkout is only enabled for locked resources, "
1774 "but there is no lock provider.");
1777 if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, lockdb)) != NULL) {
1778 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1779 "Cannot open lock database to determine "
1780 "auto-versioning behavior.",
1785 if ((err = dav_lock_query(*lockdb, resource, &lock_list)) != NULL) {
1786 return dav_push_error(r->pool,
1787 HTTP_INTERNAL_SERVER_ERROR, 0,
1788 "The locks could not be queried for "
1789 "determining auto-versioning behavior.",
1793 if (lock_list != NULL)
1800 /* see mod_dav.h for docco */
1801 DAV_DECLARE(dav_error *) dav_auto_checkout(
1803 dav_resource *resource,
1805 dav_auto_version_info *av_info)
1807 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1808 dav_lockdb *lockdb = NULL;
1809 dav_error *err = NULL;
1811 /* Initialize results */
1812 memset(av_info, 0, sizeof(*av_info));
1814 /* if no versioning provider, just return */
1815 if (vsn_hooks == NULL)
1818 /* check parent resource if requested or if resource must be created */
1819 if (!resource->exists || parent_only) {
1820 dav_resource *parent;
1822 if ((err = (*resource->hooks->get_parent_resource)(resource,
1826 if (parent == NULL || !parent->exists) {
1827 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1828 apr_psprintf(r->pool,
1829 "Missing one or more intermediate "
1830 "collections. Cannot create resource %s.",
1831 ap_escape_html(r->pool, resource->uri)));
1835 av_info->parent_resource = parent;
1837 /* if parent versioned and not checked out, see if it can be */
1838 if (parent->versioned && !parent->working) {
1839 int checkout_parent;
1841 if ((err = dav_can_auto_checkout(r, parent,
1842 (*vsn_hooks->auto_versionable)(parent),
1843 &lockdb, &checkout_parent))
1848 if (!checkout_parent) {
1849 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1850 "<DAV:cannot-modify-checked-in-parent>");
1854 /* Try to checkout the parent collection.
1855 * Note that auto-versioning can only be applied to a version selector,
1856 * so no separate working resource will be created.
1858 if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
1859 0, 0, 0, NULL, NULL))
1862 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1863 apr_psprintf(r->pool,
1864 "Unable to auto-checkout parent collection. "
1865 "Cannot create resource %s.",
1866 ap_escape_html(r->pool, resource->uri)),
1871 /* remember that parent was checked out */
1872 av_info->parent_checkedout = 1;
1876 /* if only checking parent, we're done */
1880 /* if creating a new resource, see if it should be version-controlled */
1881 if (!resource->exists
1882 && (*vsn_hooks->auto_versionable)(resource) == DAV_AUTO_VERSION_ALWAYS) {
1884 if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
1885 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1886 apr_psprintf(r->pool,
1887 "Unable to create versioned resource %s.",
1888 ap_escape_html(r->pool, resource->uri)),
1893 /* remember that resource was created */
1894 av_info->resource_versioned = 1;
1897 /* if resource is versioned, make sure it is checked out */
1898 if (resource->versioned && !resource->working) {
1899 int checkout_resource;
1901 if ((err = dav_can_auto_checkout(r, resource,
1902 (*vsn_hooks->auto_versionable)(resource),
1903 &lockdb, &checkout_resource)) != NULL) {
1907 if (!checkout_resource) {
1908 err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
1909 "<DAV:cannot-modify-version-controlled-content>");
1913 /* Auto-versioning can only be applied to version selectors, so
1914 * no separate working resource will be created. */
1915 if ((err = (*vsn_hooks->checkout)(resource, 1 /*auto_checkout*/,
1916 0, 0, 0, NULL, NULL))
1919 err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
1920 apr_psprintf(r->pool,
1921 "Unable to checkout resource %s.",
1922 ap_escape_html(r->pool, resource->uri)),
1927 /* remember that resource was checked out */
1928 av_info->resource_checkedout = 1;
1933 /* make sure lock database is closed */
1935 (*lockdb->hooks->close_lockdb)(lockdb);
1937 /* if an error occurred, undo any auto-versioning operations already done */
1939 dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
1946 /* see mod_dav.h for docco */
1947 DAV_DECLARE(dav_error *) dav_auto_checkin(
1949 dav_resource *resource,
1952 dav_auto_version_info *av_info)
1954 const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
1955 dav_error *err = NULL;
1956 dav_auto_version auto_version;
1958 /* If no versioning provider, this is a no-op */
1959 if (vsn_hooks == NULL)
1962 /* If undoing auto-checkouts, then do uncheckouts */
1964 if (resource != NULL) {
1965 if (av_info->resource_checkedout) {
1966 if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
1967 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1968 apr_psprintf(r->pool,
1969 "Unable to undo auto-checkout "
1971 ap_escape_html(r->pool, resource->uri)),
1976 if (av_info->resource_versioned) {
1977 dav_response *response;
1979 /* ### should we do anything with the response? */
1980 if ((err = (*resource->hooks->remove_resource)(resource,
1981 &response)) != NULL) {
1982 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1983 apr_psprintf(r->pool,
1984 "Unable to undo auto-version-control "
1986 ap_escape_html(r->pool, resource->uri)),
1992 if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
1993 if ((err = (*vsn_hooks->uncheckout)(av_info->parent_resource)) != NULL) {
1994 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
1995 apr_psprintf(r->pool,
1996 "Unable to undo auto-checkout "
1997 "of parent collection %s.",
1998 ap_escape_html(r->pool, av_info->parent_resource->uri)),
2006 /* If the resource was checked out, and auto-checkin is enabled,
2009 if (resource != NULL && resource->working
2010 && (unlock || av_info->resource_checkedout)) {
2012 auto_version = (*vsn_hooks->auto_versionable)(resource);
2014 if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
2015 (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
2017 if ((err = (*vsn_hooks->checkin)(resource,
2018 0 /*keep_checked_out*/, NULL))
2020 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2021 apr_psprintf(r->pool,
2022 "Unable to auto-checkin resource %s.",
2023 ap_escape_html(r->pool, resource->uri)),
2029 /* If parent resource was checked out, and auto-checkin is enabled,
2033 && av_info->parent_checkedout
2034 && av_info->parent_resource != NULL
2035 && av_info->parent_resource->working) {
2037 auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
2039 if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
2040 if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
2041 0 /*keep_checked_out*/, NULL))
2043 return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
2044 apr_psprintf(r->pool,
2045 "Unable to auto-checkin parent collection %s.",
2046 ap_escape_html(r->pool, av_info->parent_resource->uri)),