~ubuntu-branches/ubuntu/feisty/apache2/feisty

« back to all changes in this revision

Viewing changes to modules/dav/main/util.c

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Barth
  • Date: 2006-12-09 21:05:45 UTC
  • mfrom: (0.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061209210545-h70s0xaqc2v8vqr2
Tags: 2.2.3-3.2
* Non-maintainer upload.
* 043_ajp_connection_reuse: Patch from upstream Bugzilla, fixing a critical
  issue with regard to connection reuse in mod_proxy_ajp.
  Closes: #396265

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
7
 *
 
8
 *     http://www.apache.org/licenses/LICENSE-2.0
 
9
 *
 
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.
 
15
 */
 
16
 
 
17
/*
 
18
** DAV extension module for Apache 2.0.*
 
19
**  - various utilities, repository-independent
 
20
*/
 
21
 
 
22
#include "apr_strings.h"
 
23
#include "apr_lib.h"
 
24
 
 
25
#define APR_WANT_STRFUNC
 
26
#include "apr_want.h"
 
27
 
 
28
#include "mod_dav.h"
 
29
 
 
30
#include "http_request.h"
 
31
#include "http_config.h"
 
32
#include "http_vhost.h"
 
33
#include "http_log.h"
 
34
#include "http_protocol.h"
 
35
 
 
36
DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status,
 
37
                                      int error_id, const char *desc)
 
38
{
 
39
    int save_errno = errno;
 
40
    dav_error *err = apr_pcalloc(p, sizeof(*err));
 
41
 
 
42
    /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */
 
43
 
 
44
    err->status = status;
 
45
    err->error_id = error_id;
 
46
    err->desc = desc;
 
47
    err->save_errno = save_errno;
 
48
 
 
49
    return err;
 
50
}
 
51
 
 
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,
 
55
                                          const char *tagname)
 
56
{
 
57
    dav_error *err = dav_new_error(p, status, error_id, desc);
 
58
 
 
59
    err->tagname = tagname;
 
60
    err->namespace = namespace;
 
61
 
 
62
    return err;
 
63
}
 
64
 
 
65
 
 
66
DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status,
 
67
                                       int error_id, const char *desc,
 
68
                                       dav_error *prev)
 
69
{
 
70
    dav_error *err = apr_pcalloc(p, sizeof(*err));
 
71
 
 
72
    err->status = status;
 
73
    err->error_id = error_id;
 
74
    err->desc = desc;
 
75
    err->prev = prev;
 
76
 
 
77
    return err;
 
78
}
 
79
 
 
80
DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
 
81
                                    apr_size_t extra_needed)
 
82
{
 
83
    /* grow the buffer if necessary */
 
84
    if (pbuf->cur_len + extra_needed > pbuf->alloc_len) {
 
85
        char *newbuf;
 
86
 
 
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);
 
90
        pbuf->buf = newbuf;
 
91
    }
 
92
}
 
93
 
 
94
DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf,
 
95
                                  apr_size_t size)
 
96
{
 
97
    /* NOTE: this does not retain prior contents */
 
98
 
 
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 */
 
101
 
 
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;
 
108
 
 
109
        pbuf->buf = apr_palloc(p, pbuf->alloc_len);
 
110
    }
 
111
    pbuf->cur_len = size;
 
112
}
 
113
 
 
114
 
 
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,
 
117
                                  const char *str)
 
118
{
 
119
    dav_set_bufsize(p, pbuf, strlen(str));
 
120
    memcpy(pbuf->buf, str, pbuf->cur_len + 1);
 
121
}
 
122
 
 
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,
 
125
                                    const char *str)
 
126
{
 
127
    apr_size_t len = strlen(str);
 
128
 
 
129
    dav_check_bufsize(p, pbuf, len + 1);
 
130
    memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
 
131
    pbuf->cur_len += len;
 
132
}
 
133
 
 
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,
 
136
                                   const char *str)
 
137
{
 
138
    apr_size_t len = strlen(str);
 
139
 
 
140
    dav_check_bufsize(p, pbuf, len + 1);
 
141
    memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
 
142
}
 
143
 
 
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,
 
147
                                       apr_size_t pad)
 
148
{
 
149
    dav_check_bufsize(p, pbuf, amt + pad);
 
150
    memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
 
151
}
 
152
 
 
153
/*
 
154
** dav_lookup_uri()
 
155
**
 
156
** Extension for ap_sub_req_lookup_uri() which can't handle absolute
 
157
** URIs properly.
 
158
**
 
159
** If NULL is returned, then an error occurred with parsing the URI or
 
160
** the URI does not match the current server.
 
161
*/
 
162
DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri,
 
163
                                              request_rec * r,
 
164
                                              int must_be_absolute)
 
165
{
 
166
    dav_lookup_result result = { 0 };
 
167
    const char *scheme;
 
168
    apr_port_t port;
 
169
    apr_uri_t comp;
 
170
    char *new_file;
 
171
    const char *domain;
 
172
 
 
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.";
 
177
        return result;
 
178
    }
 
179
 
 
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.";
 
184
        return result;
 
185
    }
 
186
 
 
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;
 
190
        result.err.desc =
 
191
            "Destination URI contains invalid components "
 
192
            "(a query or a fragment).";
 
193
        return result;
 
194
    }
 
195
 
 
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.
 
199
 
 
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.
 
203
    */
 
204
    if (comp.scheme != NULL || comp.port != 0 || must_be_absolute)
 
205
    {
 
206
        /* ### not sure this works if the current request came in via https: */
 
207
        scheme = r->parsed_uri.scheme;
 
208
        if (scheme == NULL)
 
209
            scheme = ap_http_scheme(r);
 
210
 
 
211
        /* insert a port if the URI did not contain one */
 
212
        if (comp.port == 0)
 
213
            comp.port = apr_uri_port_of_scheme(comp.scheme);
 
214
 
 
215
        /* now, verify that the URI uses the same scheme as the current.
 
216
           request. the port must match our port.
 
217
        */
 
218
        port = r->connection->local_addr->port;
 
219
        if (strcasecmp(comp.scheme, scheme) != 0
 
220
#ifdef APACHE_PORT_HANDLING_IS_BUSTED
 
221
            || comp.port != port
 
222
#endif
 
223
            ) {
 
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,
 
232
                                           scheme, port);
 
233
            return result;
 
234
        }
 
235
    }
 
236
 
 
237
    /* we have verified the scheme, port, and general structure */
 
238
 
 
239
    /*
 
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.
 
243
    **
 
244
    ** For now, qualify unqualified comp.hostnames with
 
245
    ** r->server->server_hostname.
 
246
    **
 
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?
 
250
    */
 
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);
 
255
    }
 
256
 
 
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.";
 
265
        return result;
 
266
    }
 
267
#endif
 
268
 
 
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() */
 
271
 
 
272
    /* reconstruct a URI as just the path */
 
