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_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)),