273
    new_file = apr_uri_unparse(r->pool, &comp, APR_URI_UNP_OMITSITEPART);
 
274
 
 
275
    /*
 
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).
 
279
     */
 
280
    result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
 
281
 
 
282
    return result;
 
283
}
 
284
 
 
285
/* ---------------------------------------------------------------
 
286
**
 
287
** XML UTILITY FUNCTIONS
 
288
*/
 
289
 
 
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,
 
292
                                   const char *tagname)
 
293
{
 
294
    return doc->root &&
 
295
        doc->root->ns == APR_XML_NS_DAV_ID &&
 
296
        strcmp(doc->root->name, tagname) == 0;
 
297
}
 
298
 
 
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,
 
301
                                           const char *tagname)
 
302
{
 
303
    apr_xml_elem *child = elem->first_child;
 
304
 
 
305
    for (; child; child = child->next)
 
306
        if (child->ns == APR_XML_NS_DAV_ID && !strcmp(child->name, tagname))
 
307
            return child;
 
308
    return NULL;
 
309
}
 
310
 
 
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,
 
313
                              int strip_white)
 
314
{
 
315
    apr_size_t len = 0;
 
316
    apr_text *scan;
 
317
    const apr_xml_elem *child;
 
318
    char *cdata;
 
319
    char *s;
 
320
    apr_size_t tlen;
 
321
    const char *found_text = NULL; /* initialize to avoid gcc warning */
 
322
    int found_count = 0;
 
323
 
 
324
    for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
 
325
        found_text = scan->text;
 
326
        ++found_count;
 
327
        len += strlen(found_text);
 
328
    }
 
329
 
 
330
    for (child = elem->first_child; child != NULL; child = child->next) {
 
331
        for (scan = child->following_cdata.first;
 
332
             scan != NULL;
 
333
             scan = scan->next) {
 
334
            found_text = scan->text;
 
335
            ++found_count;
 
336
            len += strlen(found_text);
 
337
        }
 
338
    }
 
339
 
 
340
    /* some fast-path cases:
 
341
     * 1) zero-length cdata
 
342
     * 2) a single piece of cdata with no whitespace to strip
 
343
     */
 
344
    if (len == 0)
 
345
        return "";
 
346
    if (found_count == 1) {
 
347
        if (!strip_white
 
348
            || (!apr_isspace(*found_text)
 
349
                && !apr_isspace(found_text[len - 1])))
 
350
            return found_text;
 
351
    }
 
352
 
 
353
    cdata = s = apr_palloc(pool, len + 1);
 
354
 
 
355
    for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
 
356
        tlen = strlen(scan->text);
 
357
        memcpy(s, scan->text, tlen);
 
358
        s += tlen;
 
359
    }
 
360
 
 
361
    for (child = elem->first_child; child != NULL; child = child->next) {
 
362
        for (scan = child->following_cdata.first;
 
363
             scan != NULL;
 
364
             scan = scan->next) {
 
365
            tlen = strlen(scan->text);
 
366
            memcpy(s, scan->text, tlen);
 
367
            s += tlen;
 
368
        }
 
369
    }
 
370
 
 
371
    *s = '\0';
 
372
 
 
373
    if (strip_white) {
 
374
        /* trim leading whitespace */
 
375
        while (apr_isspace(*cdata))     /* assume: return false for '\0' */
 
376
            ++cdata;
 
377
 
 
378
        /* trim trailing whitespace */
 
379
        while (len-- > 0 && apr_isspace(cdata[len]))
 
380
            continue;
 
381
        cdata[len + 1] = '\0';
 
382
    }
 
383
 
 
384
    return cdata;
 
385
}
 
386
 
 
387
DAV_DECLARE(dav_xmlns_info *) dav_xmlns_create(apr_pool_t *pool)
 
388
{
 
389
    dav_xmlns_info *xi = apr_pcalloc(pool, sizeof(*xi));
 
390
 
 
391
    xi->pool = pool;
 
392
    xi->uri_prefix = apr_hash_make(pool);
 
393
    xi->prefix_uri = apr_hash_make(pool);
 
394
 
 
395
    return xi;
 
396
}
 
397
 
 
398
DAV_DECLARE(void) dav_xmlns_add(dav_xmlns_info *xi,
 
399
                                const char *prefix, const char *uri)
 
400
{
 
401
    /* this "should" not overwrite a prefix mapping */
 
402
    apr_hash_set(xi->prefix_uri, prefix, APR_HASH_KEY_STRING, uri);
 
403
 
 
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);
 
407
}
 
408
 
 
409
DAV_DECLARE(const char *) dav_xmlns_add_uri(dav_xmlns_info *xi,
 
410
                                            const char *uri)
 
411
{
 
412
    const char *prefix;
 
413
 
 
414
    if ((prefix = apr_hash_get(xi->uri_prefix, uri,
 
415
                               APR_HASH_KEY_STRING)) != NULL)
 
416
        return prefix;
 
417
 
 
418
    prefix = apr_psprintf(xi->pool, "g%d", xi->count++);
 
419
    dav_xmlns_add(xi, prefix, uri);
 
420
    return prefix;
 
421
}
 
422
 
 
423
DAV_DECLARE(const char *) dav_xmlns_get_uri(dav_xmlns_info *xi,
 
424
                                            const char *prefix)
 
425
{
 
426
    return apr_hash_get(xi->prefix_uri, prefix, APR_HASH_KEY_STRING);
 
427
}
 
428
 
 
429
DAV_DECLARE(const char *) dav_xmlns_get_prefix(dav_xmlns_info *xi,
 
430
                                               const char *uri)
 
431
{
 
432
    return apr_hash_get(xi->uri_prefix, uri, APR_HASH_KEY_STRING);
 
433
}
 
434
 
 
435
DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi,
 
436
                                     apr_text_header *phdr)
 
437
{
 
438
    apr_hash_index_t *hi = apr_hash_first(xi->pool, xi->prefix_uri);
 
439
 
 
440
    for (; hi != NULL; hi = apr_hash_next(hi)) {
 
441
        const void *prefix;
 
442
        void *uri;
 
443
        const char *s;
 
444
 
 
445
        apr_hash_this(hi, &prefix, NULL, &uri);
 
446
 
 
447
        s = apr_psprintf(xi->pool, " xmlns:%s=\"%s\"",
 
448
                         (const char *)prefix, (const char *)uri);
 
449
        apr_text_append(xi->pool, phdr, s);
 
450
    }
 
451
}
 
452
 
 
453
/* ---------------------------------------------------------------
 
454
**
 
455
** Timeout header processing
 
456
**
 
457
*/
 
458
 
 
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.
 
462
 *
 
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.
 
466
 */
 
467
DAV_DECLARE(time_t) dav_get_timeout(request_rec *r)
 
468
{
 
469
    time_t now, expires = DAV_TIMEOUT_INFINITE;
 
470
 
 
471
    const char *timeout_const = apr_table_get(r->headers_in, "Timeout");
 
472
    const char *timeout = apr_pstrdup(r->pool, timeout_const), *val;
 
473
 
 
474
    if (timeout == NULL)
 
475
        return DAV_TIMEOUT_INFINITE;
 
476
 
 
477
    /* Use the first thing we understand, or infinity if
 
478
     * we don't understand anything.
 
479
     */
 
480
 
 
481
    while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) {
 
482
        if (!strncmp(val, "Infinite", 8)) {
 
483
            return DAV_TIMEOUT_INFINITE;
 
484
        }
 
485
 
 
486
        if (!strncmp(val, "Second-", 7)) {
 
487
            val += 7;
 
488
            /* ### We need to handle overflow better:
 
489
             * ### timeout will be <= 2^32 - 1
 
490
             */
 
491
            expires = atol(val);
 
492
            now     = time(NULL);
 
493
            return now + expires;
 
494
        }
 
495
    }
 
496
 
 
497
    return DAV_TIMEOUT_INFINITE;
 
498
}
 
499
 
 
500
/* ---------------------------------------------------------------
 
501
**
 
502
** If Header processing
 
503
**
 
504
*/
 
505
 
 
506
/* add_if_resource returns a new if_header, linking it to next_ih.
 
507
 */
 
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)
 
510
{
 
511
    dav_if_header *ih;
 
512
 
 
513
    if ((ih = apr_pcalloc(p, sizeof(*ih))) == NULL)
 
514
        return NULL;
 
515
 
 
516
    ih->uri = uri;
 
517
    ih->uri_len = uri_len;
 
518
    ih->next = next_ih;
 
519
 
 
520
    return ih;
 
521
}
 
522
 
 
523
/* add_if_state adds a condition to an if_header.
 
524
 */
 
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)
 
529
{
 
530
    dav_if_state_list *new_sl;
 
531
 
 
532
    new_sl = apr_pcalloc(p, sizeof(*new_sl));
 
533
 
 
534
    new_sl->condition = condition;
 
535
    new_sl->type      = t;
 
536
 
 
537
    if (t == dav_if_opaquelock) {
 
538
        dav_error *err;
 
539
 
 
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;
 
547
            }
 
548
            else {
 
549
                /* ### maybe add a higher-level description */
 
550
                return err;
 
551
            }
 
552
        }
 
553
    }
 
554
    else
 
555
        new_sl->etag = state_token;
 
556
 
 
557
    new_sl->next = ih->state;
 
558
    ih->state = new_sl;
 
559
 
 
560
    return NULL;
 
561
}
 
562
 
 
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.
 
566
 */
 
567
static char *dav_fetch_next_token(char **str, char term)
 
568
{
 
569
    char *sp;
 
570
    char *token;
 
571
 
 
572
    token = *str + 1;
 
573
 
 
574
    while (*token && (*token == ' ' || *token == '\t'))
 
575
        token++;
 
576
 
 
577
    if ((sp = strchr(token, term)) == NULL)
 
578
        return NULL;
 
579
 
 
580
    *sp = '\0';
 
581
    *str = sp;
 
582
    return token;
 
583
}
 
584
 
 
585
/* dav_process_if_header:
 
586
 *
 
587
 *   If NULL (no error) is returned, then **if_header points to the
 
588
 *   "If" productions structure (or NULL if "If" is not present).
 
589
 *
 
590
 *   ### this part is bogus:
 
591
 *   If an error is encountered, the error is logged.  Parent should
 
592
 *   return err->status.
 
593
 */
 
594
static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
 
595
{
 
596
    dav_error *err;
 
597
    char *str;
 
598
    char *list;
 
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;
 
606
    int condition;
 
607
 
 
608
    *p_ih = NULL;
 
609
 
 
610
    if ((str = apr_pstrdup(r->pool, apr_table_get(r->headers_in, "If"))) == NULL)
 
611
        return NULL;
 
612
 
 
613
    while (*str) {
 
614
        switch(*str) {
 
615
        case '<':
 
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,
 
620
                                     DAV_ERR_IF_TAGGED,
 
621
                                     "Invalid If-header: unclosed \"<\" or "
 
622
                                     "unexpected tagged-list production.");
 
623
            }
 
624
 
 
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,
 
629
                                     DAV_ERR_IF_TAGGED,
 
630
                                     "Invalid URI in tagged If-header.");
 
631
            }
 
632
            /* note that parsed_uri.path is allocated; we can trash it */
 
633
 
 
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';
 
639
 
 
640
            uri = parsed_uri.path;
 
641
            list_type = tagged;
 
642
            break;
 
643
 
 
644
        case '(':
 
645
            /* List production */
 
646
 
 
647
            /* If a uri has not been encountered, this is a No-Tagged-List */
 
648
            if (list_type == unknown)
 
649
                list_type = no_tagged;
 
650
 
 
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 \"(\".");
 
655
            }
 
656
 
 
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,
 
660
                                     DAV_ERR_IF_PARSE,
 
661
                                     "Internal server error parsing \"If:\" "
 
662
                                     "header.");
 
663
            }
 
664
 
 
665
            condition = DAV_IF_COND_NORMAL;
 
666
 
 
667
            while (*list) {
 
668
                /* List is the entire production (in a uri scope) */
 
669
 
 
670
                switch (*list) {
 
671
                case '<':
 
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);
 
676
                    }
 
677
 
 
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 */
 
681
                        return err;
 
682
                    }
 
683
                    condition = DAV_IF_COND_NORMAL;
 
684
                    break;
 
685
 
 
686
                case '[':
 
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);
 
691
                    }
 
692
 
 
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 */
 
696
                        return err;
 
697
                    }
 
698
                    condition = DAV_IF_COND_NORMAL;
 
699
                    break;
 
700
 
 
701
                case 'N':
 
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.");
 
709
                        }
 
710
                        condition = DAV_IF_COND_NOT;
 
711
                    }
 
712
                    list += 2;
 
713
                    break;
 
714
 
 
715
                case ' ':
 
716
                case '\t':
 
717
                    break;
 
718
 
 
719
                default:
 
720
                    return dav_new_error(r->pool, HTTP_BAD_REQUEST,
 
721
                                         DAV_ERR_IF_UNK_CHAR,
 
722
                                         apr_psprintf(r->pool,
 
723
                                                     "Invalid \"If:\" "
 
724
                                                     "header: Unexpected "
 
725
                                                     "character encountered "
 
726
                                                     "(0x%02x, '%c').",
 
727
                                                     *list, *list));
 
728
                }
 
729
 
 
730
                list++;
 
731
            }
 
732
            break;
 
733
 
 
734
        case ' ':
 
735
        case '\t':
 
736
            break;
 
737
 
 
738
        default:
 
739
            return dav_new_error(r->pool, HTTP_BAD_REQUEST,
 
740
                                 DAV_ERR_IF_UNK_CHAR,
 
741
                                 apr_psprintf(r->pool,
 
742
                                             "Invalid \"If:\" header: "
 
743
                                             "Unexpected character "
 
744
                                             "encountered (0x%02x, '%c').",
 
745
                                             *str, *str));
 
746
        }
 
747
 
 
748
        str++;
 
749
    }
 
750
 
 
751
    *p_ih = ih;
 
752
    return NULL;
 
753
}
 
754
 
 
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)
 
758
{
 
759
    for (; if_header != NULL; if_header = if_header->next) {
 
760
        const dav_if_state_list *state_list;
 
761
 
 
762
        for (state_list = if_header->state;
 
763
             state_list != NULL;
 
764
             state_list = state_list->next) {
 
765
 
 
766
            if (state_list->type == dav_if_opaquelock) {
 
767
                const dav_lock *lock;
 
768
 
 
769
                /* given state_list->locktoken, match it */
 
770
 
 
771
                /*
 
772
                ** The resource will have one or more lock tokens. We only
 
773
                ** need to match one of them against any token in the
 
774
                ** If: header.
 
775
                **
 
776
                ** One token case: It is an exclusive or shared lock. Either
 
777
                **                 way, we must find it.
 
778
                **
 
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)
 
783
                */
 
784
                for (lock = lock_list; lock != NULL; lock = lock->next) {
 
785
 
 
786
                    if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
 
787
                        return 1;
 
788
                    }
 
789
                }
 
790
            }
 
791
        }
 
792
    }
 
793
 
 
794
    return 0;
 
795
}
 
796
 
 
797
/* dav_validate_resource_state:
 
798
 *    Returns NULL if path/uri meets if-header and lock requirements
 
799
 */
 
800
static dav_error * dav_validate_resource_state(apr_pool_t *p,
 
801
                                               const dav_resource *resource,
 
802
                                               dav_lockdb *lockdb,
 
803
                                               const dav_if_header *if_header,
 
804
                                               int flags,
 
805
                                               dav_buffer *pbuf,
 
806
                                               request_rec *r)
 
807
{
 
808
    dav_error *err;
 
809
    const char *uri;
 
810
    const char *etag;
 
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;
 
814
    dav_lock *lock_list;
 
815
    dav_lock *lock;
 
816
    int num_matched;
 
817
    int num_that_apply;
 
818
    int seen_locktoken;
 
819
    apr_size_t uri_len;
 
820
    const char *reason = NULL;
 
821
 
 
822
    /* DBG1("validate: <%s>", resource->uri); */
 
823
 
 
824
    /*
 
825
    ** The resource will have one of three states:
 
826
    **
 
827
    ** 1) No locks. We have no special requirements that the user supply
 
828
    **    specific locktokens. One of the state lists must match, and
 
829
    **    we're done.
 
830
    **
 
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
 
835
    **    done.
 
836
    **
 
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.
 
840
    **
 
841
    ** The <seen_locktoken> variable determines whether we have seen one
 
842
    ** of this resource's locktokens in the If: header.
 
843
    */
 
844
 
 
845
    /*
 
846
    ** If this is a new lock request, <flags> will contain the requested
 
847
    ** lock scope.  Three rules apply:
 
848
    **
 
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.
 
853
    */
 
854
 
 
855
    if (lockdb == NULL) {
 
856
        /* we're in State 1. no locks. */
 
857
        lock_list = NULL;
 
858
    }
 
859
    else {
 
860
        /*
 
861
        ** ### hrm... we don't need to have these fully
 
862
        ** ### resolved since we're only looking at the
 
863
        ** ### locktokens...
 
864
        **
 
865
        ** ### use get_locks w/ calltype=PARTIAL
 
866
        */
 
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:\" "
 
872
                                  "header.",
 
873
                                  err);
 
874
        }
 
875
 
 
876
        /* lock_list now determines whether we're in State 1, 2, or 3. */
 
877
    }
 
878
 
 
879
    /*
 
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.
 
883
    */
 
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.");
 
889
        }
 
890
 
 
891
        /*
 
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.
 
894
        */
 
895
        seen_locktoken = 1;
 
896
    }
 
897
    else if (flags & DAV_LOCKSCOPE_SHARED) {
 
898
        /*
 
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.
 
901
        */
 
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.");
 
907
            }
 
908
        }
 
909
 
 
910
        /*
 
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.
 
914
        */
 
915
        seen_locktoken = 1;
 
916
    }
 
917
    else {
 
918
        /*
 
919
        ** For methods other than LOCK:
 
920
        **
 
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.
 
924
        **
 
925
        ** Otherwise, it must be cleared and we'll look for one.
 
926
        */
 
927
        seen_locktoken = (lock_list == NULL);
 
928
    }
 
929
 
 
930
    /*
 
931
    ** If there is no If: header, then we can shortcut some logic:
 
932
    **
 
933
    ** 1) if we do not need to find a locktoken in the (non-existent) If:
 
934
    **    header, then we are successful.
 
935
    **
 
936
    ** 2) if we must find a locktoken in the (non-existent) If: header, then
 
937
    **    we fail.
 
938
    */
 
939
    if (if_header == NULL) {
 
940
        if (seen_locktoken)
 
941
            return NULL;
 
942
 
 
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 "
 
946
                             "resource.");
 
947
    }
 
948
    /* the If: header is present */
 
949
 
 
950
    /*
 
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.
 
954
    **
 
955
    ** This is a 400 (Bad Request) since they should only submit a locktoken
 
956
    ** that actually exists.
 
957
    **
 
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)
 
963
    */
 
964
    if (lock_list == NULL && if_header->dummy_header) {
 
965
        if (flags & DAV_VALIDATE_IS_PARENT)
 
966
            return NULL;
 
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.");
 
971
    }
 
972
 
 
973
    /*
 
974
    ** Prepare the input URI. We want the URI to never have a trailing slash.
 
975
    **
 
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
 
979
    ** still equivalent.
 
980
    **
 
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.
 
984
    */
 
985
    uri = resource->uri;
 
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';
 
991
        uri = pbuf->buf;
 
992
    }
 
993
 
 
994
    /* get the resource's etag; we may need it during the checks */
 
995
    etag = (*resource->hooks->getetag)(resource);
 
996
 
 
997
    /* how many state_lists apply to this URI? */
 
998
    num_that_apply = 0;
 
999
 
 
1000
    /* If there are if-headers, fail if this resource
 
1001
     * does not match at least one state_list.
 
1002
     */
 
1003
    for (ifhdr_scan = if_header;
 
1004
         ifhdr_scan != NULL;
 
1005
         ifhdr_scan = ifhdr_scan->next) {
 
1006
 
 
1007
        /* DBG2("uri=<%s>  if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
 
1008
 
 
1009
        if (ifhdr_scan->uri != NULL
 
1010
            && (uri_len != ifhdr_scan->uri_len
 
1011
                || memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
 
1012
            /*
 
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.
 
1015
            */
 
1016
            continue;
 
1017
        }
 
1018
 
 
1019
        /* this state_list applies to this resource */
 
1020
 
 
1021
        /*
 
1022
        ** ### only one state_list should ever apply! a no-tag, or a tagged
 
1023
        ** ### where S9.4.2 states only one can match.
 
1024
        **
 
1025
        ** ### revamp this code to loop thru ifhdr_scan until we find the
 
1026
        ** ### matching state_list. process it. stop.
 
1027
        */
 
1028
        ++num_that_apply;
 
1029
 
 
1030
        /* To succeed, resource must match *all* of the states
 
1031
         * specified in the state_list.
 
1032
         */
 
1033
        for (state_list = ifhdr_scan->state;
 
1034
             state_list != NULL;
 
1035
             state_list = state_list->next) {
 
1036
 
 
1037
            switch(state_list->type) {
 
1038
            case dav_if_etag:
 
1039
            {
 
1040
                const char *given_etag, *current_etag;
 
1041
                int mismatch;
 
1042
 
 
1043
                /* Do a weak entity comparison function as defined in
 
1044
                 * RFC 2616 13.3.3.
 
1045
                 */
 
1046
                if (state_list->etag[0] == 'W' &&
 
1047
                    state_list->etag[1] == '/') {
 
1048
                    given_etag = state_list->etag + 2;
 
1049
                }
 
1050
                else {
 
1051
                    given_etag = state_list->etag;
 
1052
                }
 
1053
                if (etag[0] == 'W' &&
 
1054
                    etag[1] == '/') {
 
1055
                    current_etag = etag + 2;
 
1056
                }
 
1057
                else {
 
1058
                    current_etag = etag;
 
1059
                }
 
1060
 
 
1061
                mismatch = strcmp(given_etag, current_etag);
 
1062
 
 
1063
                if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
 
1064
                    /*
 
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.
 
1068
                    */
 
1069
                    reason =
 
1070
                        "an entity-tag was specified, but the resource's "
 
1071
                        "actual ETag does not match.";
 
1072
                    goto state_list_failed;
 
1073
                }
 
1074
                else if (state_list->condition == DAV_IF_COND_NOT
 
1075
                         && !mismatch) {
 
1076
                    /*
 
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.
 
1080
                    */
 
1081
                    reason =
 
1082
                        "an entity-tag was specified using the \"Not\" form, "
 
1083
                        "but the resource's actual ETag matches the provided "
 
1084
                        "entity-tag.";
 
1085
                    goto state_list_failed;
 
1086
                }
 
1087
                break;
 
1088
            }
 
1089
 
 
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) */
 
1094
                        continue;
 
1095
                    }
 
1096
 
 
1097
                    /* condition == DAV_IF_COND_NORMAL */
 
1098
 
 
1099
                    /*
 
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).
 
1104
                    **
 
1105
                    ** Go and try the next state list.
 
1106
                    */
 
1107
                    reason =
 
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;
 
1111
                }
 
1112
 
 
1113
                /* Resource validation 'fails' if:
 
1114
                 *    ANY  of the lock->locktokens match
 
1115
                 *         a NOT state_list->locktoken,
 
1116
                 * OR
 
1117
                 *    NONE of the lock->locktokens match
 
1118
                 *         a NORMAL state_list->locktoken.
 
1119
                 */
 
1120
                num_matched = 0;
 
1121
                for (lock = lock_list; lock != NULL; lock = lock->next) {
 
1122
 
 
1123
                    /*
 
1124
                    DBG2("compare: rsrc=%s  ifhdr=%s",
 
1125
                         (*locks_hooks->format_locktoken)(p, lock->locktoken),
 
1126
                         (*locks_hooks->format_locktoken)(p, state_list->locktoken));
 
1127
                    */
 
1128
 
 
1129
                    /* nothing to do if the locktokens do not match. */
 
1130
                    if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
 
1131
                        continue;
 
1132
                    }
 
1133
 
 
1134
                    /*
 
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
 
1139
                    ** locktokens.
 
1140
                    */
 
1141
                    seen_locktoken = 1;
 
1142
 
 
1143
                    if (state_list->condition == DAV_IF_COND_NOT) {
 
1144
                        /*
 
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.
 
1149
                        */
 
1150
                        reason =
 
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;
 
1155
                    }
 
1156
 
 
1157
                    /* condition == DAV_IF_COND_NORMAL */
 
1158
 
 
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.
 
1162
                    */
 
1163
                    if (lock->auth_user &&
 
1164
                        (!r->user ||
 
1165
                         strcmp(lock->auth_user, r->user))) {
 
1166
                        const char *errmsg;
 
1167
 
 
1168
                        errmsg = apr_pstrcat(p, "User \"",
 
1169
                                            r->user,
 
1170
                                            "\" submitted a locktoken created "
 
1171
                                            "by user \"",
 
1172
                                            lock->auth_user, "\".", NULL);
 
1173
                        return dav_new_error(p, HTTP_FORBIDDEN, 0, errmsg);
 
1174
                    }
 
1175
 
 
1176
                    /*
 
1177
                    ** We just matched a specified State-Token to one of the
 
1178
                    ** resource's locktokens.
 
1179
                    **
 
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).
 
1183
                    */
 
1184
                    num_matched = 1;
 
1185
                    break;
 
1186
                }
 
1187
 
 
1188
                if (num_matched == 0
 
1189
                    && state_list->condition == DAV_IF_COND_NORMAL) {
 
1190
                    /*
 
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.
 
1194
                    */
 
1195
                    reason =
 
1196
                        "a State-token was supplied, but it was not found "
 
1197
                        "in the locks on this resource.";
 
1198
                    goto state_list_failed;
 
1199
                }
 
1200
 
 
1201
                break;
 
1202
 
 
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. */
 
1207
 
 
1208
                if (state_list->condition == DAV_IF_COND_NORMAL) {
 
1209
                    reason =
 
1210
                        "an unknown state token was supplied";
 
1211
                    goto state_list_failed;
 
1212
                }
 
1213
                break;
 
1214
 
 
1215
            } /* switch */
 
1216
        } /* foreach ( state_list ) */
 
1217
 
 
1218
        /*
 
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.
 
1222
        **
 
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.
 
1226
        */
 
1227
        if (seen_locktoken) {
 
1228
            /* woo hoo! */
 
1229
            return NULL;
 
1230
        }
 
1231
 
 
1232
        /*
 
1233
        ** Haven't seen one. Let's break out of the search and just look
 
1234
        ** for a matching locktoken.
 
1235
        */
 
1236
        break;
 
1237
 
 
1238
        /*
 
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
 
1241
        ** state_list.
 
1242
        */
 
1243
      state_list_failed:
 
1244
        ;
 
1245
 
 
1246
    } /* foreach ( ifhdr_scan ) */
 
1247
 
 
1248
    /*
 
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
 
1252
    */
 
1253
 
 
1254
    if (ifhdr_scan == NULL) {
 
1255
        /*
 
1256
        ** We finished the loop without finding any matching state lists.
 
1257
        */
 
1258
 
 
1259
        /*
 
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.
 
1264
        **
 
1265
        ** S9.4.2 states that when no state_lists apply, then the header
 
1266
        ** should be ignored.
 
1267
        **
 
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.
 
1270
        */
 
1271
        if (num_that_apply == 0) {
 
1272
            if (seen_locktoken)
 
1273
                return NULL;
 
1274
 
 
1275
            /*
 
1276
            ** We may have aborted the scan before seeing the locktoken.
 
1277
            ** Rescan the If: header to see if we can find the locktoken
 
1278
            ** somewhere.
 
1279
            **
 
1280
            ** Note that seen_locktoken == 0 implies lock_list != NULL
 
1281
            ** which implies locks_hooks != NULL.
 
1282
            */
 
1283
            if (dav_find_submitted_locktoken(if_header, lock_list,
 
1284
                                             locks_hooks)) {
 
1285
                /*
 
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.
 
1289
                */
 
1290
                return NULL;
 
1291
            }
 
1292
 
 
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).");
 
1297
        }
 
1298
        /* else: one or more state_lists were applicable, but failed. */
 
1299
 
 
1300
        /*
 
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).
 
1305
        */
 
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).");
 
1311
        }
 
1312
 
 
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.");
 
1317
        }
 
1318
 
 
1319
        return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
 
1320
                             apr_psprintf(p,
 
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));
 
1325
    }
 
1326
 
 
1327
    /* assert seen_locktoken == 0 */
 
1328
 
 
1329
    /*
 
1330
    ** ifhdr_scan != NULL implies we found a matching state_list.
 
1331
    **
 
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.
 
1334
    **
 
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
 
1338
    ** matching token.
 
1339
    **
 
1340
    ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
 
1341
    ** locks_hooks != NULL.
 
1342
    */
 
1343
    if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
 
1344
        /*
 
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.
 
1347
        */
 
1348
        return NULL;
 
1349
    }
 
1350
 
 
1351
    /*
 
1352
    ** We had a matching state list, but the user agent did not specify one
 
1353
    ** of this resource's locktokens. Tell them so.
 
1354
    **
 
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)
 
1360
    **
 
1361
    ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
 
1362
    */
 
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).");
 
1368
    }
 
1369
 
 
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).");
 
1374
}
 
1375
 
 
1376
/* dav_validate_walker:  Walker callback function to validate resource state */
 
1377
static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
 
1378
{
 
1379
    dav_walker_ctx *ctx = wres->walk_ctx;
 
1380
    dav_error *err;
 
1381
 
 
1382
    if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
 
1383
                                           ctx->w.lockdb,
 
1384
                                           ctx->if_header, ctx->flags,
 
1385
                                           &ctx->work_buf, ctx->r)) == NULL) {
 
1386
        /* There was no error, so just bug out. */
 
1387
        return NULL;
 
1388
    }
 
1389
 
 
1390
    /*
 
1391
    ** If we have a serious server error, or if the request itself failed,
 
1392
    ** then just return error (not a multistatus).
 
1393
    */
 
1394
    if (ap_is_HTTP_SERVER_ERROR(err->status)
 
1395
        || (*wres->resource->hooks->is_same_resource)(wres->resource,
 
1396
                                                      ctx->w.root)) {
 
1397
        /* ### maybe push a higher-level description? */
 
1398
        return err;
 
1399
    }
 
1400
 
 
1401
    /* associate the error with the current URI */
 
1402
    dav_add_response(wres, err->status, NULL);
 
1403
 
 
1404
    return NULL;
 
1405
}
 
1406
 
 
1407
/*
 
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
 
1411
**
 
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
 
1414
** state is null).
 
1415
**
 
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.
 
1419
*/
 
1420
DAV_DECLARE(dav_error *) dav_validate_request(request_rec *r,
 
1421
                                              dav_resource *resource,
 
1422
                                              int depth,
 
1423
                                              dav_locktoken *locktoken,
 
1424
                                              dav_response **response,
 
1425
                                              int flags,
 
1426
                                              dav_lockdb *lockdb)
 
1427
{
 
1428
    dav_error *err;
 
1429
    int result;
 
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;
 
1436
 
 
1437
#if DAV_DEBUG
 
1438
    if (depth && response == NULL) {
 
1439
        /*
 
1440
        ** ### bleck. we can't return errors for other URIs unless we have
 
1441
        ** ### a "response" ptr.
 
1442
        */
 
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.");
 
1446
    }
 
1447
#endif
 
1448
 
 
1449
    if (response != NULL)
 
1450
        *response = NULL;
 
1451
 
 
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);
 
1457
    }
 
1458
 
 
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 */
 
1462
        return err;
 
1463
    }
 
1464
 
 
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.
 
1469
     */
 
1470
    if (locktoken != NULL) {
 
1471
        dav_if_header *ifhdr_new;
 
1472
 
 
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;
 
1477
 
 
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;
 
1482
 
 
1483
        ifhdr_new->next = if_header;
 
1484
        if_header = ifhdr_new;
 
1485
    }
 
1486
 
 
1487
    /*
 
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.
 
1491
    */
 
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 */
 
1496
                return err;
 
1497
            }
 
1498
            lock_db_opened_locally = 1;
 
1499
        }
 
1500
    }
 
1501
 
 
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;
 
1506
 
 
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;
 
1512
 
 
1513
        ctx.if_header = if_header;
 
1514
        ctx.r = r;
 
1515
        ctx.flags = flags;
 
1516
 
 
1517
        if (lockdb != NULL) {
 
1518
            ctx.w.lockdb = lockdb;
 
1519
            ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
 
1520
        }
 
1521
 
 
1522
        err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
 
1523
        if (err == NULL) {
 
1524
            *response = multi_status;;
 
1525
        }
 
1526
        /* else: implies a 5xx status code occurred. */
 
1527
    }
 
1528
    else {
 
1529
        err = dav_validate_resource_state(r->pool, resource, lockdb,
 
1530
                                          if_header, flags, &work_buf, r);
 
1531
    }
 
1532
 
 
1533
    /* (2) Validate the parent resource if requested */
 
1534
    if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
 
1535
        dav_resource *parent_resource;
 
1536
 
 
1537
        err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
 
1538
 
 
1539
        if (err == NULL && parent_resource == NULL) {
 
1540
            err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
 
1541
                                "Cannot access parent of repository root.");
 
1542
        }
 
1543
        else if (err == NULL) {
 
1544
            err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
 
1545
                                              if_header,
 
1546
                                              flags | DAV_VALIDATE_IS_PARENT,
 
1547
                                              &work_buf, r);
 
1548
 
 
1549
            /*
 
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.
 
1554
            */
 
1555
            if (err != NULL) {
 
1556
                new_response = apr_pcalloc(r->pool, sizeof(*new_response));
 
1557
 
 
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 "
 
1563
                    "the Request-URI.";
 
1564
                if (err->desc != NULL) {
 
1565
                    new_response->desc = apr_pstrcat(r->pool,
 
1566
                                                    new_response->desc,
 
1567
                                                    " The error was: ",
 
1568
                                                    err->desc, NULL);
 
1569
                }
 
1570
 
 
1571
                /* assert: DAV_VALIDATE_PARENT implies response != NULL */
 
1572
                new_response->next = *response;
 
1573
                *response = new_response;
 
1574
 
 
1575
                err = NULL;
 
1576
            }
 
1577
        }
 
1578
    }
 
1579
 
 
1580
    if (lock_db_opened_locally)
 
1581
        (*locks_hooks->close_lockdb)(lockdb);
 
1582
 
 
1583
    /*
 
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.
 
1587
    **
 
1588
    ** For certain methods, the overall error will be a 424. The default is
 
1589
    ** to construct a standard 207 response.
 
1590
    */
 
1591
    if (err == NULL && response != NULL && *response != NULL) {
 
1592
        apr_text *propstat = NULL;
 
1593
 
 
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 "
 
1599
                                 "this resource.");
 
1600
        }
 
1601
 
 
1602
        /*
 
1603
        ** Whatever caused the error, the Request-URI should have a 424
 
1604
        ** associated with it since we cannot complete the method.
 
1605
        **
 
1606
        ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
 
1607
        ** For other methods, return a simple 424.
 
1608
        */
 
1609
        if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
 
1610
            propstat = apr_pcalloc(r->pool, sizeof(*propstat));
 
1611
            propstat->text =
 
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;
 
1616
        }
 
1617
 
 
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.";
 
1626
 
 
1627
        new_response->next = *response;
 
1628
        *response = new_response;
 
1629
 
 
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.");
 
1634
    }
 
1635
 
 
1636
    return err;
 
1637
}
 
1638
 
 
1639
/* dav_get_locktoken_list:
 
1640
 *
 
1641
 * Sets ltl to a locktoken_list of all positive locktokens in header,
 
1642
 * else NULL if no If-header, or no positive locktokens.
 
1643
 */
 
1644
DAV_DECLARE(dav_error *) dav_get_locktoken_list(request_rec *r,
 
1645
                                                dav_locktoken_list **ltl)
 
1646
{
 
1647
    dav_error *err;
 
1648
    dav_if_header *if_header;
 
1649
    dav_if_state_list *if_state;
 
1650
    dav_locktoken_list *lock_token = NULL;
 
1651
 
 
1652
    *ltl = NULL;
 
1653
 
 
1654
    if ((err = dav_process_if_header(r, &if_header)) != NULL) {
 
1655
        /* ### add a higher-level description? */
 
1656
        return err;
 
1657
    }
 
1658
 
 
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;
 
1667
                *ltl = lock_token;
 
1668
            }
 
1669
            if_state = if_state->next;
 
1670
        }
 
1671
        if_header = if_header->next;
 
1672
    }
 
1673
    if (*ltl == NULL) {
 
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.");
 
1678
    }
 
1679
 
 
1680
    return NULL;
 
1681
}
 
1682
 
 
1683
#if 0 /* not needed right now... */
 
1684
 
 
1685
static const char *strip_white(const char *s, apr_pool_t *pool)
 
1686
{
 
1687
    apr_size_t idx;
 
1688
 
 
1689
    /* trim leading whitespace */
 
1690
    while (apr_isspace(*s))     /* assume: return false for '\0' */
 
1691
        ++s;
 
1692
 
 
1693
    /* trim trailing whitespace */
 
1694
    idx = strlen(s) - 1;
 
1695
    if (apr_isspace(s[idx])) {
 
1696
        char *s2 = apr_pstrdup(pool, s);
 
1697
 
 
1698
        while (apr_isspace(s2[idx]) && idx > 0)
 
1699
            --idx;
 
1700
        s2[idx + 1] = '\0';
 
1701
        return s2;
 
1702
    }
 
1703
 
 
1704
    return s;
 
1705
}
 
1706
#endif
 
1707
 
 
1708
#define DAV_LABEL_HDR "Label"
 
1709
 
 
1710
/* dav_add_vary_header
 
1711
 *
 
1712
 * If there were any headers in the request which require a Vary header
 
1713
 * in the response, add it.
 
1714
 */
 
1715
DAV_DECLARE(void) dav_add_vary_header(request_rec *in_req,
 
1716
                                      request_rec *out_req,
 
1717
                                      const dav_resource *resource)
 
1718
{
 
1719
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
 
1720
 
 
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 */
 
1723
 
 
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");
 
1729
 
 
1730
        /* If Target-Selector specified, add it to the Vary header */
 
1731
        if (target != NULL) {
 
1732
            if (vary == NULL)
 
1733
                vary = DAV_LABEL_HDR;
 
1734
            else
 
1735
                vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
 
1736
                                   NULL);
 
1737
 
 
1738
            apr_table_setn(out_req->headers_out, "Vary", vary);
 
1739
        }
 
1740
    }
 
1741
}
 
1742
 
 
1743
/* dav_can_auto_checkout
 
1744
 *
 
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
 
1751
 */
 
1752
static dav_error * dav_can_auto_checkout(
 
1753
    request_rec *r,
 
1754
    dav_resource *resource,
 
1755
    dav_auto_version auto_version,
 
1756
    dav_lockdb **lockdb,
 
1757
    int *auto_checkout)
 
1758
{
 
1759
    dav_error *err;
 
1760
    dav_lock *lock_list;
 
1761
 
 
1762
    *auto_checkout = 0;
 
1763
 
 
1764
    if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
 
1765
        *auto_checkout = 1;
 
1766
    }
 
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);
 
1770
 
 
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.");
 
1775
            }
 
1776
 
 
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.",
 
1781
                                      err);
 
1782
            }
 
1783
        }
 
1784
 
 
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.",
 
1790
                                  err);
 
1791
        }
 
1792
 
 
1793
        if (lock_list != NULL)
 
1794
            *auto_checkout = 1;
 
1795
    }
 
1796
 
 
1797
    return NULL;
 
1798
}
 
1799
 
 
1800
/* see mod_dav.h for docco */
 
1801
DAV_DECLARE(dav_error *) dav_auto_checkout(
 
1802
    request_rec *r,
 
1803
    dav_resource *resource,
 
1804
    int parent_only,
 
1805
    dav_auto_version_info *av_info)
 
1806
{
 
1807
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
1808
    dav_lockdb *lockdb = NULL;
 
1809
    dav_error *err = NULL;
 
1810
 
 
1811
    /* Initialize results */
 
1812
    memset(av_info, 0, sizeof(*av_info));
 
1813
 
 
1814
    /* if no versioning provider, just return */
 
1815
    if (vsn_hooks == NULL)
 
1816
        return NULL;
 
1817
 
 
1818
    /* check parent resource if requested or if resource must be created */
 
1819
    if (!resource->exists || parent_only) {
 
1820
        dav_resource *parent;
 
1821
 
 
1822
        if ((err = (*resource->hooks->get_parent_resource)(resource,
 
1823
                                                           &parent)) != NULL)
 
1824
            goto done;
 
1825
 
 
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)));
 
1832
            goto done;
 
1833
        }
 
1834
 
 
1835
        av_info->parent_resource = parent;
 
1836
 
 
1837
        /* if parent versioned and not checked out, see if it can be */
 
1838
        if (parent->versioned && !parent->working) {
 
1839
            int checkout_parent;
 
1840
 
 
1841
            if ((err = dav_can_auto_checkout(r, parent,
 
1842
                                             (*vsn_hooks->auto_versionable)(parent),
 
1843
                                             &lockdb, &checkout_parent))
 
1844
                != NULL) {
 
1845
                goto done;
 
1846
            }
 
1847
 
 
1848
            if (!checkout_parent) {
 
1849
                err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
 
1850
                                    "<DAV:cannot-modify-checked-in-parent>");
 
1851
                goto done;
 
1852
            }
 
1853
 
 
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.
 
1857
             */
 
1858
            if ((err = (*vsn_hooks->checkout)(parent, 1 /*auto_checkout*/,
 
1859
                                              0, 0, 0, NULL, NULL))
 
1860
                != NULL)
 
1861
            {
 
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)),
 
1867
                                     err);
 
1868
                goto done;
 
1869
            }
 
1870
 
 
1871
            /* remember that parent was checked out */
 
1872
            av_info->parent_checkedout = 1;
 
1873
        }
 
1874
    }
 
1875
 
 
1876
    /* if only checking parent, we're done */
 
1877
    if (parent_only)
 
1878
        goto done;
 
1879
 
 
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) {
 
1883
 
 
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)),
 
1889
                                 err);
 
1890
            goto done;
 
1891
        }
 
1892
 
 
1893
        /* remember that resource was created */
 
1894
        av_info->resource_versioned = 1;
 
1895
    }
 
1896
 
 
1897
    /* if resource is versioned, make sure it is checked out */
 
1898
    if (resource->versioned && !resource->working) {
 
1899
        int checkout_resource;
 
1900
 
 
1901
        if ((err = dav_can_auto_checkout(r, resource,
 
1902
                                         (*vsn_hooks->auto_versionable)(resource),
 
1903
                                         &lockdb, &checkout_resource)) != NULL) {
 
1904
            goto done;
 
1905
        }
 
1906
 
 
1907
        if (!checkout_resource) {
 
1908
            err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
 
1909
                                "<DAV:cannot-modify-version-controlled-content>");
 
1910
            goto done;
 
1911
        }
 
1912
 
 
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))
 
1917
            != NULL)
 
1918
        {
 
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)),
 
1923
                                 err);
 
1924
            goto done;
 
1925
        }
 
1926
 
 
1927
        /* remember that resource was checked out */
 
1928
        av_info->resource_checkedout = 1;
 
1929
    }
 
1930
 
 
1931
done:
 
1932
 
 
1933
    /* make sure lock database is closed */
 
1934
    if (lockdb != NULL)
 
1935
        (*lockdb->hooks->close_lockdb)(lockdb);
 
1936
 
 
1937
    /* if an error occurred, undo any auto-versioning operations already done */
 
1938
    if (err != NULL) {
 
1939
        dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, av_info);
 
1940
        return err;
 
1941
    }
 
1942
 
 
1943
    return NULL;
 
1944
}
 
1945
 
 
1946
/* see mod_dav.h for docco */
 
1947
DAV_DECLARE(dav_error *) dav_auto_checkin(
 
1948
    request_rec *r,
 
1949
    dav_resource *resource,
 
1950
    int undo,
 
1951
    int unlock,
 
1952
    dav_auto_version_info *av_info)
 
1953
{
 
1954
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
1955
    dav_error *err = NULL;
 
1956
    dav_auto_version auto_version;
 
1957
 
 
1958
    /* If no versioning provider, this is a no-op */
 
1959
    if (vsn_hooks == NULL)
 
1960
        return NULL;
 
1961
 
 
1962
    /* If undoing auto-checkouts, then do uncheckouts */
 
1963
    if (undo) {
 
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 "
 
1970
                                                      "of resource %s.",
 
1971
                                                      ap_escape_html(r->pool, resource->uri)),
 
1972
                                          err);
 
1973
                }
 
1974
            }
 
1975
 
 
1976
            if (av_info->resource_versioned) {
 
1977
                dav_response *response;
 
1978
 
 
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 "
 
1985
                                                      "of resource %s.",
 
1986
                                                      ap_escape_html(r->pool, resource->uri)),
 
1987
                                          err);
 
1988
                }
 
1989
            }
 
1990
        }
 
1991
 
 
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)),
 
1999
                                      err);
 
2000
            }
 
2001
        }
 
2002
 
 
2003
        return NULL;
 
2004
    }
 
2005
 
 
2006
    /* If the resource was checked out, and auto-checkin is enabled,
 
2007
     * then check it in.
 
2008
     */
 
2009
    if (resource != NULL && resource->working
 
2010
        && (unlock || av_info->resource_checkedout)) {
 
2011
 
 
2012
        auto_version = (*vsn_hooks->auto_versionable)(resource);
 
2013
 
 
2014
        if (auto_version == DAV_AUTO_VERSION_ALWAYS ||
 
2015
            (unlock && (auto_version == DAV_AUTO_VERSION_LOCKED))) {
 
2016
 
 
2017
            if ((err = (*vsn_hooks->checkin)(resource,
 
2018
                                             0 /*keep_checked_out*/, NULL))
 
2019
                != 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)),
 
2024
                                      err);
 
2025
            }
 
2026
        }
 
2027
    }
 
2028
 
 
2029
    /* If parent resource was checked out, and auto-checkin is enabled,
 
2030
     * then check it in.
 
2031
     */
 
2032
    if (!unlock
 
2033
        && av_info->parent_checkedout
 
2034
        && av_info->parent_resource != NULL
 
2035
        && av_info->parent_resource->working) {
 
2036
 
 
2037
        auto_version = (*vsn_hooks->auto_versionable)(av_info->parent_resource);
 
2038
 
 
2039
        if (auto_version == DAV_AUTO_VERSION_ALWAYS) {
 
2040
            if ((err = (*vsn_hooks->checkin)(av_info->parent_resource,
 
2041
                                             0 /*keep_checked_out*/, NULL))
 
2042
                != 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)),
 
2047
                                                  err);
 
2048
            }
 
2049
        }
 
2050
    }
 
2051
 
 
2052
    return NULL;
 
2053
}