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

« back to all changes in this revision

Viewing changes to modules/dav/main/mod_dav.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
 *
 
20
 * This module is repository-independent. It depends on hooks provided by a
 
21
 * repository implementation.
 
22
 *
 
23
 * APACHE ISSUES:
 
24
 *   - within a DAV hierarchy, if an unknown method is used and we default
 
25
 *     to Apache's implementation, it sends back an OPTIONS with the wrong
 
26
 *     set of methods -- there is NO HOOK for us.
 
27
 *     therefore: we need to manually handle the HTTP_METHOD_NOT_ALLOWED
 
28
 *       and HTTP_NOT_IMPLEMENTED responses (not ap_send_error_response).
 
29
 *   - process_mkcol_body() had to dup code from ap_setup_client_block().
 
30
 *   - it would be nice to get status lines from Apache for arbitrary
 
31
 *     status codes
 
32
 *   - it would be nice to be able to extend Apache's set of response
 
33
 *     codes so that it doesn't return 500 when an unknown code is placed
 
34
 *     into r->status.
 
35
 *   - http_vhost functions should apply "const" to their params
 
36
 *
 
37
 * DESIGN NOTES:
 
38
 *   - For PROPFIND, we batch up the entire response in memory before
 
39
 *     sending it. We may want to reorganize around sending the information
 
40
 *     as we suck it in from the propdb. Alternatively, we should at least
 
41
 *     generate a total Content-Length if we're going to buffer in memory
 
42
 *     so that we can keep the connection open.
 
43
 */
 
44
 
 
45
#include "apr_strings.h"
 
46
#include "apr_lib.h"            /* for apr_is* */
 
47
 
 
48
#define APR_WANT_STRFUNC
 
49
#include "apr_want.h"
 
50
 
 
51
#include "httpd.h"
 
52
#include "http_config.h"
 
53
#include "http_core.h"
 
54
#include "http_log.h"
 
55
#include "http_main.h"
 
56
#include "http_protocol.h"
 
57
#include "http_request.h"
 
58
#include "util_script.h"
 
59
 
 
60
#include "mod_dav.h"
 
61
 
 
62
 
 
63
/* ### what is the best way to set this? */
 
64
#define DAV_DEFAULT_PROVIDER    "filesystem"
 
65
 
 
66
/* used to denote that mod_dav will be handling this request */
 
67
#define DAV_HANDLER_NAME "dav-handler"
 
68
 
 
69
enum {
 
70
    DAV_ENABLED_UNSET = 0,
 
71
    DAV_ENABLED_OFF,
 
72
    DAV_ENABLED_ON
 
73
};
 
74
 
 
75
/* per-dir configuration */
 
76
typedef struct {
 
77
    const char *provider_name;
 
78
    const dav_provider *provider;
 
79
    const char *dir;
 
80
    int locktimeout;
 
81
    int allow_depthinfinity;
 
82
 
 
83
} dav_dir_conf;
 
84
 
 
85
/* per-server configuration */
 
86
typedef struct {
 
87
    int unused;
 
88
 
 
89
} dav_server_conf;
 
90
 
 
91
#define DAV_INHERIT_VALUE(parent, child, field) \
 
92
                ((child)->field ? (child)->field : (parent)->field)
 
93
 
 
94
 
 
95
/* forward-declare for use in configuration lookup */
 
96
extern module DAV_DECLARE_DATA dav_module;
 
97
 
 
98
/* DAV methods */
 
99
enum {
 
100
    DAV_M_BIND = 0,
 
101
    DAV_M_SEARCH,
 
102
    DAV_M_LAST
 
103
};
 
104
static int dav_methods[DAV_M_LAST];
 
105
 
 
106
 
 
107
static int dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
 
108
                             server_rec *s)
 
109
{
 
110
    /* DBG0("dav_init_handler"); */
 
111
 
 
112
    /* Register DAV methods */
 
113
    dav_methods[DAV_M_BIND] = ap_method_register(p, "BIND");
 
114
    dav_methods[DAV_M_SEARCH] = ap_method_register(p, "SEARCH");
 
115
 
 
116
    ap_add_version_component(p, "DAV/2");
 
117
 
 
118
    return OK;
 
119
}
 
120
 
 
121
static void *dav_create_server_config(apr_pool_t *p, server_rec *s)
 
122
{
 
123
    dav_server_conf *newconf;
 
124
 
 
125
    newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
 
126
 
 
127
    /* ### this isn't used at the moment... */
 
128
 
 
129
    return newconf;
 
130
}
 
131
 
 
132
static void *dav_merge_server_config(apr_pool_t *p, void *base, void *overrides)
 
133
{
 
134
#if 0
 
135
    dav_server_conf *child = overrides;
 
136
#endif
 
137
    dav_server_conf *newconf;
 
138
 
 
139
    newconf = (dav_server_conf *)apr_pcalloc(p, sizeof(*newconf));
 
140
 
 
141
    /* ### nothing to merge right now... */
 
142
 
 
143
    return newconf;
 
144
}
 
145
 
 
146
static void *dav_create_dir_config(apr_pool_t *p, char *dir)
 
147
{
 
148
    /* NOTE: dir==NULL creates the default per-dir config */
 
149
 
 
150
    dav_dir_conf *conf;
 
151
 
 
152
    conf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*conf));
 
153
 
 
154
    /* clean up the directory to remove any trailing slash */
 
155
    if (dir != NULL) {
 
156
        char *d;
 
157
        apr_size_t l;
 
158
 
 
159
        d = apr_pstrdup(p, dir);
 
160
        l = strlen(d);
 
161
        if (l > 1 && d[l - 1] == '/')
 
162
            d[l - 1] = '\0';
 
163
        conf->dir = d;
 
164
    }
 
165
 
 
166
    return conf;
 
167
}
 
168
 
 
169
static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
 
170
{
 
171
    dav_dir_conf *parent = base;
 
172
    dav_dir_conf *child = overrides;
 
173
    dav_dir_conf *newconf = (dav_dir_conf *)apr_pcalloc(p, sizeof(*newconf));
 
174
 
 
175
    /* DBG3("dav_merge_dir_config: new=%08lx  base=%08lx  overrides=%08lx",
 
176
       (long)newconf, (long)base, (long)overrides); */
 
177
 
 
178
    newconf->provider_name = DAV_INHERIT_VALUE(parent, child, provider_name);
 
179
    newconf->provider = DAV_INHERIT_VALUE(parent, child, provider);
 
180
    if (parent->provider_name != NULL) {
 
181
        if (child->provider_name == NULL) {
 
182
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
 
183
                         "\"DAV Off\" cannot be used to turn off a subtree "
 
184
                         "of a DAV-enabled location.");
 
185
        }
 
186
        else if (strcasecmp(child->provider_name,
 
187
                            parent->provider_name) != 0) {
 
188
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
 
189
                         "A subtree cannot specify a different DAV provider "
 
190
                         "than its parent.");
 
191
        }
 
192
    }
 
193
 
 
194
    newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
 
195
    newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
 
196
    newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
 
197
                                                     allow_depthinfinity);
 
198
 
 
199
    return newconf;
 
200
}
 
201
 
 
202
static const dav_provider *dav_get_provider(request_rec *r)
 
203
{
 
204
    dav_dir_conf *conf;
 
205
 
 
206
    conf = ap_get_module_config(r->per_dir_config, &dav_module);
 
207
    /* assert: conf->provider_name != NULL
 
208
       (otherwise, DAV is disabled, and we wouldn't be here) */
 
209
 
 
210
    /* assert: conf->provider != NULL
 
211
       (checked when conf->provider_name is set) */
 
212
    return conf->provider;
 
213
}
 
214
 
 
215
DAV_DECLARE(const dav_hooks_locks *) dav_get_lock_hooks(request_rec *r)
 
216
{
 
217
    return dav_get_provider(r)->locks;
 
218
}
 
219
 
 
220
DAV_DECLARE(const dav_hooks_propdb *) dav_get_propdb_hooks(request_rec *r)
 
221
{
 
222
    return dav_get_provider(r)->propdb;
 
223
}
 
224
 
 
225
DAV_DECLARE(const dav_hooks_vsn *) dav_get_vsn_hooks(request_rec *r)
 
226
{
 
227
    return dav_get_provider(r)->vsn;
 
228
}
 
229
 
 
230
DAV_DECLARE(const dav_hooks_binding *) dav_get_binding_hooks(request_rec *r)
 
231
{
 
232
    return dav_get_provider(r)->binding;
 
233
}
 
234
 
 
235
DAV_DECLARE(const dav_hooks_search *) dav_get_search_hooks(request_rec *r)
 
236
{
 
237
    return dav_get_provider(r)->search;
 
238
}
 
239
 
 
240
/*
 
241
 * Command handler for the DAV directive, which is TAKE1.
 
242
 */
 
243
static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
 
244
{
 
245
    dav_dir_conf *conf = (dav_dir_conf *)config;
 
246
 
 
247
    if (strcasecmp(arg1, "on") == 0) {
 
248
        conf->provider_name = DAV_DEFAULT_PROVIDER;
 
249
    }
 
250
    else if (strcasecmp(arg1, "off") == 0) {
 
251
        conf->provider_name = NULL;
 
252
        conf->provider = NULL;
 
253
    }
 
254
    else {
 
255
        conf->provider_name = apr_pstrdup(cmd->pool, arg1);
 
256
    }
 
257
 
 
258
    if (conf->provider_name != NULL) {
 
259
        /* lookup and cache the actual provider now */
 
260
        conf->provider = dav_lookup_provider(conf->provider_name);
 
261
 
 
262
        if (conf->provider == NULL) {
 
263
            /* by the time they use it, the provider should be loaded and
 
264
               registered with us. */
 
265
            return apr_psprintf(cmd->pool,
 
266
                                "Unknown DAV provider: %s",
 
267
                                conf->provider_name);
 
268
        }
 
269
    }
 
270
 
 
271
    return NULL;
 
272
}
 
273
 
 
274
/*
 
275
 * Command handler for the DAVDepthInfinity directive, which is FLAG.
 
276
 */
 
277
static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
 
278
                                            int arg)
 
279
{
 
280
    dav_dir_conf *conf = (dav_dir_conf *)config;
 
281
 
 
282
    if (arg)
 
283
        conf->allow_depthinfinity = DAV_ENABLED_ON;
 
284
    else
 
285
        conf->allow_depthinfinity = DAV_ENABLED_OFF;
 
286
    return NULL;
 
287
}
 
288
 
 
289
/*
 
290
 * Command handler for DAVMinTimeout directive, which is TAKE1
 
291
 */
 
292
static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config,
 
293
                                         const char *arg1)
 
294
{
 
295
    dav_dir_conf *conf = (dav_dir_conf *)config;
 
296
 
 
297
    conf->locktimeout = atoi(arg1);
 
298
    if (conf->locktimeout < 0)
 
299
        return "DAVMinTimeout requires a non-negative integer.";
 
300
 
 
301
    return NULL;
 
302
}
 
303
 
 
304
/*
 
305
** dav_error_response()
 
306
**
 
307
** Send a nice response back to the user. In most cases, Apache doesn't
 
308
** allow us to provide details in the body about what happened. This
 
309
** function allows us to completely specify the response body.
 
310
**
 
311
** ### this function is not logging any errors! (e.g. the body)
 
312
*/
 
313
static int dav_error_response(request_rec *r, int status, const char *body)
 
314
{
 
315
    r->status = status;
 
316
 
 
317
    /* ### I really don't think this is needed; gotta test */
 
318
    r->status_line = ap_get_status_line(status);
 
319
 
 
320
    ap_set_content_type(r, "text/html");
 
321
 
 
322
    /* begin the response now... */
 
323
    ap_rvputs(r,
 
324
              DAV_RESPONSE_BODY_1,
 
325
              r->status_line,
 
326
              DAV_RESPONSE_BODY_2,
 
327
              &r->status_line[4],
 
328
              DAV_RESPONSE_BODY_3,
 
329
              body,
 
330
              DAV_RESPONSE_BODY_4,
 
331
              ap_psignature("<hr />\n", r),
 
332
              DAV_RESPONSE_BODY_5,
 
333
              NULL);
 
334
 
 
335
    /* the response has been sent. */
 
336
    /*
 
337
     * ### Use of DONE obviates logging..!
 
338
     */
 
339
    return DONE;
 
340
}
 
341
 
 
342
 
 
343
/*
 
344
 * Send a "standardized" error response based on the error's namespace & tag
 
345
 */
 
346
static int dav_error_response_tag(request_rec *r,
 
347
                                  dav_error *err)
 
348
{
 
349
    r->status = err->status;
 
350
 
 
351
    /* ### I really don't think this is needed; gotta test */
 
352
    r->status_line = ap_get_status_line(err->status);
 
353
 
 
354
    ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
 
355
 
 
356
    ap_rputs(DAV_XML_HEADER DEBUG_CR
 
357
             "<D:error xmlns:D=\"DAV:\"", r);
 
358
 
 
359
    if (err->desc != NULL) {
 
360
        /* ### should move this namespace somewhere (with the others!) */
 
361
        ap_rputs(" xmlns:m=\"http://apache.org/dav/xmlns\"", r);
 
362
    }
 
363
 
 
364
    if (err->namespace != NULL) {
 
365
        ap_rprintf(r,
 
366
                   " xmlns:C=\"%s\">" DEBUG_CR
 
367
                   "<C:%s/>" DEBUG_CR,
 
368
                   err->namespace, err->tagname);
 
369
    }
 
370
    else {
 
371
        ap_rprintf(r,
 
372
                   ">" DEBUG_CR
 
373
                   "<D:%s/>" DEBUG_CR, err->tagname);
 
374
    }
 
375
 
 
376
    /* here's our mod_dav specific tag: */
 
377
    if (err->desc != NULL) {
 
378
        ap_rprintf(r,
 
379
                   "<m:human-readable errcode=\"%d\">" DEBUG_CR
 
380
                   "%s" DEBUG_CR
 
381
                   "</m:human-readable>" DEBUG_CR,
 
382
                   err->error_id,
 
383
                   apr_xml_quote_string(r->pool, err->desc, 0));
 
384
    }
 
385
 
 
386
    ap_rputs("</D:error>" DEBUG_CR, r);
 
387
 
 
388
    /* the response has been sent. */
 
389
    /*
 
390
     * ### Use of DONE obviates logging..!
 
391
     */
 
392
    return DONE;
 
393
}
 
394
 
 
395
 
 
396
/*
 
397
 * Apache's URI escaping does not replace '&' since that is a valid character
 
398
 * in a URI (to form a query section). We must explicitly handle it so that
 
399
 * we can embed the URI into an XML document.
 
400
 */
 
401
static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri)
 
402
{
 
403
    const char *e_uri = ap_escape_uri(p, uri);
 
404
 
 
405
    /* check the easy case... */
 
406
    if (ap_strchr_c(e_uri, '&') == NULL)
 
407
        return e_uri;
 
408
 
 
409
    /* there was a '&', so more work is needed... sigh. */
 
410
 
 
411
    /*
 
412
     * Note: this is a teeny bit of overkill since we know there are no
 
413
     * '<' or '>' characters, but who cares.
 
414
     */
 
415
    return apr_xml_quote_string(p, e_uri, 0);
 
416
}
 
417
 
 
418
 
 
419
/* Write a complete RESPONSE object out as a <DAV:repsonse> xml
 
420
   element.  Data is sent into brigade BB, which is auto-flushed into
 
421
   OUTPUT filter stack.  Use POOL for any temporary allocations.
 
422
 
 
423
   [Presumably the <multistatus> tag has already been written;  this
 
424
   routine is shared by dav_send_multistatus and dav_stream_response.]
 
425
*/
 
426
static void dav_send_one_response(dav_response *response,
 
427
                                  apr_bucket_brigade *bb,
 
428
                                  ap_filter_t *output,
 
429
                                  apr_pool_t *pool)
 
430
{
 
431
    apr_text *t = NULL;
 
432
 
 
433
    if (response->propresult.xmlns == NULL) {
 
434
      ap_fputs(output, bb, "<D:response>");
 
435
    }
 
436
    else {
 
437
      ap_fputs(output, bb, "<D:response");
 
438
      for (t = response->propresult.xmlns; t; t = t->next) {
 
439
        ap_fputs(output, bb, t->text);
 
440
      }
 
441
      ap_fputc(output, bb, '>');
 
442
    }
 
443
 
 
444
    ap_fputstrs(output, bb,
 
445
                DEBUG_CR "<D:href>",
 
446
                dav_xml_escape_uri(pool, response->href),
 
447
                "</D:href>" DEBUG_CR,
 
448
                NULL);
 
449
 
 
450
    if (response->propresult.propstats == NULL) {
 
451
      /* use the Status-Line text from Apache.  Note, this will
 
452
       * default to 500 Internal Server Error if first->status
 
453
       * is not a known (or valid) status code.
 
454
       */
 
455
      ap_fputstrs(output, bb,
 
456
                  "<D:status>HTTP/1.1 ",
 
457
                  ap_get_status_line(response->status),
 
458
                  "</D:status>" DEBUG_CR,
 
459
                  NULL);
 
460
    }
 
461
    else {
 
462
      /* assume this includes <propstat> and is quoted properly */
 
463
      for (t = response->propresult.propstats; t; t = t->next) {
 
464
        ap_fputs(output, bb, t->text);
 
465
      }
 
466
    }
 
467
 
 
468
    if (response->desc != NULL) {
 
469
      /*
 
470
       * We supply the description, so we know it doesn't have to
 
471
       * have any escaping/encoding applied to it.
 
472
       */
 
473
      ap_fputstrs(output, bb,
 
474
                  "<D:responsedescription>",
 
475
                  response->desc,
 
476
                  "</D:responsedescription>" DEBUG_CR,
 
477
                  NULL);
 
478
    }
 
479
 
 
480
    ap_fputs(output, bb, "</D:response>" DEBUG_CR);
 
481
}
 
482
 
 
483
 
 
484
/* Factorized helper function: prep request_rec R for a multistatus
 
485
   response and write <multistatus> tag into BB, destined for
 
486
   R->output_filters.  Use xml NAMESPACES in initial tag, if
 
487
   non-NULL. */
 
488
static void dav_begin_multistatus(apr_bucket_brigade *bb,
 
489
                                  request_rec *r, int status,
 
490
                                  apr_array_header_t *namespaces)
 
491
{
 
492
    /* Set the correct status and Content-Type */
 
493
    r->status = status;
 
494
    ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
 
495
 
 
496
    /* Send the headers and actual multistatus response now... */
 
497
    ap_fputs(r->output_filters, bb, DAV_XML_HEADER DEBUG_CR
 
498
             "<D:multistatus xmlns:D=\"DAV:\"");
 
499
 
 
500
    if (namespaces != NULL) {
 
501
       int i;
 
502
 
 
503
       for (i = namespaces->nelts; i--; ) {
 
504
           ap_fprintf(r->output_filters, bb, " xmlns:ns%d=\"%s\"", i,
 
505
                      APR_XML_GET_URI_ITEM(namespaces, i));
 
506
       }
 
507
    }
 
508
 
 
509
    ap_fputs(r->output_filters, bb, ">" DEBUG_CR);
 
510
}
 
511
 
 
512
/* Finish a multistatus response started by dav_begin_multistatus: */
 
513
static apr_status_t dav_finish_multistatus(request_rec *r,
 
514
                                           apr_bucket_brigade *bb)
 
515
{
 
516
    apr_bucket *b;
 
517
 
 
518
    ap_fputs(r->output_filters, bb, "</D:multistatus>" DEBUG_CR);
 
519
 
 
520
    /* indicate the end of the response body */
 
521
    b = apr_bucket_eos_create(r->connection->bucket_alloc);
 
522
    APR_BRIGADE_INSERT_TAIL(bb, b);
 
523
 
 
524
    /* deliver whatever might be remaining in the brigade */
 
525
    return ap_pass_brigade(r->output_filters, bb);
 
526
}
 
527
 
 
528
static void dav_send_multistatus(request_rec *r, int status,
 
529
                                 dav_response *first,
 
530
                                 apr_array_header_t *namespaces)
 
531
{
 
532
    apr_pool_t *subpool;
 
533
    apr_bucket_brigade *bb = apr_brigade_create(r->pool,
 
534
                                                r->connection->bucket_alloc);
 
535
 
 
536
    dav_begin_multistatus(bb, r, status, namespaces);
 
537
 
 
538
    apr_pool_create(&subpool, r->pool);
 
539
 
 
540
    for (; first != NULL; first = first->next) {
 
541
      apr_pool_clear(subpool);
 
542
      dav_send_one_response(first, bb, r->output_filters, subpool);
 
543
    }
 
544
    apr_pool_destroy(subpool);
 
545
 
 
546
    dav_finish_multistatus(r, bb);
 
547
}
 
548
 
 
549
/*
 
550
 * dav_log_err()
 
551
 *
 
552
 * Write error information to the log.
 
553
 */
 
554
static void dav_log_err(request_rec *r, dav_error *err, int level)
 
555
{
 
556
    dav_error *errscan;
 
557
 
 
558
    /* Log the errors */
 
559
    /* ### should have a directive to log the first or all */
 
560
    for (errscan = err; errscan != NULL; errscan = errscan->prev) {
 
561
        if (errscan->desc == NULL)
 
562
            continue;
 
563
 
 
564
        if (errscan->save_errno != 0) {
 
565
            errno = errscan->save_errno;
 
566
            ap_log_rerror(APLOG_MARK, level, errno, r, "%s  [%d, #%d]",
 
567
                          errscan->desc, errscan->status, errscan->error_id);
 
568
        }
 
569
        else {
 
570
            ap_log_rerror(APLOG_MARK, level, 0, r,
 
571
                          "%s  [%d, #%d]",
 
572
                          errscan->desc, errscan->status, errscan->error_id);
 
573
        }
 
574
    }
 
575
}
 
576
 
 
577
/*
 
578
 * dav_handle_err()
 
579
 *
 
580
 * Handle the standard error processing. <err> must be non-NULL.
 
581
 *
 
582
 * <response> is set by the following:
 
583
 *   - dav_validate_request()
 
584
 *   - dav_add_lock()
 
585
 *   - repos_hooks->remove_resource
 
586
 *   - repos_hooks->move_resource
 
587
 *   - repos_hooks->copy_resource
 
588
 *   - vsn_hooks->update
 
589
 */
 
590
static int dav_handle_err(request_rec *r, dav_error *err,
 
591
                          dav_response *response)
 
592
{
 
593
    /* log the errors */
 
594
    dav_log_err(r, err, APLOG_ERR);
 
595
 
 
596
    if (response == NULL) {
 
597
        dav_error *stackerr = err;
 
598
 
 
599
        /* our error messages are safe; tell Apache this */
 
600
        apr_table_setn(r->notes, "verbose-error-to", "*");
 
601
 
 
602
        /* Didn't get a multistatus response passed in, but we still
 
603
           might be able to generate a standard <D:error> response.
 
604
           Search the error stack for an errortag. */
 
605
        while (stackerr != NULL && stackerr->tagname == NULL)
 
606
            stackerr = stackerr->prev;
 
607
 
 
608
        if (stackerr != NULL && stackerr->tagname != NULL)
 
609
            return dav_error_response_tag(r, stackerr);
 
610
 
 
611
        return err->status;
 
612
    }
 
613
 
 
614
    /* send the multistatus and tell Apache the request/response is DONE. */
 
615
    dav_send_multistatus(r, err->status, response, NULL);
 
616
    return DONE;
 
617
}
 
618
 
 
619
/* handy function for return values of methods that (may) create things */
 
620
static int dav_created(request_rec *r, const char *locn, const char *what,
 
621
                       int replaced)
 
622
{
 
623
    const char *body;
 
624
 
 
625
    if (locn == NULL) {
 
626
        locn = r->uri;
 
627
    }
 
628
 
 
629
    /* did the target resource already exist? */
 
630
    if (replaced) {
 
631
        /* Apache will supply a default message */
 
632
        return HTTP_NO_CONTENT;
 
633
    }
 
634
 
 
635
    /* Per HTTP/1.1, S10.2.2: add a Location header to contain the
 
636
     * URI that was created. */
 
637
 
 
638
    /* Convert locn to an absolute URI, and return in Location header */
 
639
    apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, locn, r));
 
640
 
 
641
    /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */
 
642
 
 
643
    /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
 
644
     * we must manufacture the entire response. */
 
645
    body = apr_psprintf(r->pool, "%s %s has been created.",
 
646
                        what, ap_escape_html(r->pool, locn));
 
647
    return dav_error_response(r, HTTP_CREATED, body);
 
648
}
 
649
 
 
650
/* ### move to dav_util? */
 
651
DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth)
 
652
{
 
653
    const char *depth = apr_table_get(r->headers_in, "Depth");
 
654
 
 
655
    if (depth == NULL) {
 
656
        return def_depth;
 
657
    }
 
658
 
 
659
    if (strcasecmp(depth, "infinity") == 0) {
 
660
        return DAV_INFINITY;
 
661
    }
 
662
    else if (strcmp(depth, "0") == 0) {
 
663
        return 0;
 
664
    }
 
665
    else if (strcmp(depth, "1") == 0) {
 
666
        return 1;
 
667
    }
 
668
 
 
669
    /* The caller will return an HTTP_BAD_REQUEST. This will augment the
 
670
     * default message that Apache provides. */
 
671
    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
672
                  "An invalid Depth header was specified.");
 
673
    return -1;
 
674
}
 
675
 
 
676
static int dav_get_overwrite(request_rec *r)
 
677
{
 
678
    const char *overwrite = apr_table_get(r->headers_in, "Overwrite");
 
679
 
 
680
    if (overwrite == NULL) {
 
681
        return 1; /* default is "T" */
 
682
    }
 
683
 
 
684
    if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') {
 
685
        return 0;
 
686
    }
 
687
 
 
688
    if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') {
 
689
        return 1;
 
690
    }
 
691
 
 
692
    /* The caller will return an HTTP_BAD_REQUEST. This will augment the
 
693
     * default message that Apache provides. */
 
694
    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
695
                  "An invalid Overwrite header was specified.");
 
696
    return -1;
 
697
}
 
698
 
 
699
/* resolve a request URI to a resource descriptor.
 
700
 *
 
701
 * If label_allowed != 0, then allow the request target to be altered by
 
702
 * a Label: header.
 
703
 *
 
704
 * If use_checked_in is true, then the repository provider should return
 
705
 * the resource identified by the DAV:checked-in property of the resource
 
706
 * identified by the Request-URI.
 
707
 */
 
708
static dav_error *dav_get_resource(request_rec *r, int label_allowed,
 
709
                                   int use_checked_in, dav_resource **res_p)
 
710
{
 
711
    dav_dir_conf *conf;
 
712
    const char *label = NULL;
 
713
    dav_error *err;
 
714
 
 
715
    /* if the request target can be overridden, get any target selector */
 
716
    if (label_allowed) {
 
717
        label = apr_table_get(r->headers_in, "label");
 
718
    }
 
719
 
 
720
    conf = ap_get_module_config(r->per_dir_config, &dav_module);
 
721
    /* assert: conf->provider != NULL */
 
722
 
 
723
    /* resolve the resource */
 
724
    err = (*conf->provider->repos->get_resource)(r, conf->dir,
 
725
                                                 label, use_checked_in,
 
726
                                                 res_p);
 
727
    if (err != NULL) {
 
728
        err = dav_push_error(r->pool, err->status, 0,
 
729
                             "Could not fetch resource information.", err);
 
730
        return err;
 
731
    }
 
732
 
 
733
    /* Note: this shouldn't happen, but just be sure... */
 
734
    if (*res_p == NULL) {
 
735
        /* ### maybe use HTTP_INTERNAL_SERVER_ERROR */
 
736
        return dav_new_error(r->pool, HTTP_NOT_FOUND, 0,
 
737
                             apr_psprintf(r->pool,
 
738
                                          "The provider did not define a "
 
739
                                          "resource for %s.",
 
740
                                          ap_escape_html(r->pool, r->uri)));
 
741
    }
 
742
 
 
743
    /* ### hmm. this doesn't feel like the right place or thing to do */
 
744
    /* if there were any input headers requiring a Vary header in the response,
 
745
     * add it now */
 
746
    dav_add_vary_header(r, r, *res_p);
 
747
 
 
748
    return NULL;
 
749
}
 
750
 
 
751
static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
 
752
{
 
753
    const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
 
754
 
 
755
    if (hooks == NULL) {
 
756
        *lockdb = NULL;
 
757
        return NULL;
 
758
    }
 
759
 
 
760
    /* open the thing lazily */
 
761
    return (*hooks->open_lockdb)(r, ro, 0, lockdb);
 
762
}
 
763
 
 
764
static int dav_parse_range(request_rec *r,
 
765
                           apr_off_t *range_start, apr_off_t *range_end)
 
766
{
 
767
    const char *range_c;
 
768
    char *range;
 
769
    char *dash;
 
770
    char *slash;
 
771
    char *errp;
 
772
 
 
773
    range_c = apr_table_get(r->headers_in, "content-range");
 
774
    if (range_c == NULL)
 
775
        return 0;
 
776
 
 
777
    range = apr_pstrdup(r->pool, range_c);
 
778
    if (strncasecmp(range, "bytes ", 6) != 0
 
779
        || (dash = ap_strchr(range, '-')) == NULL
 
780
        || (slash = ap_strchr(range, '/')) == NULL) {
 
781
        /* malformed header. ignore it (per S14.16 of RFC2616) */
 
782
        return 0;
 
783
    }
 
784
 
 
785
    *dash++ = *slash++ = '\0';
 
786
 
 
787
    /* ignore invalid ranges. (per S14.16 of RFC2616) */
 
788
    if (apr_strtoff(range_start, range + 6, &errp, 10)
 
789
        || *errp || *range_start < 0) {
 
790
        return 0;
 
791
    }
 
792
 
 
793
    if (apr_strtoff(range_end, dash, &errp, 10)
 
794
        || *errp || *range_end < 0 || *range_end < *range_start) {
 
795
        return 0;
 
796
    }
 
797
 
 
798
    if (*slash != '*') {
 
799
        apr_off_t dummy;
 
800
 
 
801
        if (apr_strtoff(&dummy, slash, &errp, 10)
 
802
            || *errp || dummy <= *range_end) {
 
803
            return 0;
 
804
        }
 
805
    }
 
806
 
 
807
    /* we now have a valid range */
 
808
    return 1;
 
809
}
 
810
 
 
811
/* handle the GET method */
 
812
static int dav_method_get(request_rec *r)
 
813
{
 
814
    dav_resource *resource;
 
815
    dav_error *err;
 
816
    int status;
 
817
 
 
818
    /* This method should only be called when the resource is not
 
819
     * visible to Apache. We will fetch the resource from the repository,
 
820
     * then create a subrequest for Apache to handle.
 
821
     */
 
822
    err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
 
823
                           &resource);
 
824
    if (err != NULL)
 
825
        return dav_handle_err(r, err, NULL);
 
826
 
 
827
    if (!resource->exists) {
 
828
        /* Apache will supply a default error for this. */
 
829
        return HTTP_NOT_FOUND;
 
830
    }
 
831
 
 
832
    /* set up the HTTP headers for the response */
 
833
    if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
 
834
        err = dav_push_error(r->pool, err->status, 0,
 
835
                             "Unable to set up HTTP headers.",
 
836
                             err);
 
837
        return dav_handle_err(r, err, NULL);
 
838
    }
 
839
 
 
840
    /* Handle conditional requests */
 
841
    status = ap_meets_conditions(r);
 
842
    if (status) {
 
843
      return status;
 
844
    }
 
845
 
 
846
    if (r->header_only) {
 
847
        return DONE;
 
848
    }
 
849
 
 
850
    /* okay... time to deliver the content */
 
851
    if ((err = (*resource->hooks->deliver)(resource,
 
852
                                           r->output_filters)) != NULL) {
 
853
        err = dav_push_error(r->pool, err->status, 0,
 
854
                             "Unable to deliver content.",
 
855
                             err);
 
856
        return dav_handle_err(r, err, NULL);
 
857
    }
 
858
 
 
859
    return DONE;
 
860
}
 
861
 
 
862
/* validate resource/locks on POST, then pass to the default handler */
 
863
static int dav_method_post(request_rec *r)
 
864
{
 
865
    dav_resource *resource;
 
866
    dav_error *err;
 
867
 
 
868
    /* Ask repository module to resolve the resource */
 
869
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
870
                           &resource);
 
871
    if (err != NULL)
 
872
        return dav_handle_err(r, err, NULL);
 
873
 
 
874
    /* Note: depth == 0. Implies no need for a multistatus response. */
 
875
    if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
 
876
                                    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
 
877
        /* ### add a higher-level description? */
 
878
        return dav_handle_err(r, err, NULL);
 
879
    }
 
880
 
 
881
    return DECLINED;
 
882
}
 
883
 
 
884
/* handle the PUT method */
 
885
static int dav_method_put(request_rec *r)
 
886
{
 
887
    dav_resource *resource;
 
888
    int resource_state;
 
889
    dav_auto_version_info av_info;
 
890
    const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
 
891
    const char *body;
 
892
    dav_error *err;
 
893
    dav_error *err2;
 
894
    dav_stream_mode mode;
 
895
    dav_stream *stream;
 
896
    dav_response *multi_response;
 
897
    int has_range;
 
898
    apr_off_t range_start;
 
899
    apr_off_t range_end;
 
900
 
 
901
    /* Ask repository module to resolve the resource */
 
902
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
903
                           &resource);
 
904
    if (err != NULL)
 
905
        return dav_handle_err(r, err, NULL);
 
906
 
 
907
    /* If not a file or collection resource, PUT not allowed */
 
908
    if (resource->type != DAV_RESOURCE_TYPE_REGULAR
 
909
        && resource->type != DAV_RESOURCE_TYPE_WORKING) {
 
910
        body = apr_psprintf(r->pool,
 
911
                            "Cannot create resource %s with PUT.",
 
912
                            ap_escape_html(r->pool, r->uri));
 
913
        return dav_error_response(r, HTTP_CONFLICT, body);
 
914
    }
 
915
 
 
916
    /* Cannot PUT a collection */
 
917
    if (resource->collection) {
 
918
        return dav_error_response(r, HTTP_CONFLICT,
 
919
                                  "Cannot PUT to a collection.");
 
920
 
 
921
    }
 
922
 
 
923
    resource_state = dav_get_resource_state(r, resource);
 
924
 
 
925
    /*
 
926
     * Note: depth == 0 normally requires no multistatus response. However,
 
927
     * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
 
928
     * other than the Request-URI, thereby requiring a multistatus.
 
929
     *
 
930
     * If the resource does not exist (DAV_RESOURCE_NULL), then we must
 
931
     * check the resource *and* its parent. If the resource exists or is
 
932
     * a locknull resource, then we check only the resource.
 
933
     */
 
934
    if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
 
935
                                    resource_state == DAV_RESOURCE_NULL ?
 
936
                                    DAV_VALIDATE_PARENT :
 
937
                                    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
 
938
        /* ### add a higher-level description? */
 
939
        return dav_handle_err(r, err, multi_response);
 
940
    }
 
941
 
 
942
    /* make sure the resource can be modified (if versioning repository) */
 
943
    if ((err = dav_auto_checkout(r, resource,
 
944
                                 0 /* not parent_only */,
 
945
                                 &av_info)) != NULL) {
 
946
        /* ### add a higher-level description? */
 
947
        return dav_handle_err(r, err, NULL);
 
948
    }
 
949
 
 
950
    /* truncate and rewrite the file unless we see a Content-Range */
 
951
    mode = DAV_MODE_WRITE_TRUNC;
 
952
 
 
953
    has_range = dav_parse_range(r, &range_start, &range_end);
 
954
    if (has_range) {
 
955
        mode = DAV_MODE_WRITE_SEEKABLE;
 
956
    }
 
957
 
 
958
    /* Create the new file in the repository */
 
959
    if ((err = (*resource->hooks->open_stream)(resource, mode,
 
960
                                               &stream)) != NULL) {
 
961
        /* ### assuming FORBIDDEN is probably not quite right... */
 
962
        err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
 
963
                             apr_psprintf(r->pool,
 
964
                                          "Unable to PUT new contents for %s.",
 
965
                                          ap_escape_html(r->pool, r->uri)),
 
966
                             err);
 
967
    }
 
968
 
 
969
    if (err == NULL && has_range) {
 
970
        /* a range was provided. seek to the start */
 
971
        err = (*resource->hooks->seek_stream)(stream, range_start);
 
972
    }
 
973
 
 
974
    if (err == NULL) {
 
975
        apr_bucket_brigade *bb;
 
976
        apr_bucket *b;
 
977
        int seen_eos = 0;
 
978
 
 
979
        bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
 
980
 
 
981
        do {
 
982
            apr_status_t rc;
 
983
 
 
984
            rc = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
 
985
                                APR_BLOCK_READ, DAV_READ_BLOCKSIZE);
 
986
 
 
987
            if (rc != APR_SUCCESS) {
 
988
                err = dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
 
989
                                    "Could not get next bucket brigade");
 
990
                break;
 
991
            }
 
992
 
 
993
            for (b = APR_BRIGADE_FIRST(bb);
 
994
                 b != APR_BRIGADE_SENTINEL(bb);
 
995
                 b = APR_BUCKET_NEXT(b))
 
996
            {
 
997
                const char *data;
 
998
                apr_size_t len;
 
999
 
 
1000
                if (APR_BUCKET_IS_EOS(b)) {
 
1001
                    seen_eos = 1;
 
1002
                    break;
 
1003
                }
 
1004
 
 
1005
                if (APR_BUCKET_IS_METADATA(b)) {
 
1006
                    continue;
 
1007
                }
 
1008
 
 
1009
                rc = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
 
1010
                if (rc != APR_SUCCESS) {
 
1011
                    err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
 
1012
                                        "An error occurred while reading "
 
1013
                                        "the request body.");
 
1014
                    break;
 
1015
                }
 
1016
 
 
1017
                if (err == NULL) {
 
1018
                    /* write whatever we read, until we see an error */
 
1019
                    err = (*resource->hooks->write_stream)(stream, data, len);
 
1020
                }
 
1021
            }
 
1022
 
 
1023
            apr_brigade_cleanup(bb);
 
1024
        } while (!seen_eos);
 
1025
 
 
1026
        apr_brigade_destroy(bb);
 
1027
 
 
1028
        err2 = (*resource->hooks->close_stream)(stream,
 
1029
                                                err == NULL /* commit */);
 
1030
        if (err2 != NULL && err == NULL) {
 
1031
            /* no error during the write, but we hit one at close. use it. */
 
1032
            err = err2;
 
1033
        }
 
1034
    }
 
1035
 
 
1036
    /*
 
1037
     * Ensure that we think the resource exists now.
 
1038
     * ### eek. if an error occurred during the write and we did not commit,
 
1039
     * ### then the resource might NOT exist (e.g. dav_fs_repos.c)
 
1040
     */
 
1041
    if (err == NULL) {
 
1042
        resource->exists = 1;
 
1043
    }
 
1044
 
 
1045
    /* restore modifiability of resources back to what they were */
 
1046
    err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */,
 
1047
                            0 /*unlock*/, &av_info);
 
1048
 
 
1049
    /* check for errors now */
 
1050
    if (err != NULL) {
 
1051
        return dav_handle_err(r, err, NULL);
 
1052
    }
 
1053
 
 
1054
    if (err2 != NULL) {
 
1055
        /* just log a warning */
 
1056
        err2 = dav_push_error(r->pool, err2->status, 0,
 
1057
                              "The PUT was successful, but there "
 
1058
                              "was a problem automatically checking in "
 
1059
                              "the resource or its parent collection.",
 
1060
                              err2);
 
1061
        dav_log_err(r, err2, APLOG_WARNING);
 
1062
    }
 
1063
 
 
1064
    /* ### place the Content-Type and Content-Language into the propdb */
 
1065
 
 
1066
    if (locks_hooks != NULL) {
 
1067
        dav_lockdb *lockdb;
 
1068
 
 
1069
        if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
 
1070
            /* The file creation was successful, but the locking failed. */
 
1071
            err = dav_push_error(r->pool, err->status, 0,
 
1072
                                 "The file was PUT successfully, but there "
 
1073
                                 "was a problem opening the lock database "
 
1074
                                 "which prevents inheriting locks from the "
 
1075
                                 "parent resources.",
 
1076
                                 err);
 
1077
            return dav_handle_err(r, err, NULL);
 
1078
        }
 
1079
 
 
1080
        /* notify lock system that we have created/replaced a resource */
 
1081
        err = dav_notify_created(r, lockdb, resource, resource_state, 0);
 
1082
 
 
1083
        (*locks_hooks->close_lockdb)(lockdb);
 
1084
 
 
1085
        if (err != NULL) {
 
1086
            /* The file creation was successful, but the locking failed. */
 
1087
            err = dav_push_error(r->pool, err->status, 0,
 
1088
                                 "The file was PUT successfully, but there "
 
1089
                                 "was a problem updating its lock "
 
1090
                                 "information.",
 
1091
                                 err);
 
1092
            return dav_handle_err(r, err, NULL);
 
1093
        }
 
1094
    }
 
1095
 
 
1096
    /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
 
1097
 
 
1098
    /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
 
1099
    return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
 
1100
}
 
1101
 
 
1102
 
 
1103
/* Use POOL to temporarily construct a dav_response object (from WRES
 
1104
   STATUS, and PROPSTATS) and stream it via WRES's ctx->brigade. */
 
1105
static void dav_stream_response(dav_walk_resource *wres,
 
1106
                                int status,
 
1107
                                dav_get_props_result *propstats,
 
1108
                                apr_pool_t *pool)
 
1109
{
 
1110
    dav_response resp = { 0 };
 
1111
    dav_walker_ctx *ctx = wres->walk_ctx;
 
1112
 
 
1113
    resp.href = wres->resource->uri;
 
1114
    resp.status = status;
 
1115
    if (propstats) {
 
1116
        resp.propresult = *propstats;
 
1117
    }
 
1118
 
 
1119
    dav_send_one_response(&resp, ctx->bb, ctx->r->output_filters, pool);
 
1120
}
 
1121
 
 
1122
 
 
1123
/* ### move this to dav_util? */
 
1124
DAV_DECLARE(void) dav_add_response(dav_walk_resource *wres,
 
1125
                                   int status, dav_get_props_result *propstats)
 
1126
{
 
1127
    dav_response *resp;
 
1128
 
 
1129
    /* just drop some data into an dav_response */
 
1130
    resp = apr_pcalloc(wres->pool, sizeof(*resp));
 
1131
    resp->href = apr_pstrdup(wres->pool, wres->resource->uri);
 
1132
    resp->status = status;
 
1133
    if (propstats) {
 
1134
        resp->propresult = *propstats;
 
1135
    }
 
1136
 
 
1137
    resp->next = wres->response;
 
1138
    wres->response = resp;
 
1139
}
 
1140
 
 
1141
 
 
1142
/* handle the DELETE method */
 
1143
static int dav_method_delete(request_rec *r)
 
1144
{
 
1145
    dav_resource *resource;
 
1146
    dav_auto_version_info av_info;
 
1147
    dav_error *err;
 
1148
    dav_error *err2;
 
1149
    dav_response *multi_response;
 
1150
    int result;
 
1151
    int depth;
 
1152
 
 
1153
    /* We don't use the request body right now, so torch it. */
 
1154
    if ((result = ap_discard_request_body(r)) != OK) {
 
1155
        return result;
 
1156
    }
 
1157
 
 
1158
    /* Ask repository module to resolve the resource */
 
1159
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
1160
                           &resource);
 
1161
    if (err != NULL)
 
1162
        return dav_handle_err(r, err, NULL);
 
1163
    if (!resource->exists) {
 
1164
        /* Apache will supply a default error for this. */
 
1165
        return HTTP_NOT_FOUND;
 
1166
    }
 
1167
 
 
1168
    /* 2518 says that depth must be infinity only for collections.
 
1169
     * For non-collections, depth is ignored, unless it is an illegal value (1).
 
1170
     */
 
1171
    depth = dav_get_depth(r, DAV_INFINITY);
 
1172
 
 
1173
    if (resource->collection && depth != DAV_INFINITY) {
 
1174
        /* This supplies additional information for the default message. */
 
1175
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
1176
                      "Depth must be \"infinity\" for DELETE of a collection.");
 
1177
        return HTTP_BAD_REQUEST;
 
1178
    }
 
1179
 
 
1180
    if (!resource->collection && depth == 1) {
 
1181
        /* This supplies additional information for the default message. */
 
1182
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
1183
                      "Depth of \"1\" is not allowed for DELETE.");
 
1184
        return HTTP_BAD_REQUEST;
 
1185
    }
 
1186
 
 
1187
    /*
 
1188
    ** If any resources fail the lock/If: conditions, then we must fail
 
1189
    ** the delete. Each of the failing resources will be listed within
 
1190
    ** a DAV:multistatus body, wrapped into a 424 response.
 
1191
    **
 
1192
    ** Note that a failure on the resource itself does not generate a
 
1193
    ** multistatus response -- only internal members/collections.
 
1194
    */
 
1195
    if ((err = dav_validate_request(r, resource, depth, NULL,
 
1196
                                    &multi_response,
 
1197
                                    DAV_VALIDATE_PARENT
 
1198
                                    | DAV_VALIDATE_USE_424, NULL)) != NULL) {
 
1199
        err = dav_push_error(r->pool, err->status, 0,
 
1200
                             apr_psprintf(r->pool,
 
1201
                                          "Could not DELETE %s due to a failed "
 
1202
                                          "precondition (e.g. locks).",
 
1203
                                          ap_escape_html(r->pool, r->uri)),
 
1204
                             err);
 
1205
        return dav_handle_err(r, err, multi_response);
 
1206
    }
 
1207
 
 
1208
    /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
 
1209
     *     locked by the token(s) in the if_header.
 
1210
     */
 
1211
    if ((result = dav_unlock(r, resource, NULL)) != OK) {
 
1212
        return result;
 
1213
    }
 
1214
 
 
1215
    /* if versioned resource, make sure parent is checked out */
 
1216
    if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
 
1217
                                 &av_info)) != NULL) {
 
1218
        /* ### add a higher-level description? */
 
1219
        return dav_handle_err(r, err, NULL);
 
1220
    }
 
1221
 
 
1222
    /* try to remove the resource */
 
1223
    err = (*resource->hooks->remove_resource)(resource, &multi_response);
 
1224
 
 
1225
    /* restore writability of parent back to what it was */
 
1226
    err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
 
1227
                            0 /*unlock*/, &av_info);
 
1228
 
 
1229
    /* check for errors now */
 
1230
    if (err != NULL) {
 
1231
        err = dav_push_error(r->pool, err->status, 0,
 
1232
                             apr_psprintf(r->pool,
 
1233
                                          "Could not DELETE %s.",
 
1234
                                          ap_escape_html(r->pool, r->uri)),
 
1235
                             err);
 
1236
        return dav_handle_err(r, err, multi_response);
 
1237
    }
 
1238
    if (err2 != NULL) {
 
1239
        /* just log a warning */
 
1240
        err = dav_push_error(r->pool, err2->status, 0,
 
1241
                             "The DELETE was successful, but there "
 
1242
                             "was a problem automatically checking in "
 
1243
                             "the parent collection.",
 
1244
                             err2);
 
1245
        dav_log_err(r, err, APLOG_WARNING);
 
1246
    }
 
1247
 
 
1248
    /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
 
1249
 
 
1250
    /* Apache will supply a default error for this. */
 
1251
    return HTTP_NO_CONTENT;
 
1252
}
 
1253
 
 
1254
/* generate DAV:supported-method-set OPTIONS response */
 
1255
static dav_error *dav_gen_supported_methods(request_rec *r,
 
1256
                                            const apr_xml_elem *elem,
 
1257
                                            const apr_table_t *methods,
 
1258
                                            apr_text_header *body)
 
1259
{
 
1260
    const apr_array_header_t *arr;
 
1261
    const apr_table_entry_t *elts;
 
1262
    apr_xml_elem *child;
 
1263
    apr_xml_attr *attr;
 
1264
    char *s;
 
1265
    int i;
 
1266
 
 
1267
    apr_text_append(r->pool, body, "<D:supported-method-set>" DEBUG_CR);
 
1268
 
 
1269
    if (elem->first_child == NULL) {
 
1270
        /* show all supported methods */
 
1271
        arr = apr_table_elts(methods);
 
1272
        elts = (const apr_table_entry_t *)arr->elts;
 
1273
 
 
1274
        for (i = 0; i < arr->nelts; ++i) {
 
1275
            if (elts[i].key == NULL)
 
1276
                continue;
 
1277
 
 
1278
            s = apr_psprintf(r->pool,
 
1279
                             "<D:supported-method D:name=\"%s\"/>"
 
1280
                             DEBUG_CR,
 
1281
                             elts[i].key);
 
1282
            apr_text_append(r->pool, body, s);
 
1283
        }
 
1284
    }
 
1285
    else {
 
1286
        /* check for support of specific methods */
 
1287
        for (child = elem->first_child; child != NULL; child = child->next) {
 
1288
            if (child->ns == APR_XML_NS_DAV_ID
 
1289
                && strcmp(child->name, "supported-method") == 0) {
 
1290
                const char *name = NULL;
 
1291
 
 
1292
                /* go through attributes to find method name */
 
1293
                for (attr = child->attr; attr != NULL; attr = attr->next) {
 
1294
                    if (attr->ns == APR_XML_NS_DAV_ID
 
1295
                        && strcmp(attr->name, "name") == 0)
 
1296
                            name = attr->value;
 
1297
                }
 
1298
 
 
1299
                if (name == NULL) {
 
1300
                    return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
 
1301
                                         "A DAV:supported-method element "
 
1302
                                         "does not have a \"name\" attribute");
 
1303
                }
 
1304
 
 
1305
                /* see if method is supported */
 
1306
                if (apr_table_get(methods, name) != NULL) {
 
1307
                    s = apr_psprintf(r->pool,
 
1308
                                     "<D:supported-method D:name=\"%s\"/>"
 
1309
                                     DEBUG_CR,
 
1310
                                     name);
 
1311
                    apr_text_append(r->pool, body, s);
 
1312
                }
 
1313
            }
 
1314
        }
 
1315
    }
 
1316
 
 
1317
    apr_text_append(r->pool, body, "</D:supported-method-set>" DEBUG_CR);
 
1318
    return NULL;
 
1319
}
 
1320
 
 
1321
/* generate DAV:supported-live-property-set OPTIONS response */
 
1322
static dav_error *dav_gen_supported_live_props(request_rec *r,
 
1323
                                               const dav_resource *resource,
 
1324
                                               const apr_xml_elem *elem,
 
1325
                                               apr_text_header *body)
 
1326
{
 
1327
    dav_lockdb *lockdb;
 
1328
    dav_propdb *propdb;
 
1329
    apr_xml_elem *child;
 
1330
    apr_xml_attr *attr;
 
1331
    dav_error *err;
 
1332
 
 
1333
    /* open lock database, to report on supported lock properties */
 
1334
    /* ### should open read-only */
 
1335
    if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
 
1336
        return dav_push_error(r->pool, err->status, 0,
 
1337
                              "The lock database could not be opened, "
 
1338
                              "preventing the reporting of supported lock "
 
1339
                              "properties.",
 
1340
                              err);
 
1341
    }
 
1342
 
 
1343
    /* open the property database (readonly) for the resource */
 
1344
    if ((err = dav_open_propdb(r, lockdb, resource, 1, NULL,
 
1345
                               &propdb)) != NULL) {
 
1346
        if (lockdb != NULL)
 
1347
            (*lockdb->hooks->close_lockdb)(lockdb);
 
1348
 
 
1349
        return dav_push_error(r->pool, err->status, 0,
 
1350
                              "The property database could not be opened, "
 
1351
                              "preventing report of supported properties.",
 
1352
                              err);
 
1353
    }
 
1354
 
 
1355
    apr_text_append(r->pool, body, "<D:supported-live-property-set>" DEBUG_CR);
 
1356
 
 
1357
    if (elem->first_child == NULL) {
 
1358
        /* show all supported live properties */
 
1359
        dav_get_props_result props = dav_get_allprops(propdb, DAV_PROP_INSERT_SUPPORTED);
 
1360
        body->last->next = props.propstats;
 
1361
        while (body->last->next != NULL)
 
1362
            body->last = body->last->next;
 
1363
    }
 
1364
    else {
 
1365
        /* check for support of specific live property */
 
1366
        for (child = elem->first_child; child != NULL; child = child->next) {
 
1367
            if (child->ns == APR_XML_NS_DAV_ID
 
1368
                && strcmp(child->name, "supported-live-property") == 0) {
 
1369
                const char *name = NULL;
 
1370
                const char *nmspace = NULL;
 
1371
 
 
1372
                /* go through attributes to find name and namespace */
 
1373
                for (attr = child->attr; attr != NULL; attr = attr->next) {
 
1374
                    if (attr->ns == APR_XML_NS_DAV_ID) {
 
1375
                        if (strcmp(attr->name, "name") == 0)
 
1376
                            name = attr->value;
 
1377
                        else if (strcmp(attr->name, "namespace") == 0)
 
1378
                            nmspace = attr->value;
 
1379
                    }
 
1380
                }
 
1381
 
 
1382
                if (name == NULL) {
 
1383
                    err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
 
1384
                                        "A DAV:supported-live-property "
 
1385
                                        "element does not have a \"name\" "
 
1386
                                        "attribute");
 
1387
                    break;
 
1388
                }
 
1389
 
 
1390
                /* default namespace to DAV: */
 
1391
                if (nmspace == NULL)
 
1392
                    nmspace = "DAV:";
 
1393
 
 
1394
                /* check for support of property */
 
1395
                dav_get_liveprop_supported(propdb, nmspace, name, body);
 
1396
            }
 
1397
        }
 
1398
    }
 
1399
 
 
1400
    apr_text_append(r->pool, body, "</D:supported-live-property-set>" DEBUG_CR);
 
1401
 
 
1402
    dav_close_propdb(propdb);
 
1403
 
 
1404
    if (lockdb != NULL)
 
1405
        (*lockdb->hooks->close_lockdb)(lockdb);
 
1406
 
 
1407
    return err;
 
1408
}
 
1409
 
 
1410
/* generate DAV:supported-report-set OPTIONS response */
 
1411
static dav_error *dav_gen_supported_reports(request_rec *r,
 
1412
                                            const dav_resource *resource,
 
1413
                                            const apr_xml_elem *elem,
 
1414
                                            const dav_hooks_vsn *vsn_hooks,
 
1415
                                            apr_text_header *body)
 
1416
{
 
1417
    apr_xml_elem *child;
 
1418
    apr_xml_attr *attr;
 
1419
    dav_error *err;
 
1420
    char *s;
 
1421
 
 
1422
    apr_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR);
 
1423
 
 
1424
    if (vsn_hooks != NULL) {
 
1425
        const dav_report_elem *reports;
 
1426
        const dav_report_elem *rp;
 
1427
 
 
1428
        if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) {
 
1429
            return dav_push_error(r->pool, err->status, 0,
 
1430
                                  "DAV:supported-report-set could not be "
 
1431
                                  "determined due to a problem fetching the "
 
1432
                                  "available reports for this resource.",
 
1433
                                  err);
 
1434
        }
 
1435
 
 
1436
        if (reports != NULL) {
 
1437
            if (elem->first_child == NULL) {
 
1438
                /* show all supported reports */
 
1439
                for (rp = reports; rp->nmspace != NULL; ++rp) {
 
1440
                    /* Note: we presume reports->namespace is
 
1441
                     * properly XML/URL quoted */
 
1442
                    s = apr_psprintf(r->pool,
 
1443
                                     "<D:supported-report D:name=\"%s\" "
 
1444
                                     "D:namespace=\"%s\"/>" DEBUG_CR,
 
1445
                                     rp->name, rp->nmspace);
 
1446
                    apr_text_append(r->pool, body, s);
 
1447
                }
 
1448
            }
 
1449
            else {
 
1450
                /* check for support of specific report */
 
1451
                for (child = elem->first_child; child != NULL; child = child->next) {
 
1452
                    if (child->ns == APR_XML_NS_DAV_ID
 
1453
                        && strcmp(child->name, "supported-report") == 0) {
 
1454
                        const char *name = NULL;
 
1455
                        const char *nmspace = NULL;
 
1456
 
 
1457
                        /* go through attributes to find name and namespace */
 
1458
                        for (attr = child->attr; attr != NULL; attr = attr->next) {
 
1459
                            if (attr->ns == APR_XML_NS_DAV_ID) {
 
1460
                                if (strcmp(attr->name, "name") == 0)
 
1461
                                    name = attr->value;
 
1462
                                else if (strcmp(attr->name, "namespace") == 0)
 
1463
                                    nmspace = attr->value;
 
1464
                            }
 
1465
                        }
 
1466
 
 
1467
                        if (name == NULL) {
 
1468
                            return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
 
1469
                                                 "A DAV:supported-report element "
 
1470
                                                 "does not have a \"name\" attribute");
 
1471
                        }
 
1472
 
 
1473
                        /* default namespace to DAV: */
 
1474
                        if (nmspace == NULL)
 
1475
                            nmspace = "DAV:";
 
1476
 
 
1477
                        for (rp = reports; rp->nmspace != NULL; ++rp) {
 
1478
                            if (strcmp(name, rp->name) == 0
 
1479
                                && strcmp(nmspace, rp->nmspace) == 0) {
 
1480
                                /* Note: we presume reports->nmspace is
 
1481
                                 * properly XML/URL quoted
 
1482
                                 */
 
1483
                                s = apr_psprintf(r->pool,
 
1484
                                                 "<D:supported-report "
 
1485
                                                 "D:name=\"%s\" "
 
1486
                                                 "D:namespace=\"%s\"/>"
 
1487
                                                 DEBUG_CR,
 
1488
                                                 rp->name, rp->nmspace);
 
1489
                                apr_text_append(r->pool, body, s);
 
1490
                                break;
 
1491
                            }
 
1492
                        }
 
1493
                    }
 
1494
                }
 
1495
            }
 
1496
        }
 
1497
    }
 
1498
 
 
1499
    apr_text_append(r->pool, body, "</D:supported-report-set>" DEBUG_CR);
 
1500
    return NULL;
 
1501
}
 
1502
 
 
1503
 
 
1504
/* handle the SEARCH method */
 
1505
static int dav_method_search(request_rec *r)
 
1506
{
 
1507
    const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
 
1508
    dav_resource *resource;
 
1509
    dav_error *err;
 
1510
    dav_response *multi_status;
 
1511
 
 
1512
    /* If no search provider, decline the request */
 
1513
    if (search_hooks == NULL)
 
1514
        return DECLINED;
 
1515
 
 
1516
    /* This method should only be called when the resource is not
 
1517
     * visible to Apache. We will fetch the resource from the repository,
 
1518
     * then create a subrequest for Apache to handle.
 
1519
     */
 
1520
    err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
 
1521
                           &resource);
 
1522
    if (err != NULL)
 
1523
        return dav_handle_err(r, err, NULL);
 
1524
 
 
1525
    if (!resource->exists) {
 
1526
        /* Apache will supply a default error for this. */
 
1527
        return HTTP_NOT_FOUND;
 
1528
    }
 
1529
 
 
1530
    /* set up the HTTP headers for the response */
 
1531
    if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
 
1532
        err = dav_push_error(r->pool, err->status, 0,
 
1533
                             "Unable to set up HTTP headers.",
 
1534
                             err);
 
1535
        return dav_handle_err(r, err, NULL);
 
1536
    }
 
1537
 
 
1538
    if (r->header_only) {
 
1539
        return DONE;
 
1540
    }
 
1541
 
 
1542
    /* okay... time to search the content */
 
1543
    /* Let's validate XML and process walk function
 
1544
     * in the hook function
 
1545
     */
 
1546
    if ((err = (*search_hooks->search_resource)(r, &multi_status)) != NULL) {
 
1547
        /* ### add a higher-level description? */
 
1548
        return dav_handle_err(r, err, NULL);
 
1549
    }
 
1550
 
 
1551
    /* We have results in multi_status */
 
1552
    /* Should I pass namespace?? */
 
1553
    dav_send_multistatus(r, HTTP_MULTI_STATUS, multi_status, NULL);
 
1554
 
 
1555
    return DONE;
 
1556
}
 
1557
 
 
1558
 
 
1559
/* handle the OPTIONS method */
 
1560
static int dav_method_options(request_rec *r)
 
1561
{
 
1562
    const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
 
1563
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
1564
    const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
 
1565
    const dav_hooks_search *search_hooks = DAV_GET_HOOKS_SEARCH(r);
 
1566
    dav_resource *resource;
 
1567
    const char *dav_level;
 
1568
    char *allow;
 
1569
    char *s;
 
1570
    const apr_array_header_t *arr;
 
1571
    const apr_table_entry_t *elts;
 
1572
    apr_table_t *methods = apr_table_make(r->pool, 12);
 
1573
    apr_text_header vsn_options = { 0 };
 
1574
    apr_text_header body = { 0 };
 
1575
    apr_text *t;
 
1576
    int text_size;
 
1577
    int result;
 
1578
    int i;
 
1579
    apr_array_header_t *uri_ary;
 
1580
    apr_xml_doc *doc;
 
1581
    const apr_xml_elem *elem;
 
1582
    dav_error *err;
 
1583
 
 
1584
    /* resolve the resource */
 
1585
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
1586
                           &resource);
 
1587
    if (err != NULL)
 
1588
        return dav_handle_err(r, err, NULL);
 
1589
 
 
1590
    /* parse any request body */
 
1591
    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
 
1592
        return result;
 
1593
    }
 
1594
    /* note: doc == NULL if no request body */
 
1595
 
 
1596
    if (doc && !dav_validate_root(doc, "options")) {
 
1597
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
1598
                      "The \"options\" element was not found.");
 
1599
        return HTTP_BAD_REQUEST;
 
1600
    }
 
1601
 
 
1602
    /* determine which providers are available */
 
1603
    dav_level = "1";
 
1604
 
 
1605
    if (locks_hooks != NULL) {
 
1606
        dav_level = "1,2";
 
1607
    }
 
1608
 
 
1609
    if (binding_hooks != NULL)
 
1610
        dav_level = apr_pstrcat(r->pool, dav_level, ",bindings", NULL);
 
1611
 
 
1612
    /* ###
 
1613
     * MSFT Web Folders chokes if length of DAV header value > 63 characters!
 
1614
     * To workaround that, we use separate DAV headers for versioning and
 
1615
     * live prop provider namespace URIs.
 
1616
     * ###
 
1617
     */
 
1618
    apr_table_setn(r->headers_out, "DAV", dav_level);
 
1619
 
 
1620
    /*
 
1621
     * If there is a versioning provider, generate DAV headers
 
1622
     * for versioning options.
 
1623
     */
 
1624
    if (vsn_hooks != NULL) {
 
1625
        (*vsn_hooks->get_vsn_options)(r->pool, &vsn_options);
 
1626
 
 
1627
        for (t = vsn_options.first; t != NULL; t = t->next)
 
1628
            apr_table_addn(r->headers_out, "DAV", t->text);
 
1629
    }
 
1630
 
 
1631
    /*
 
1632
     * Gather property set URIs from all the liveprop providers,
 
1633
     * and generate a separate DAV header for each URI, to avoid
 
1634
     * problems with long header lengths.
 
1635
     */
 
1636
    uri_ary = apr_array_make(r->pool, 5, sizeof(const char *));
 
1637
    dav_run_gather_propsets(uri_ary);
 
1638
    for (i = 0; i < uri_ary->nelts; ++i) {
 
1639
        if (((char **)uri_ary->elts)[i] != NULL)
 
1640
            apr_table_addn(r->headers_out, "DAV", ((char **)uri_ary->elts)[i]);
 
1641
    }
 
1642
 
 
1643
    /* this tells MSFT products to skip looking for FrontPage extensions */
 
1644
    apr_table_setn(r->headers_out, "MS-Author-Via", "DAV");
 
1645
 
 
1646
    /*
 
1647
     * Determine which methods are allowed on the resource.
 
1648
     * Three cases:  resource is null (3), is lock-null (7.4), or exists.
 
1649
     *
 
1650
     * All cases support OPTIONS, and if there is a lock provider, LOCK.
 
1651
     * (Lock-) null resources also support MKCOL and PUT.
 
1652
     * Lock-null supports PROPFIND and UNLOCK.
 
1653
     * Existing resources support lots of stuff.
 
1654
     */
 
1655
 
 
1656
    apr_table_addn(methods, "OPTIONS", "");
 
1657
 
 
1658
    /* ### take into account resource type */
 
1659
    switch (dav_get_resource_state(r, resource))
 
1660
    {
 
1661
    case DAV_RESOURCE_EXISTS:
 
1662
        /* resource exists */
 
1663
        apr_table_addn(methods, "GET", "");
 
1664
        apr_table_addn(methods, "HEAD", "");
 
1665
        apr_table_addn(methods, "POST", "");
 
1666
        apr_table_addn(methods, "DELETE", "");
 
1667
        apr_table_addn(methods, "TRACE", "");
 
1668
        apr_table_addn(methods, "PROPFIND", "");
 
1669
        apr_table_addn(methods, "PROPPATCH", "");
 
1670
        apr_table_addn(methods, "COPY", "");
 
1671
        apr_table_addn(methods, "MOVE", "");
 
1672
 
 
1673
        if (!resource->collection)
 
1674
            apr_table_addn(methods, "PUT", "");
 
1675
 
 
1676
        if (locks_hooks != NULL) {
 
1677
            apr_table_addn(methods, "LOCK", "");
 
1678
            apr_table_addn(methods, "UNLOCK", "");
 
1679
        }
 
1680
 
 
1681
        break;
 
1682
 
 
1683
    case DAV_RESOURCE_LOCK_NULL:
 
1684
        /* resource is lock-null. */
 
1685
        apr_table_addn(methods, "MKCOL", "");
 
1686
        apr_table_addn(methods, "PROPFIND", "");
 
1687
        apr_table_addn(methods, "PUT", "");
 
1688
 
 
1689
        if (locks_hooks != NULL) {
 
1690
            apr_table_addn(methods, "LOCK", "");
 
1691
            apr_table_addn(methods, "UNLOCK", "");
 
1692
        }
 
1693
 
 
1694
        break;
 
1695
 
 
1696
    case DAV_RESOURCE_NULL:
 
1697
        /* resource is null. */
 
1698
        apr_table_addn(methods, "MKCOL", "");
 
1699
        apr_table_addn(methods, "PUT", "");
 
1700
 
 
1701
        if (locks_hooks != NULL)
 
1702
            apr_table_addn(methods, "LOCK", "");
 
1703
 
 
1704
        break;
 
1705
 
 
1706
    default:
 
1707
        /* ### internal error! */
 
1708
        break;
 
1709
    }
 
1710
 
 
1711
    /* If there is a versioning provider, add versioning methods */
 
1712
    if (vsn_hooks != NULL) {
 
1713
        if (!resource->exists) {
 
1714
            if ((*vsn_hooks->versionable)(resource))
 
1715
                apr_table_addn(methods, "VERSION-CONTROL", "");
 
1716
 
 
1717
            if (vsn_hooks->can_be_workspace != NULL
 
1718
                && (*vsn_hooks->can_be_workspace)(resource))
 
1719
                apr_table_addn(methods, "MKWORKSPACE", "");
 
1720
 
 
1721
            if (vsn_hooks->can_be_activity != NULL
 
1722
                && (*vsn_hooks->can_be_activity)(resource))
 
1723
                apr_table_addn(methods, "MKACTIVITY", "");
 
1724
        }
 
1725
        else if (!resource->versioned) {
 
1726
            if ((*vsn_hooks->versionable)(resource))
 
1727
                apr_table_addn(methods, "VERSION-CONTROL", "");
 
1728
        }
 
1729
        else if (resource->working) {
 
1730
            apr_table_addn(methods, "CHECKIN", "");
 
1731
 
 
1732
            /* ### we might not support this DeltaV option */
 
1733
            apr_table_addn(methods, "UNCHECKOUT", "");
 
1734
        }
 
1735
        else if (vsn_hooks->add_label != NULL) {
 
1736
            apr_table_addn(methods, "CHECKOUT", "");
 
1737
            apr_table_addn(methods, "LABEL", "");
 
1738
        }
 
1739
        else {
 
1740
            apr_table_addn(methods, "CHECKOUT", "");
 
1741
        }
 
1742
    }
 
1743
 
 
1744
    /* If there is a bindings provider, see if resource is bindable */
 
1745
    if (binding_hooks != NULL
 
1746
        && (*binding_hooks->is_bindable)(resource)) {
 
1747
        apr_table_addn(methods, "BIND", "");
 
1748
    }
 
1749
 
 
1750
    /* If there is a search provider, set SEARCH in option */
 
1751
    if (search_hooks != NULL) {
 
1752
        apr_table_addn(methods, "SEARCH", "");
 
1753
    }
 
1754
 
 
1755
    /* Generate the Allow header */
 
1756
    arr = apr_table_elts(methods);
 
1757
    elts = (const apr_table_entry_t *)arr->elts;
 
1758
    text_size = 0;
 
1759
 
 
1760
    /* first, compute total length */
 
1761
    for (i = 0; i < arr->nelts; ++i) {
 
1762
        if (elts[i].key == NULL)
 
1763
            continue;
 
1764
 
 
1765
        /* add 1 for comma or null */
 
1766
        text_size += strlen(elts[i].key) + 1;
 
1767
    }
 
1768
 
 
1769
    s = allow = apr_palloc(r->pool, text_size);
 
1770
 
 
1771
    for (i = 0; i < arr->nelts; ++i) {
 
1772
        if (elts[i].key == NULL)
 
1773
            continue;
 
1774
 
 
1775
        if (s != allow)
 
1776
            *s++ = ',';
 
1777
 
 
1778
        strcpy(s, elts[i].key);
 
1779
        s += strlen(s);
 
1780
    }
 
1781
 
 
1782
    apr_table_setn(r->headers_out, "Allow", allow);
 
1783
 
 
1784
 
 
1785
    /* If there is search set_option_head function, set head */
 
1786
    /* DASL: <DAV:basicsearch>
 
1787
     * DASL: <http://foo.bar.com/syntax1>
 
1788
     * DASL: <http://akuma.com/syntax2>
 
1789
     */
 
1790
    if (search_hooks != NULL
 
1791
        && *search_hooks->set_option_head != NULL) {
 
1792
        if ((err = (*search_hooks->set_option_head)(r)) != NULL) {
 
1793
            return dav_handle_err(r, err, NULL);
 
1794
        }
 
1795
    }
 
1796
 
 
1797
    /* if there was no request body, then there is no response body */
 
1798
    if (doc == NULL) {
 
1799
        ap_set_content_length(r, 0);
 
1800
 
 
1801
        /* ### this sends a Content-Type. the default OPTIONS does not. */
 
1802
 
 
1803
        /* ### the default (ap_send_http_options) returns OK, but I believe
 
1804
         * ### that is because it is the default handler and nothing else
 
1805
         * ### will run after the thing. */
 
1806
        return DONE;
 
1807
    }
 
1808
 
 
1809
    /* handle each options request */
 
1810
    for (elem = doc->root->first_child; elem != NULL; elem = elem->next) {
 
1811
        /* check for something we recognize first */
 
1812
        int core_option = 0;
 
1813
        dav_error *err = NULL;
 
1814
 
 
1815
        if (elem->ns == APR_XML_NS_DAV_ID) {
 
1816
            if (strcmp(elem->name, "supported-method-set") == 0) {
 
1817
                err = dav_gen_supported_methods(r, elem, methods, &body);
 
1818
                core_option = 1;
 
1819
            }
 
1820
            else if (strcmp(elem->name, "supported-live-property-set") == 0) {
 
1821
                err = dav_gen_supported_live_props(r, resource, elem, &body);
 
1822
                core_option = 1;
 
1823
            }
 
1824
            else if (strcmp(elem->name, "supported-report-set") == 0) {
 
1825
                err = dav_gen_supported_reports(r, resource, elem, vsn_hooks, &body);
 
1826
                core_option = 1;
 
1827
            }
 
1828
        }
 
1829
 
 
1830
        if (err != NULL)
 
1831
            return dav_handle_err(r, err, NULL);
 
1832
 
 
1833
        /* if unrecognized option, pass to versioning provider */
 
1834
        if (!core_option && vsn_hooks != NULL) {
 
1835
            if ((err = (*vsn_hooks->get_option)(resource, elem, &body))
 
1836
                != NULL) {
 
1837
                return dav_handle_err(r, err, NULL);
 
1838
            }
 
1839
        }
 
1840
    }
 
1841
 
 
1842
    /* send the options response */
 
1843
    r->status = HTTP_OK;
 
1844
    ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
 
1845
 
 
1846
    /* send the headers and response body */
 
1847
    ap_rputs(DAV_XML_HEADER DEBUG_CR
 
1848
             "<D:options-response xmlns:D=\"DAV:\">" DEBUG_CR, r);
 
1849
 
 
1850
    for (t = body.first; t != NULL; t = t->next)
 
1851
        ap_rputs(t->text, r);
 
1852
 
 
1853
    ap_rputs("</D:options-response>" DEBUG_CR, r);
 
1854
 
 
1855
    /* we've sent everything necessary to the client. */
 
1856
    return DONE;
 
1857
}
 
1858
 
 
1859
static void dav_cache_badprops(dav_walker_ctx *ctx)
 
1860
{
 
1861
    const apr_xml_elem *elem;
 
1862
    apr_text_header hdr = { 0 };
 
1863
 
 
1864
    /* just return if we built the thing already */
 
1865
    if (ctx->propstat_404 != NULL) {
 
1866
        return;
 
1867
    }
 
1868
 
 
1869
    apr_text_append(ctx->w.pool, &hdr,
 
1870
                    "<D:propstat>" DEBUG_CR
 
1871
                    "<D:prop>" DEBUG_CR);
 
1872
 
 
1873
    elem = dav_find_child(ctx->doc->root, "prop");
 
1874
    for (elem = elem->first_child; elem; elem = elem->next) {
 
1875
        apr_text_append(ctx->w.pool, &hdr,
 
1876
                        apr_xml_empty_elem(ctx->w.pool, elem));
 
1877
    }
 
1878
 
 
1879
    apr_text_append(ctx->w.pool, &hdr,
 
1880
                    "</D:prop>" DEBUG_CR
 
1881
                    "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
 
1882
                    "</D:propstat>" DEBUG_CR);
 
1883
 
 
1884
    ctx->propstat_404 = hdr.first;
 
1885
}
 
1886
 
 
1887
static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype)
 
1888
{
 
1889
    dav_walker_ctx *ctx = wres->walk_ctx;
 
1890
    dav_error *err;
 
1891
    dav_propdb *propdb;
 
1892
    dav_get_props_result propstats = { 0 };
 
1893
 
 
1894
    /*
 
1895
    ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
 
1896
    ** dav_get_allprops() does not need to do namespace translation,
 
1897
    ** we're okay.
 
1898
    **
 
1899
    ** Note: we cast to lose the "const". The propdb won't try to change
 
1900
    ** the resource, however, since we are opening readonly.
 
1901
    */
 
1902
    err = dav_open_propdb(ctx->r, ctx->w.lockdb, wres->resource, 1,
 
1903
                          ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
 
1904
    if (err != NULL) {
 
1905
        /* ### do something with err! */
 
1906
 
 
1907
        if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
 
1908
            dav_get_props_result badprops = { 0 };
 
1909
 
 
1910
            /* some props were expected on this collection/resource */
 
1911
            dav_cache_badprops(ctx);
 
1912
            badprops.propstats = ctx->propstat_404;
 
1913
            dav_stream_response(wres, 0, &badprops, ctx->scratchpool);
 
1914
        }
 
1915
        else {
 
1916
            /* no props on this collection/resource */
 
1917
            dav_stream_response(wres, HTTP_OK, NULL, ctx->scratchpool);
 
1918
        }
 
1919
 
 
1920
        apr_pool_clear(ctx->scratchpool);
 
1921
        return NULL;
 
1922
    }
 
1923
    /* ### what to do about closing the propdb on server failure? */
 
1924
 
 
1925
    if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
 
1926
        propstats = dav_get_props(propdb, ctx->doc);
 
1927
    }
 
1928
    else {
 
1929
        dav_prop_insert what = ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP
 
1930
                                 ? DAV_PROP_INSERT_VALUE
 
1931
                                 : DAV_PROP_INSERT_NAME;
 
1932
        propstats = dav_get_allprops(propdb, what);
 
1933
    }
 
1934
    dav_close_propdb(propdb);
 
1935
 
 
1936
    dav_stream_response(wres, 0, &propstats, ctx->scratchpool);
 
1937
 
 
1938
    /* at this point, ctx->scratchpool has been used to stream a
 
1939
       single response.  this function fully controls the pool, and
 
1940
       thus has the right to clear it for the next iteration of this
 
1941
       callback. */
 
1942
    apr_pool_clear(ctx->scratchpool);
 
1943
 
 
1944
    return NULL;
 
1945
}
 
1946
 
 
1947
/* handle the PROPFIND method */
 
1948
static int dav_method_propfind(request_rec *r)
 
1949
{
 
1950
    dav_resource *resource;
 
1951
    int depth;
 
1952
    dav_error *err;
 
1953
    int result;
 
1954
    apr_xml_doc *doc;
 
1955
    const apr_xml_elem *child;
 
1956
    dav_walker_ctx ctx = { { 0 } };
 
1957
    dav_response *multi_status;
 
1958
 
 
1959
    /* Ask repository module to resolve the resource */
 
1960
    err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
 
1961
                           &resource);
 
1962
    if (err != NULL)
 
1963
        return dav_handle_err(r, err, NULL);
 
1964
 
 
1965
    if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
 
1966
        /* Apache will supply a default error for this. */
 
1967
        return HTTP_NOT_FOUND;
 
1968
    }
 
1969
 
 
1970
    if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
 
1971
        /* dav_get_depth() supplies additional information for the
 
1972
         * default message. */
 
1973
        return HTTP_BAD_REQUEST;
 
1974
    }
 
1975
 
 
1976
    if (depth == DAV_INFINITY && resource->collection) {
 
1977
        dav_dir_conf *conf;
 
1978
        conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
 
1979
                                                    &dav_module);
 
1980
        /* default is to DISALLOW these requests */
 
1981
        if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
 
1982
            return dav_error_response(r, HTTP_FORBIDDEN,
 
1983
                                      apr_psprintf(r->pool,
 
1984
                                                   "PROPFIND requests with a "
 
1985
                                                   "Depth of \"infinity\" are "
 
1986
                                                   "not allowed for %s.",
 
1987
                                                   ap_escape_html(r->pool,
 
1988
                                                                  r->uri)));
 
1989
        }
 
1990
    }
 
1991
 
 
1992
    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
 
1993
        return result;
 
1994
    }
 
1995
    /* note: doc == NULL if no request body */
 
1996
 
 
1997
    if (doc && !dav_validate_root(doc, "propfind")) {
 
1998
        /* This supplies additional information for the default message. */
 
1999
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2000
                      "The \"propfind\" element was not found.");
 
2001
        return HTTP_BAD_REQUEST;
 
2002
    }
 
2003
 
 
2004
    /* ### validate that only one of these three elements is present */
 
2005
 
 
2006
    if (doc == NULL
 
2007
        || (child = dav_find_child(doc->root, "allprop")) != NULL) {
 
2008
        /* note: no request body implies allprop */
 
2009
        ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
 
2010
    }
 
2011
    else if ((child = dav_find_child(doc->root, "propname")) != NULL) {
 
2012
        ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
 
2013
    }
 
2014
    else if ((child = dav_find_child(doc->root, "prop")) != NULL) {
 
2015
        ctx.propfind_type = DAV_PROPFIND_IS_PROP;
 
2016
    }
 
2017
    else {
 
2018
        /* "propfind" element must have one of the above three children */
 
2019
 
 
2020
        /* This supplies additional information for the default message. */
 
2021
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2022
                      "The \"propfind\" element does not contain one of "
 
2023
                      "the required child elements (the specific command).");
 
2024
        return HTTP_BAD_REQUEST;
 
2025
    }
 
2026
 
 
2027
    ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
 
2028
    ctx.w.func = dav_propfind_walker;
 
2029
    ctx.w.walk_ctx = &ctx;
 
2030
    ctx.w.pool = r->pool;
 
2031
    ctx.w.root = resource;
 
2032
 
 
2033
    ctx.doc = doc;
 
2034
    ctx.r = r;
 
2035
    ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
 
2036
    apr_pool_create(&ctx.scratchpool, r->pool);
 
2037
 
 
2038
    /* ### should open read-only */
 
2039
    if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) {
 
2040
        err = dav_push_error(r->pool, err->status, 0,
 
2041
                             "The lock database could not be opened, "
 
2042
                             "preventing access to the various lock "
 
2043
                             "properties for the PROPFIND.",
 
2044
                             err);
 
2045
        return dav_handle_err(r, err, NULL);
 
2046
    }
 
2047
    if (ctx.w.lockdb != NULL) {
 
2048
        /* if we have a lock database, then we can walk locknull resources */
 
2049
        ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
 
2050
    }
 
2051
 
 
2052
    /* send <multistatus> tag, with all doc->namespaces attached.  */
 
2053
 
 
2054
    /* NOTE: we *cannot* leave out the doc's namespaces from the
 
2055
       initial <multistatus> tag.  if a 404 was generated for an HREF,
 
2056
       then we need to spit out the doc's namespaces for use by the
 
2057
       404. Note that <response> elements will override these ns0,
 
2058
       ns1, etc, but NOT within the <response> scope for the
 
2059
       badprops. */
 
2060
    dav_begin_multistatus(ctx.bb, r, HTTP_MULTI_STATUS,
 
2061
                          doc ? doc->namespaces : NULL);
 
2062
 
 
2063
    /* Have the provider walk the resource. */
 
2064
    err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
 
2065
 
 
2066
    if (ctx.w.lockdb != NULL) {
 
2067
        (*ctx.w.lockdb->hooks->close_lockdb)(ctx.w.lockdb);
 
2068
    }
 
2069
 
 
2070
    if (err != NULL) {
 
2071
        /* If an error occurred during the resource walk, there's
 
2072
           basically nothing we can do but abort the connection and
 
2073
           log an error.  This is one of the limitations of HTTP; it
 
2074
           needs to "know" the entire status of the response before
 
2075
           generating it, which is just impossible in these streamy
 
2076
           response situations. */
 
2077
        err = dav_push_error(r->pool, err->status, 0,
 
2078
                             "Provider encountered an error while streaming"
 
2079
                             " a multistatus PROPFIND response.", err);
 
2080
        dav_log_err(r, err, APLOG_ERR);
 
2081
        r->connection->aborted = 1;
 
2082
        return DONE;
 
2083
    }
 
2084
 
 
2085
    dav_finish_multistatus(r, ctx.bb);
 
2086
 
 
2087
    /* the response has been sent. */
 
2088
    return DONE;
 
2089
}
 
2090
 
 
2091
static apr_text * dav_failed_proppatch(apr_pool_t *p,
 
2092
                                      apr_array_header_t *prop_ctx)
 
2093
{
 
2094
    apr_text_header hdr = { 0 };
 
2095
    int i = prop_ctx->nelts;
 
2096
    dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
 
2097
    dav_error *err424_set = NULL;
 
2098
    dav_error *err424_delete = NULL;
 
2099
    const char *s;
 
2100
 
 
2101
    /* ### might be nice to sort by status code and description */
 
2102
 
 
2103
    for ( ; i-- > 0; ++ctx ) {
 
2104
        apr_text_append(p, &hdr,
 
2105
                        "<D:propstat>" DEBUG_CR
 
2106
                        "<D:prop>");
 
2107
        apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
 
2108
        apr_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
 
2109
 
 
2110
        if (ctx->err == NULL) {
 
2111
            /* nothing was assigned here yet, so make it a 424 */
 
2112
 
 
2113
            if (ctx->operation == DAV_PROP_OP_SET) {
 
2114
                if (err424_set == NULL)
 
2115
                    err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
 
2116
                                               "Attempted DAV:set operation "
 
2117
                                               "could not be completed due "
 
2118
                                               "to other errors.");
 
2119
                ctx->err = err424_set;
 
2120
            }
 
2121
            else if (ctx->operation == DAV_PROP_OP_DELETE) {
 
2122
                if (err424_delete == NULL)
 
2123
                    err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
 
2124
                                                  "Attempted DAV:remove "
 
2125
                                                  "operation could not be "
 
2126
                                                  "completed due to other "
 
2127
                                                  "errors.");
 
2128
                ctx->err = err424_delete;
 
2129
            }
 
2130
        }
 
2131
 
 
2132
        s = apr_psprintf(p,
 
2133
                         "<D:status>"
 
2134
                         "HTTP/1.1 %d (status)"
 
2135
                         "</D:status>" DEBUG_CR,
 
2136
                         ctx->err->status);
 
2137
        apr_text_append(p, &hdr, s);
 
2138
 
 
2139
        /* ### we should use compute_desc if necessary... */
 
2140
        if (ctx->err->desc != NULL) {
 
2141
            apr_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
 
2142
            apr_text_append(p, &hdr, ctx->err->desc);
 
2143
            apr_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
 
2144
        }
 
2145
 
 
2146
        apr_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
 
2147
    }
 
2148
 
 
2149
    return hdr.first;
 
2150
}
 
2151
 
 
2152
static apr_text * dav_success_proppatch(apr_pool_t *p, apr_array_header_t *prop_ctx)
 
2153
{
 
2154
    apr_text_header hdr = { 0 };
 
2155
    int i = prop_ctx->nelts;
 
2156
    dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
 
2157
 
 
2158
    /*
 
2159
     * ### we probably need to revise the way we assemble the response...
 
2160
     * ### this code assumes everything will return status==200.
 
2161
     */
 
2162
 
 
2163
    apr_text_append(p, &hdr,
 
2164
                    "<D:propstat>" DEBUG_CR
 
2165
                    "<D:prop>" DEBUG_CR);
 
2166
 
 
2167
    for ( ; i-- > 0; ++ctx ) {
 
2168
        apr_text_append(p, &hdr, apr_xml_empty_elem(p, ctx->prop));
 
2169
    }
 
2170
 
 
2171
    apr_text_append(p, &hdr,
 
2172
                   "</D:prop>" DEBUG_CR
 
2173
                   "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
 
2174
                   "</D:propstat>" DEBUG_CR);
 
2175
 
 
2176
    return hdr.first;
 
2177
}
 
2178
 
 
2179
static void dav_prop_log_errors(dav_prop_ctx *ctx)
 
2180
{
 
2181
    dav_log_err(ctx->r, ctx->err, APLOG_ERR);
 
2182
}
 
2183
 
 
2184
/*
 
2185
 * Call <func> for each context. This can stop when an error occurs, or
 
2186
 * simply iterate through the whole list.
 
2187
 *
 
2188
 * Returns 1 if an error occurs (and the iteration is aborted). Returns 0
 
2189
 * if all elements are processed.
 
2190
 *
 
2191
 * If <reverse> is true (non-zero), then the list is traversed in
 
2192
 * reverse order.
 
2193
 */
 
2194
static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
 
2195
                                apr_array_header_t *ctx_list, int stop_on_error,
 
2196
                                int reverse)
 
2197
{
 
2198
    int i = ctx_list->nelts;
 
2199
    dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
 
2200
 
 
2201
    if (reverse)
 
2202
        ctx += i;
 
2203
 
 
2204
    while (i--) {
 
2205
        if (reverse)
 
2206
            --ctx;
 
2207
 
 
2208
        (*func)(ctx);
 
2209
        if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
 
2210
            return 1;
 
2211
        }
 
2212
 
 
2213
        if (!reverse)
 
2214
            ++ctx;
 
2215
    }
 
2216
 
 
2217
    return 0;
 
2218
}
 
2219
 
 
2220
/* handle the PROPPATCH method */
 
2221
static int dav_method_proppatch(request_rec *r)
 
2222
{
 
2223
    dav_error *err;
 
2224
    dav_resource *resource;
 
2225
    int result;
 
2226
    apr_xml_doc *doc;
 
2227
    apr_xml_elem *child;
 
2228
    dav_propdb *propdb;
 
2229
    int failure = 0;
 
2230
    dav_response resp = { 0 };
 
2231
    apr_text *propstat_text;
 
2232
    apr_array_header_t *ctx_list;
 
2233
    dav_prop_ctx *ctx;
 
2234
    dav_auto_version_info av_info;
 
2235
 
 
2236
    /* Ask repository module to resolve the resource */
 
2237
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
2238
                           &resource);
 
2239
    if (err != NULL)
 
2240
        return dav_handle_err(r, err, NULL);
 
2241
    if (!resource->exists) {
 
2242
        /* Apache will supply a default error for this. */
 
2243
        return HTTP_NOT_FOUND;
 
2244
    }
 
2245
 
 
2246
    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
 
2247
        return result;
 
2248
    }
 
2249
    /* note: doc == NULL if no request body */
 
2250
 
 
2251
    if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
 
2252
        /* This supplies additional information for the default message. */
 
2253
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2254
                      "The request body does not contain "
 
2255
                      "a \"propertyupdate\" element.");
 
2256
        return HTTP_BAD_REQUEST;
 
2257
    }
 
2258
 
 
2259
    /* Check If-Headers and existing locks */
 
2260
    /* Note: depth == 0. Implies no need for a multistatus response. */
 
2261
    if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
 
2262
                                    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
 
2263
        /* ### add a higher-level description? */
 
2264
        return dav_handle_err(r, err, NULL);
 
2265
    }
 
2266
 
 
2267
    /* make sure the resource can be modified (if versioning repository) */
 
2268
    if ((err = dav_auto_checkout(r, resource,
 
2269
                                 0 /* not parent_only */,
 
2270
                                 &av_info)) != NULL) {
 
2271
        /* ### add a higher-level description? */
 
2272
        return dav_handle_err(r, err, NULL);
 
2273
    }
 
2274
 
 
2275
    if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
 
2276
                               &propdb)) != NULL) {
 
2277
        /* undo any auto-checkout */
 
2278
        dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
 
2279
 
 
2280
        err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
 
2281
                             apr_psprintf(r->pool,
 
2282
                                          "Could not open the property "
 
2283
                                          "database for %s.",
 
2284
                                          ap_escape_html(r->pool, r->uri)),
 
2285
                             err);
 
2286
        return dav_handle_err(r, err, NULL);
 
2287
    }
 
2288
    /* ### what to do about closing the propdb on server failure? */
 
2289
 
 
2290
    /* ### validate "live" properties */
 
2291
 
 
2292
    /* set up an array to hold property operation contexts */
 
2293
    ctx_list = apr_array_make(r->pool, 10, sizeof(dav_prop_ctx));
 
2294
 
 
2295
    /* do a first pass to ensure that all "remove" properties exist */
 
2296
    for (child = doc->root->first_child; child; child = child->next) {
 
2297
        int is_remove;
 
2298
        apr_xml_elem *prop_group;
 
2299
        apr_xml_elem *one_prop;
 
2300
 
 
2301
        /* Ignore children that are not set/remove */
 
2302
        if (child->ns != APR_XML_NS_DAV_ID
 
2303
            || (!(is_remove = strcmp(child->name, "remove") == 0)
 
2304
                && strcmp(child->name, "set") != 0)) {
 
2305
            continue;
 
2306
        }
 
2307
 
 
2308
        /* make sure that a "prop" child exists for set/remove */
 
2309
        if ((prop_group = dav_find_child(child, "prop")) == NULL) {
 
2310
            dav_close_propdb(propdb);
 
2311
 
 
2312
            /* undo any auto-checkout */
 
2313
            dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
 
2314
 
 
2315
            /* This supplies additional information for the default message. */
 
2316
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2317
                          "A \"prop\" element is missing inside "
 
2318
                          "the propertyupdate command.");
 
2319
            return HTTP_BAD_REQUEST;
 
2320
        }
 
2321
 
 
2322
        for (one_prop = prop_group->first_child; one_prop;
 
2323
             one_prop = one_prop->next) {
 
2324
 
 
2325
            ctx = (dav_prop_ctx *)apr_array_push(ctx_list);
 
2326
            ctx->propdb = propdb;
 
2327
            ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
 
2328
            ctx->prop = one_prop;
 
2329
 
 
2330
            ctx->r = r;         /* for later use by dav_prop_log_errors() */
 
2331
 
 
2332
            dav_prop_validate(ctx);
 
2333
 
 
2334
            if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
 
2335
                failure = 1;
 
2336
            }
 
2337
        }
 
2338
    }
 
2339
 
 
2340
    /* ### should test that we found at least one set/remove */
 
2341
 
 
2342
    /* execute all of the operations */
 
2343
    if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
 
2344
        failure = 1;
 
2345
    }
 
2346
 
 
2347
    /* generate a failure/success response */
 
2348
    if (failure) {
 
2349
        (void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
 
2350
        propstat_text = dav_failed_proppatch(r->pool, ctx_list);
 
2351
    }
 
2352
    else {
 
2353
        (void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
 
2354
        propstat_text = dav_success_proppatch(r->pool, ctx_list);
 
2355
    }
 
2356
 
 
2357
    /* make sure this gets closed! */
 
2358
    dav_close_propdb(propdb);
 
2359
 
 
2360
    /* complete any auto-versioning */
 
2361
    dav_auto_checkin(r, resource, failure, 0 /*unlock*/, &av_info);
 
2362
 
 
2363
    /* log any errors that occurred */
 
2364
    (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
 
2365
 
 
2366
    resp.href = resource->uri;
 
2367
 
 
2368
    /* ### should probably use something new to pass along this text... */
 
2369
    resp.propresult.propstats = propstat_text;
 
2370
 
 
2371
    dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
 
2372
 
 
2373
    /* the response has been sent. */
 
2374
    return DONE;
 
2375
}
 
2376
 
 
2377
static int process_mkcol_body(request_rec *r)
 
2378
{
 
2379
    /* This is snarfed from ap_setup_client_block(). We could get pretty
 
2380
     * close to this behavior by passing REQUEST_NO_BODY, but we need to
 
2381
     * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
 
2382
     * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
 
2383
 
 
2384
    const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
 
2385
    const char *lenp = apr_table_get(r->headers_in, "Content-Length");
 
2386
 
 
2387
    /* make sure to set the Apache request fields properly. */
 
2388
    r->read_body = REQUEST_NO_BODY;
 
2389
    r->read_chunked = 0;
 
2390
    r->remaining = 0;
 
2391
 
 
2392
    if (tenc) {
 
2393
        if (strcasecmp(tenc, "chunked")) {
 
2394
            /* Use this instead of Apache's default error string */
 
2395
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2396
                          "Unknown Transfer-Encoding %s", tenc);
 
2397
            return HTTP_NOT_IMPLEMENTED;
 
2398
        }
 
2399
 
 
2400
        r->read_chunked = 1;
 
2401
    }
 
2402
    else if (lenp) {
 
2403
        const char *pos = lenp;
 
2404
 
 
2405
        while (apr_isdigit(*pos) || apr_isspace(*pos)) {
 
2406
            ++pos;
 
2407
        }
 
2408
 
 
2409
        if (*pos != '\0') {
 
2410
            /* This supplies additional information for the default message. */
 
2411
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2412
                          "Invalid Content-Length %s", lenp);
 
2413
            return HTTP_BAD_REQUEST;
 
2414
        }
 
2415
 
 
2416
        r->remaining = apr_atoi64(lenp);
 
2417
    }
 
2418
 
 
2419
    if (r->read_chunked || r->remaining > 0) {
 
2420
        /* ### log something? */
 
2421
 
 
2422
        /* Apache will supply a default error for this. */
 
2423
        return HTTP_UNSUPPORTED_MEDIA_TYPE;
 
2424
    }
 
2425
 
 
2426
    /*
 
2427
     * Get rid of the body. this will call ap_setup_client_block(), but
 
2428
     * our copy above has already verified its work.
 
2429
     */
 
2430
    return ap_discard_request_body(r);
 
2431
}
 
2432
 
 
2433
/* handle the MKCOL method */
 
2434
static int dav_method_mkcol(request_rec *r)
 
2435
{
 
2436
    dav_resource *resource;
 
2437
    int resource_state;
 
2438
    dav_auto_version_info av_info;
 
2439
    const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
 
2440
    dav_error *err;
 
2441
    dav_error *err2;
 
2442
    int result;
 
2443
    dav_dir_conf *conf;
 
2444
    dav_response *multi_status;
 
2445
 
 
2446
    /* handle the request body */
 
2447
    /* ### this may move lower once we start processing bodies */
 
2448
    if ((result = process_mkcol_body(r)) != OK) {
 
2449
        return result;
 
2450
    }
 
2451
 
 
2452
    conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
 
2453
                                                &dav_module);
 
2454
 
 
2455
    /* Ask repository module to resolve the resource */
 
2456
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
2457
                           &resource);
 
2458
    if (err != NULL)
 
2459
        return dav_handle_err(r, err, NULL);
 
2460
 
 
2461
    if (resource->exists) {
 
2462
        /* oops. something was already there! */
 
2463
 
 
2464
        /* Apache will supply a default error for this. */
 
2465
        /* ### we should provide a specific error message! */
 
2466
        return HTTP_METHOD_NOT_ALLOWED;
 
2467
    }
 
2468
 
 
2469
    resource_state = dav_get_resource_state(r, resource);
 
2470
 
 
2471
    /*
 
2472
     * Check If-Headers and existing locks.
 
2473
     *
 
2474
     * Note: depth == 0 normally requires no multistatus response. However,
 
2475
     * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
 
2476
     * other than the Request-URI, thereby requiring a multistatus.
 
2477
     *
 
2478
     * If the resource does not exist (DAV_RESOURCE_NULL), then we must
 
2479
     * check the resource *and* its parent. If the resource exists or is
 
2480
     * a locknull resource, then we check only the resource.
 
2481
     */
 
2482
    if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
 
2483
                                    resource_state == DAV_RESOURCE_NULL ?
 
2484
                                    DAV_VALIDATE_PARENT :
 
2485
                                    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
 
2486
        /* ### add a higher-level description? */
 
2487
        return dav_handle_err(r, err, multi_status);
 
2488
    }
 
2489
 
 
2490
    /* if versioned resource, make sure parent is checked out */
 
2491
    if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
 
2492
                                 &av_info)) != NULL) {
 
2493
        /* ### add a higher-level description? */
 
2494
        return dav_handle_err(r, err, NULL);
 
2495
    }
 
2496
 
 
2497
    /* try to create the collection */
 
2498
    resource->collection = 1;
 
2499
    err = (*resource->hooks->create_collection)(resource);
 
2500
 
 
2501
    /* restore modifiability of parent back to what it was */
 
2502
    err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
 
2503
                            0 /*unlock*/, &av_info);
 
2504
 
 
2505
    /* check for errors now */
 
2506
    if (err != NULL) {
 
2507
        return dav_handle_err(r, err, NULL);
 
2508
    }
 
2509
    if (err2 != NULL) {
 
2510
        /* just log a warning */
 
2511
        err = dav_push_error(r->pool, err2->status, 0,
 
2512
                             "The MKCOL was successful, but there "
 
2513
                             "was a problem automatically checking in "
 
2514
                             "the parent collection.",
 
2515
                             err2);
 
2516
        dav_log_err(r, err, APLOG_WARNING);
 
2517
    }
 
2518
 
 
2519
    if (locks_hooks != NULL) {
 
2520
        dav_lockdb *lockdb;
 
2521
 
 
2522
        if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
 
2523
            /* The directory creation was successful, but the locking failed. */
 
2524
            err = dav_push_error(r->pool, err->status, 0,
 
2525
                                 "The MKCOL was successful, but there "
 
2526
                                 "was a problem opening the lock database "
 
2527
                                 "which prevents inheriting locks from the "
 
2528
                                 "parent resources.",
 
2529
                                 err);
 
2530
            return dav_handle_err(r, err, NULL);
 
2531
        }
 
2532
 
 
2533
        /* notify lock system that we have created/replaced a resource */
 
2534
        err = dav_notify_created(r, lockdb, resource, resource_state, 0);
 
2535
 
 
2536
        (*locks_hooks->close_lockdb)(lockdb);
 
2537
 
 
2538
        if (err != NULL) {
 
2539
            /* The dir creation was successful, but the locking failed. */
 
2540
            err = dav_push_error(r->pool, err->status, 0,
 
2541
                                 "The MKCOL was successful, but there "
 
2542
                                 "was a problem updating its lock "
 
2543
                                 "information.",
 
2544
                                 err);
 
2545
            return dav_handle_err(r, err, NULL);
 
2546
        }
 
2547
    }
 
2548
 
 
2549
    /* return an appropriate response (HTTP_CREATED) */
 
2550
    return dav_created(r, NULL, "Collection", 0);
 
2551
}
 
2552
 
 
2553
/* handle the COPY and MOVE methods */
 
2554
static int dav_method_copymove(request_rec *r, int is_move)
 
2555
{
 
2556
    dav_resource *resource;
 
2557
    dav_resource *resnew;
 
2558
    dav_auto_version_info src_av_info = { 0 };
 
2559
    dav_auto_version_info dst_av_info = { 0 };
 
2560
    const char *body;
 
2561
    const char *dest;
 
2562
    dav_error *err;
 
2563
    dav_error *err2;
 
2564
    dav_error *err3;
 
2565
    dav_response *multi_response;
 
2566
    dav_lookup_result lookup;
 
2567
    int is_dir;
 
2568
    int overwrite;
 
2569
    int depth;
 
2570
    int result;
 
2571
    dav_lockdb *lockdb;
 
2572
    int replace_dest;
 
2573
    int resnew_state;
 
2574
 
 
2575
    /* Ask repository module to resolve the resource */
 
2576
    err = dav_get_resource(r, !is_move /* label_allowed */,
 
2577
                           0 /* use_checked_in */, &resource);
 
2578
    if (err != NULL)
 
2579
        return dav_handle_err(r, err, NULL);
 
2580
 
 
2581
    if (!resource->exists) {
 
2582
        /* Apache will supply a default error for this. */
 
2583
        return HTTP_NOT_FOUND;
 
2584
    }
 
2585
 
 
2586
    /* If not a file or collection resource, COPY/MOVE not allowed */
 
2587
    /* ### allow COPY/MOVE of DeltaV resource types */
 
2588
    if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
 
2589
        body = apr_psprintf(r->pool,
 
2590
                            "Cannot COPY/MOVE resource %s.",
 
2591
                            ap_escape_html(r->pool, r->uri));
 
2592
        return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
 
2593
    }
 
2594
 
 
2595
    /* get the destination URI */
 
2596
    dest = apr_table_get(r->headers_in, "Destination");
 
2597
    if (dest == NULL) {
 
2598
        /* Look in headers provided by Netscape's Roaming Profiles */
 
2599
        const char *nscp_host = apr_table_get(r->headers_in, "Host");
 
2600
        const char *nscp_path = apr_table_get(r->headers_in, "New-uri");
 
2601
 
 
2602
        if (nscp_host != NULL && nscp_path != NULL)
 
2603
            dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
 
2604
    }
 
2605
    if (dest == NULL) {
 
2606
        /* This supplies additional information for the default message. */
 
2607
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2608
                      "The request is missing a Destination header.");
 
2609
        return HTTP_BAD_REQUEST;
 
2610
    }
 
2611
 
 
2612
    lookup = dav_lookup_uri(dest, r, 1 /* must_be_absolute */);
 
2613
    if (lookup.rnew == NULL) {
 
2614
        if (lookup.err.status == HTTP_BAD_REQUEST) {
 
2615
            /* This supplies additional information for the default message. */
 
2616
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2617
                          "%s", lookup.err.desc);
 
2618
            return HTTP_BAD_REQUEST;
 
2619
        }
 
2620
 
 
2621
        /* ### this assumes that dav_lookup_uri() only generates a status
 
2622
         * ### that Apache can provide a status line for!! */
 
2623
 
 
2624
        return dav_error_response(r, lookup.err.status, lookup.err.desc);
 
2625
    }
 
2626
    if (lookup.rnew->status != HTTP_OK) {
 
2627
        const char *auth = apr_table_get(lookup.rnew->err_headers_out,
 
2628
                                        "WWW-Authenticate");
 
2629
        if (lookup.rnew->status == HTTP_UNAUTHORIZED && auth != NULL) {
 
2630
            /* propagate the WWW-Authorization header up from the
 
2631
             * subreq so the client sees it. */
 
2632
            apr_table_set(r->err_headers_out, "WWW-Authenticate",
 
2633
                          apr_pstrdup(r->pool, auth));
 
2634
        }
 
2635
 
 
2636
        /* ### how best to report this... */
 
2637
        return dav_error_response(r, lookup.rnew->status,
 
2638
                                  "Destination URI had an error.");
 
2639
    }
 
2640
 
 
2641
    /* Resolve destination resource */
 
2642
    err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
 
2643
                           0 /* use_checked_in */, &resnew);
 
2644
    if (err != NULL)
 
2645
        return dav_handle_err(r, err, NULL);
 
2646
 
 
2647
    /* are the two resources handled by the same repository? */
 
2648
    if (resource->hooks != resnew->hooks) {
 
2649
        /* ### this message exposes some backend config, but screw it... */
 
2650
        return dav_error_response(r, HTTP_BAD_GATEWAY,
 
2651
                                  "Destination URI is handled by a "
 
2652
                                  "different repository than the source URI. "
 
2653
                                  "MOVE or COPY between repositories is "
 
2654
                                  "not possible.");
 
2655
    }
 
2656
 
 
2657
    /* get and parse the overwrite header value */
 
2658
    if ((overwrite = dav_get_overwrite(r)) < 0) {
 
2659
        /* dav_get_overwrite() supplies additional information for the
 
2660
         * default message. */
 
2661
        return HTTP_BAD_REQUEST;
 
2662
    }
 
2663
 
 
2664
    /* quick failure test: if dest exists and overwrite is false. */
 
2665
    if (resnew->exists && !overwrite) {
 
2666
        /* Supply some text for the error response body. */
 
2667
        return dav_error_response(r, HTTP_PRECONDITION_FAILED,
 
2668
                                  "Destination is not empty and "
 
2669
                                  "Overwrite is not \"T\"");
 
2670
    }
 
2671
 
 
2672
    /* are the source and destination the same? */
 
2673
    if ((*resource->hooks->is_same_resource)(resource, resnew)) {
 
2674
        /* Supply some text for the error response body. */
 
2675
        return dav_error_response(r, HTTP_FORBIDDEN,
 
2676
                                  "Source and Destination URIs are the same.");
 
2677
 
 
2678
    }
 
2679
 
 
2680
    is_dir = resource->collection;
 
2681
 
 
2682
    /* get and parse the Depth header value. "0" and "infinity" are legal. */
 
2683
    if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
 
2684
        /* dav_get_depth() supplies additional information for the
 
2685
         * default message. */
 
2686
        return HTTP_BAD_REQUEST;
 
2687
    }
 
2688
    if (depth == 1) {
 
2689
        /* This supplies additional information for the default message. */
 
2690
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2691
                      "Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
 
2692
        return HTTP_BAD_REQUEST;
 
2693
    }
 
2694
    if (is_move && is_dir && depth != DAV_INFINITY) {
 
2695
        /* This supplies additional information for the default message. */
 
2696
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2697
                      "Depth must be \"infinity\" when moving a collection.");
 
2698
        return HTTP_BAD_REQUEST;
 
2699
    }
 
2700
 
 
2701
    /*
 
2702
     * Check If-Headers and existing locks for each resource in the source
 
2703
     * if we are performing a MOVE. We will return a 424 response with a
 
2704
     * DAV:multistatus body. The multistatus responses will contain the
 
2705
     * information about any resource that fails the validation.
 
2706
     *
 
2707
     * We check the parent resource, too, since this is a MOVE. Moving the
 
2708
     * resource effectively removes it from the parent collection, so we
 
2709
     * must ensure that we have met the appropriate conditions.
 
2710
     *
 
2711
     * If a problem occurs with the Request-URI itself, then a plain error
 
2712
     * (rather than a multistatus) will be returned.
 
2713
     */
 
2714
    if (is_move
 
2715
        && (err = dav_validate_request(r, resource, depth, NULL,
 
2716
                                       &multi_response,
 
2717
                                       DAV_VALIDATE_PARENT
 
2718
                                       | DAV_VALIDATE_USE_424,
 
2719
                                       NULL)) != NULL) {
 
2720
        err = dav_push_error(r->pool, err->status, 0,
 
2721
                             apr_psprintf(r->pool,
 
2722
                                          "Could not MOVE %s due to a failed "
 
2723
                                          "precondition on the source "
 
2724
                                          "(e.g. locks).",
 
2725
                                          ap_escape_html(r->pool, r->uri)),
 
2726
                             err);
 
2727
        return dav_handle_err(r, err, multi_response);
 
2728
    }
 
2729
 
 
2730
    /*
 
2731
     * Check If-Headers and existing locks for destination. Note that we
 
2732
     * use depth==infinity since the target (hierarchy) will be deleted
 
2733
     * before the move/copy is completed.
 
2734
     *
 
2735
     * Note that we are overwriting the target, which implies a DELETE, so
 
2736
     * we are subject to the error/response rules as a DELETE. Namely, we
 
2737
     * will return a 424 error if any of the validations fail.
 
2738
     * (see dav_method_delete() for more information)
 
2739
     */
 
2740
    if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
 
2741
                                    &multi_response,
 
2742
                                    DAV_VALIDATE_PARENT
 
2743
                                    | DAV_VALIDATE_USE_424, NULL)) != NULL) {
 
2744
        err = dav_push_error(r->pool, err->status, 0,
 
2745
                             apr_psprintf(r->pool,
 
2746
                                          "Could not MOVE/COPY %s due to a "
 
2747
                                          "failed precondition on the "
 
2748
                                          "destination (e.g. locks).",
 
2749
                                          ap_escape_html(r->pool, r->uri)),
 
2750
                             err);
 
2751
        return dav_handle_err(r, err, multi_response);
 
2752
    }
 
2753
 
 
2754
    if (is_dir
 
2755
        && depth == DAV_INFINITY
 
2756
        && (*resource->hooks->is_parent_resource)(resource, resnew)) {
 
2757
        /* Supply some text for the error response body. */
 
2758
        return dav_error_response(r, HTTP_FORBIDDEN,
 
2759
                                  "Source collection contains the "
 
2760
                                  "Destination.");
 
2761
 
 
2762
    }
 
2763
    if (is_dir
 
2764
        && (*resnew->hooks->is_parent_resource)(resnew, resource)) {
 
2765
        /* The destination must exist (since it contains the source), and
 
2766
         * a condition above implies Overwrite==T. Obviously, we cannot
 
2767
         * delete the Destination before the MOVE/COPY, as that would
 
2768
         * delete the Source.
 
2769
         */
 
2770
 
 
2771
        /* Supply some text for the error response body. */
 
2772
        return dav_error_response(r, HTTP_FORBIDDEN,
 
2773
                                  "Destination collection contains the Source "
 
2774
                                  "and Overwrite has been specified.");
 
2775
    }
 
2776
 
 
2777
    /* ### for now, we don't need anything in the body */
 
2778
    if ((result = ap_discard_request_body(r)) != OK) {
 
2779
        return result;
 
2780
    }
 
2781
 
 
2782
    if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
 
2783
        /* ### add a higher-level description? */
 
2784
        return dav_handle_err(r, err, NULL);
 
2785
    }
 
2786
 
 
2787
    /* remove any locks from the old resources */
 
2788
    /*
 
2789
     * ### this is Yet Another Traversal. if we do a rename(), then we
 
2790
     * ### really don't have to do this in some cases since the inode
 
2791
     * ### values will remain constant across the move. but we can't
 
2792
     * ### know that fact from outside the provider :-(
 
2793
     *
 
2794
     * ### note that we now have a problem atomicity in the move/copy
 
2795
     * ### since a failure after this would have removed locks (technically,
 
2796
     * ### this is okay to do, but really...)
 
2797
     */
 
2798
    if (is_move && lockdb != NULL) {
 
2799
        /* ### this is wrong! it blasts direct locks on parent resources */
 
2800
        /* ### pass lockdb! */
 
2801
        (void)dav_unlock(r, resource, NULL);
 
2802
    }
 
2803
 
 
2804
    /* if this is a move, then the source parent collection will be modified */
 
2805
    if (is_move) {
 
2806
        if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
 
2807
                                     &src_av_info)) != NULL) {
 
2808
            if (lockdb != NULL)
 
2809
                (*lockdb->hooks->close_lockdb)(lockdb);
 
2810
 
 
2811
            /* ### add a higher-level description? */
 
2812
            return dav_handle_err(r, err, NULL);
 
2813
        }
 
2814
    }
 
2815
 
 
2816
    /*
 
2817
     * Remember the initial state of the destination, so the lock system
 
2818
     * can be notified as to how it changed.
 
2819
     */
 
2820
    resnew_state = dav_get_resource_state(lookup.rnew, resnew);
 
2821
 
 
2822
    /* In a MOVE operation, the destination is replaced by the source.
 
2823
     * In a COPY operation, if the destination exists, is under version
 
2824
     * control, and is the same resource type as the source,
 
2825
     * then it should not be replaced, but modified to be a copy of
 
2826
     * the source.
 
2827
     */
 
2828
    if (!resnew->exists)
 
2829
        replace_dest = 0;
 
2830
    else if (is_move || !resource->versioned)
 
2831
        replace_dest = 1;
 
2832
    else if (resource->type != resnew->type)
 
2833
        replace_dest = 1;
 
2834
    else if ((resource->collection == 0) != (resnew->collection == 0))
 
2835
        replace_dest = 1;
 
2836
    else
 
2837
        replace_dest = 0;
 
2838
 
 
2839
    /* If the destination must be created or replaced,
 
2840
     * make sure the parent collection is writable
 
2841
     */
 
2842
    if (!resnew->exists || replace_dest) {
 
2843
        if ((err = dav_auto_checkout(r, resnew, 1 /*parent_only*/,
 
2844
                                     &dst_av_info)) != NULL) {
 
2845
            /* could not make destination writable:
 
2846
             * if move, restore state of source parent
 
2847
             */
 
2848
            if (is_move) {
 
2849
                (void)dav_auto_checkin(r, NULL, 1 /* undo */,
 
2850
                                       0 /*unlock*/, &src_av_info);
 
2851
            }
 
2852
 
 
2853
            if (lockdb != NULL)
 
2854
                (*lockdb->hooks->close_lockdb)(lockdb);
 
2855
 
 
2856
            /* ### add a higher-level description? */
 
2857
            return dav_handle_err(r, err, NULL);
 
2858
        }
 
2859
    }
 
2860
 
 
2861
    /* If source and destination parents are the same, then
 
2862
     * use the same resource object, so status updates to one are reflected
 
2863
     * in the other, when doing auto-versioning. Otherwise,
 
2864
     * we may try to checkin the parent twice.
 
2865
     */
 
2866
    if (src_av_info.parent_resource != NULL
 
2867
        && dst_av_info.parent_resource != NULL
 
2868
        && (*src_av_info.parent_resource->hooks->is_same_resource)
 
2869
            (src_av_info.parent_resource, dst_av_info.parent_resource)) {
 
2870
 
 
2871
        dst_av_info.parent_resource = src_av_info.parent_resource;
 
2872
    }
 
2873
 
 
2874
    /* If destination is being replaced, remove it first
 
2875
     * (we know Ovewrite must be TRUE). Then try to copy/move the resource.
 
2876
     */
 
2877
    if (replace_dest)
 
2878
        err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
 
2879
 
 
2880
    if (err == NULL) {
 
2881
        if (is_move)
 
2882
            err = (*resource->hooks->move_resource)(resource, resnew,
 
2883
                                                    &multi_response);
 
2884
        else
 
2885
            err = (*resource->hooks->copy_resource)(resource, resnew, depth,
 
2886
                                                    &multi_response);
 
2887
    }
 
2888
 
 
2889
    /* perform any auto-versioning cleanup */
 
2890
    err2 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
 
2891
                            0 /*unlock*/, &dst_av_info);
 
2892
 
 
2893
    if (is_move) {
 
2894
        err3 = dav_auto_checkin(r, NULL, err != NULL /* undo if error */,
 
2895
                                0 /*unlock*/, &src_av_info);
 
2896
    }
 
2897
    else
 
2898
        err3 = NULL;
 
2899
 
 
2900
    /* check for error from remove/copy/move operations */
 
2901
    if (err != NULL) {
 
2902
        if (lockdb != NULL)
 
2903
            (*lockdb->hooks->close_lockdb)(lockdb);
 
2904
 
 
2905
        err = dav_push_error(r->pool, err->status, 0,
 
2906
                             apr_psprintf(r->pool,
 
2907
                                          "Could not MOVE/COPY %s.",
 
2908
                                          ap_escape_html(r->pool, r->uri)),
 
2909
                             err);
 
2910
        return dav_handle_err(r, err, multi_response);
 
2911
    }
 
2912
 
 
2913
    /* check for errors from auto-versioning */
 
2914
    if (err2 != NULL) {
 
2915
        /* just log a warning */
 
2916
        err = dav_push_error(r->pool, err2->status, 0,
 
2917
                             "The MOVE/COPY was successful, but there was a "
 
2918
                             "problem automatically checking in the "
 
2919
                             "source parent collection.",
 
2920
                             err2);
 
2921
        dav_log_err(r, err, APLOG_WARNING);
 
2922
    }
 
2923
    if (err3 != NULL) {
 
2924
        /* just log a warning */
 
2925
        err = dav_push_error(r->pool, err3->status, 0,
 
2926
                             "The MOVE/COPY was successful, but there was a "
 
2927
                             "problem automatically checking in the "
 
2928
                             "destination or its parent collection.",
 
2929
                             err3);
 
2930
        dav_log_err(r, err, APLOG_WARNING);
 
2931
    }
 
2932
 
 
2933
    /* propagate any indirect locks at the target */
 
2934
    if (lockdb != NULL) {
 
2935
 
 
2936
        /* notify lock system that we have created/replaced a resource */
 
2937
        err = dav_notify_created(r, lockdb, resnew, resnew_state, depth);
 
2938
 
 
2939
        (*lockdb->hooks->close_lockdb)(lockdb);
 
2940
 
 
2941
        if (err != NULL) {
 
2942
            /* The move/copy was successful, but the locking failed. */
 
2943
            err = dav_push_error(r->pool, err->status, 0,
 
2944
                                 "The MOVE/COPY was successful, but there "
 
2945
                                 "was a problem updating the lock "
 
2946
                                 "information.",
 
2947
                                 err);
 
2948
            return dav_handle_err(r, err, NULL);
 
2949
        }
 
2950
    }
 
2951
 
 
2952
    /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
 
2953
    return dav_created(r, lookup.rnew->uri, "Destination",
 
2954
                       resnew_state == DAV_RESOURCE_EXISTS);
 
2955
}
 
2956
 
 
2957
/* dav_method_lock:  Handler to implement the DAV LOCK method
 
2958
 *    Returns appropriate HTTP_* response.
 
2959
 */
 
2960
static int dav_method_lock(request_rec *r)
 
2961
{
 
2962
    dav_error *err;
 
2963
    dav_resource *resource;
 
2964
    const dav_hooks_locks *locks_hooks;
 
2965
    int result;
 
2966
    int depth;
 
2967
    int new_lock_request = 0;
 
2968
    apr_xml_doc *doc;
 
2969
    dav_lock *lock;
 
2970
    dav_response *multi_response = NULL;
 
2971
    dav_lockdb *lockdb;
 
2972
    int resource_state;
 
2973
 
 
2974
    /* If no locks provider, decline the request */
 
2975
    locks_hooks = DAV_GET_HOOKS_LOCKS(r);
 
2976
    if (locks_hooks == NULL)
 
2977
        return DECLINED;
 
2978
 
 
2979
    if ((result = ap_xml_parse_input(r, &doc)) != OK)
 
2980
        return result;
 
2981
 
 
2982
    depth = dav_get_depth(r, DAV_INFINITY);
 
2983
    if (depth != 0 && depth != DAV_INFINITY) {
 
2984
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
2985
                      "Depth must be 0 or \"infinity\" for LOCK.");
 
2986
        return HTTP_BAD_REQUEST;
 
2987
    }
 
2988
 
 
2989
    /* Ask repository module to resolve the resource */
 
2990
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
2991
                           &resource);
 
2992
    if (err != NULL)
 
2993
        return dav_handle_err(r, err, NULL);
 
2994
 
 
2995
    /*
 
2996
     * Open writable. Unless an error occurs, we'll be
 
2997
     * writing into the database.
 
2998
     */
 
2999
    if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
 
3000
        /* ### add a higher-level description? */
 
3001
        return dav_handle_err(r, err, NULL);
 
3002
    }
 
3003
 
 
3004
    if (doc != NULL) {
 
3005
        if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
 
3006
                                               &lock)) != NULL) {
 
3007
            /* ### add a higher-level description to err? */
 
3008
            goto error;
 
3009
        }
 
3010
        new_lock_request = 1;
 
3011
 
 
3012
        lock->auth_user = apr_pstrdup(r->pool, r->user);
 
3013
    }
 
3014
 
 
3015
    resource_state = dav_get_resource_state(r, resource);
 
3016
 
 
3017
    /*
 
3018
     * Check If-Headers and existing locks.
 
3019
     *
 
3020
     * If this will create a locknull resource, then the LOCK will affect
 
3021
     * the parent collection (much like a PUT/MKCOL). For that case, we must
 
3022
     * validate the parent resource's conditions.
 
3023
     */
 
3024
    if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
 
3025
                                    (resource_state == DAV_RESOURCE_NULL
 
3026
                                     ? DAV_VALIDATE_PARENT
 
3027
                                     : DAV_VALIDATE_RESOURCE)
 
3028
                                    | (new_lock_request ? lock->scope : 0)
 
3029
                                    | DAV_VALIDATE_ADD_LD,
 
3030
                                    lockdb)) != OK) {
 
3031
        err = dav_push_error(r->pool, err->status, 0,
 
3032
                             apr_psprintf(r->pool,
 
3033
                                          "Could not LOCK %s due to a failed "
 
3034
                                          "precondition (e.g. other locks).",
 
3035
                                          ap_escape_html(r->pool, r->uri)),
 
3036
                             err);
 
3037
        goto error;
 
3038
    }
 
3039
 
 
3040
    if (new_lock_request == 0) {
 
3041
        dav_locktoken_list *ltl;
 
3042
 
 
3043
        /*
 
3044
         * Refresh request
 
3045
         * ### Assumption:  We can renew multiple locks on the same resource
 
3046
         * ### at once. First harvest all the positive lock-tokens given in
 
3047
         * ### the If header. Then modify the lock entries for this resource
 
3048
         * ### with the new Timeout val.
 
3049
         */
 
3050
 
 
3051
        if ((err = dav_get_locktoken_list(r, &ltl)) != NULL) {
 
3052
            err = dav_push_error(r->pool, err->status, 0,
 
3053
                                 apr_psprintf(r->pool,
 
3054
                                              "The lock refresh for %s failed "
 
3055
                                              "because no lock tokens were "
 
3056
                                              "specified in an \"If:\" "
 
3057
                                              "header.",
 
3058
                                              ap_escape_html(r->pool, r->uri)),
 
3059
                                 err);
 
3060
            goto error;
 
3061
        }
 
3062
 
 
3063
        if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
 
3064
                                                 dav_get_timeout(r),
 
3065
                                                 &lock)) != NULL) {
 
3066
            /* ### add a higher-level description to err? */
 
3067
            goto error;
 
3068
        }
 
3069
    } else {
 
3070
        /* New lock request */
 
3071
        char *locktoken_txt;
 
3072
        dav_dir_conf *conf;
 
3073
 
 
3074
        conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
 
3075
                                                    &dav_module);
 
3076
 
 
3077
        /* apply lower bound (if any) from DAVMinTimeout directive */
 
3078
        if (lock->timeout != DAV_TIMEOUT_INFINITE
 
3079
            && lock->timeout < time(NULL) + conf->locktimeout)
 
3080
            lock->timeout = time(NULL) + conf->locktimeout;
 
3081
 
 
3082
        err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
 
3083
        if (err != NULL) {
 
3084
            /* ### add a higher-level description to err? */
 
3085
            goto error;
 
3086
        }
 
3087
 
 
3088
        locktoken_txt = apr_pstrcat(r->pool, "<",
 
3089
                                    (*locks_hooks->format_locktoken)(r->pool,
 
3090
                                        lock->locktoken),
 
3091
                                    ">", NULL);
 
3092
 
 
3093
        apr_table_set(r->headers_out, "Lock-Token", locktoken_txt);
 
3094
    }
 
3095
 
 
3096
    (*locks_hooks->close_lockdb)(lockdb);
 
3097
 
 
3098
    r->status = HTTP_OK;
 
3099
    ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
 
3100
 
 
3101
    ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
 
3102
    if (lock == NULL)
 
3103
        ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
 
3104
    else {
 
3105
        ap_rprintf(r,
 
3106
                   "<D:lockdiscovery>" DEBUG_CR
 
3107
                   "%s" DEBUG_CR
 
3108
                   "</D:lockdiscovery>" DEBUG_CR,
 
3109
                   dav_lock_get_activelock(r, lock, NULL));
 
3110
    }
 
3111
    ap_rputs("</D:prop>", r);
 
3112
 
 
3113
    /* the response has been sent. */
 
3114
    return DONE;
 
3115
 
 
3116
  error:
 
3117
    (*locks_hooks->close_lockdb)(lockdb);
 
3118
    return dav_handle_err(r, err, multi_response);
 
3119
}
 
3120
 
 
3121
/* dav_method_unlock:  Handler to implement the DAV UNLOCK method
 
3122
 *    Returns appropriate HTTP_* response.
 
3123
 */
 
3124
static int dav_method_unlock(request_rec *r)
 
3125
{
 
3126
    dav_error *err;
 
3127
    dav_resource *resource;
 
3128
    const dav_hooks_locks *locks_hooks;
 
3129
    int result;
 
3130
    const char *const_locktoken_txt;
 
3131
    char *locktoken_txt;
 
3132
    dav_locktoken *locktoken = NULL;
 
3133
    int resource_state;
 
3134
    dav_response *multi_response;
 
3135
 
 
3136
    /* If no locks provider, decline the request */
 
3137
    locks_hooks = DAV_GET_HOOKS_LOCKS(r);
 
3138
    if (locks_hooks == NULL)
 
3139
        return DECLINED;
 
3140
 
 
3141
    if ((const_locktoken_txt = apr_table_get(r->headers_in,
 
3142
                                             "Lock-Token")) == NULL) {
 
3143
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3144
                      "Unlock failed (%s):  "
 
3145
                      "No Lock-Token specified in header", r->filename);
 
3146
        return HTTP_BAD_REQUEST;
 
3147
    }
 
3148
 
 
3149
    locktoken_txt = apr_pstrdup(r->pool, const_locktoken_txt);
 
3150
    if (locktoken_txt[0] != '<') {
 
3151
        /* ### should provide more specifics... */
 
3152
        return HTTP_BAD_REQUEST;
 
3153
    }
 
3154
    locktoken_txt++;
 
3155
 
 
3156
    if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
 
3157
        /* ### should provide more specifics... */
 
3158
        return HTTP_BAD_REQUEST;
 
3159
    }
 
3160
    locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
 
3161
 
 
3162
    if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
 
3163
                                               &locktoken)) != NULL) {
 
3164
        err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
 
3165
                             apr_psprintf(r->pool,
 
3166
                                          "The UNLOCK on %s failed -- an "
 
3167
                                          "invalid lock token was specified "
 
3168
                                          "in the \"If:\" header.",
 
3169
                                          ap_escape_html(r->pool, r->uri)),
 
3170
                             err);
 
3171
        return dav_handle_err(r, err, NULL);
 
3172
    }
 
3173
 
 
3174
    /* Ask repository module to resolve the resource */
 
3175
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
3176
                           &resource);
 
3177
    if (err != NULL)
 
3178
        return dav_handle_err(r, err, NULL);
 
3179
 
 
3180
    resource_state = dav_get_resource_state(r, resource);
 
3181
 
 
3182
    /*
 
3183
     * Check If-Headers and existing locks.
 
3184
     *
 
3185
     * Note: depth == 0 normally requires no multistatus response. However,
 
3186
     * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
 
3187
     * other than the Request-URI, thereby requiring a multistatus.
 
3188
     *
 
3189
     * If the resource is a locknull resource, then the UNLOCK will affect
 
3190
     * the parent collection (much like a delete). For that case, we must
 
3191
     * validate the parent resource's conditions.
 
3192
     */
 
3193
    if ((err = dav_validate_request(r, resource, 0, locktoken,
 
3194
                                    &multi_response,
 
3195
                                    resource_state == DAV_RESOURCE_LOCK_NULL
 
3196
                                    ? DAV_VALIDATE_PARENT
 
3197
                                    : DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
 
3198
        /* ### add a higher-level description? */
 
3199
        return dav_handle_err(r, err, multi_response);
 
3200
    }
 
3201
 
 
3202
    /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
 
3203
     *     _all_ resources locked by locktoken are released.  It does not say
 
3204
     *     resource has to be the root of an infinte lock.  Thus, an UNLOCK
 
3205
     *     on any part of an infinte lock will remove the lock on all resources.
 
3206
     *
 
3207
     *     For us, if r->filename represents an indirect lock (part of an infinity lock),
 
3208
     *     we must actually perform an UNLOCK on the direct lock for this resource.
 
3209
     */
 
3210
    if ((result = dav_unlock(r, resource, locktoken)) != OK) {
 
3211
        return result;
 
3212
    }
 
3213
 
 
3214
    return HTTP_NO_CONTENT;
 
3215
}
 
3216
 
 
3217
static int dav_method_vsn_control(request_rec *r)
 
3218
{
 
3219
    dav_resource *resource;
 
3220
    int resource_state;
 
3221
    dav_auto_version_info av_info;
 
3222
    const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
 
3223
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
3224
    dav_error *err;
 
3225
    apr_xml_doc *doc;
 
3226
    const char *target = NULL;
 
3227
    int result;
 
3228
 
 
3229
    /* if no versioning provider, decline the request */
 
3230
    if (vsn_hooks == NULL)
 
3231
        return DECLINED;
 
3232
 
 
3233
    /* ask repository module to resolve the resource */
 
3234
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
3235
                           &resource);
 
3236
    if (err != NULL)
 
3237
        return dav_handle_err(r, err, NULL);
 
3238
 
 
3239
    /* remember the pre-creation resource state */
 
3240
    resource_state = dav_get_resource_state(r, resource);
 
3241
 
 
3242
    /* parse the request body (may be a version-control element) */
 
3243
    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
 
3244
        return result;
 
3245
    }
 
3246
    /* note: doc == NULL if no request body */
 
3247
 
 
3248
    if (doc != NULL) {
 
3249
        const apr_xml_elem *child;
 
3250
        apr_size_t tsize;
 
3251
 
 
3252
        if (!dav_validate_root(doc, "version-control")) {
 
3253
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3254
                          "The request body does not contain "
 
3255
                          "a \"version-control\" element.");
 
3256
            return HTTP_BAD_REQUEST;
 
3257
        }
 
3258
 
 
3259
        /* get the version URI */
 
3260
        if ((child = dav_find_child(doc->root, "version")) == NULL) {
 
3261
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3262
                          "The \"version-control\" element does not contain "
 
3263
                          "a \"version\" element.");
 
3264
            return HTTP_BAD_REQUEST;
 
3265
        }
 
3266
 
 
3267
        if ((child = dav_find_child(child, "href")) == NULL) {
 
3268
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3269
                          "The \"version\" element does not contain "
 
3270
                          "an \"href\" element.");
 
3271
            return HTTP_BAD_REQUEST;
 
3272
        }
 
3273
 
 
3274
        /* get version URI */
 
3275
        apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
 
3276
                        &target, &tsize);
 
3277
        if (tsize == 0) {
 
3278
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3279
                          "An \"href\" element does not contain a URI.");
 
3280
            return HTTP_BAD_REQUEST;
 
3281
        }
 
3282
    }
 
3283
 
 
3284
    /* Check request preconditions */
 
3285
 
 
3286
    /* ### need a general mechanism for reporting precondition violations
 
3287
     * ### (should be returning XML document for 403/409 responses)
 
3288
     */
 
3289
 
 
3290
    /* if not versioning existing resource, must specify version to select */
 
3291
    if (!resource->exists && target == NULL) {
 
3292
        err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
 
3293
                            "<DAV:initial-version-required/>");
 
3294
        return dav_handle_err(r, err, NULL);
 
3295
    }
 
3296
    else if (resource->exists) {
 
3297
        /* cannot add resource to existing version history */
 
3298
        if (target != NULL) {
 
3299
            err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
 
3300
                                "<DAV:cannot-add-to-existing-history/>");
 
3301
            return dav_handle_err(r, err, NULL);
 
3302
        }
 
3303
 
 
3304
        /* resource must be unversioned and versionable, or version selector */
 
3305
        if (resource->type != DAV_RESOURCE_TYPE_REGULAR
 
3306
            || (!resource->versioned && !(vsn_hooks->versionable)(resource))) {
 
3307
            err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
 
3308
                                "<DAV:must-be-versionable/>");
 
3309
            return dav_handle_err(r, err, NULL);
 
3310
        }
 
3311
 
 
3312
        /* the DeltaV spec says if resource is a version selector,
 
3313
         * then VERSION-CONTROL is a no-op
 
3314
         */
 
3315
        if (resource->versioned) {
 
3316
            /* set the Cache-Control header, per the spec */
 
3317
            apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
 
3318
 
 
3319
            /* no body */
 
3320
            ap_set_content_length(r, 0);
 
3321
 
 
3322
            return DONE;
 
3323
        }
 
3324
    }
 
3325
 
 
3326
    /* Check If-Headers and existing locks */
 
3327
    /* Note: depth == 0. Implies no need for a multistatus response. */
 
3328
    if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
 
3329
                                    resource_state == DAV_RESOURCE_NULL ?
 
3330
                                    DAV_VALIDATE_PARENT :
 
3331
                                    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
 
3332
        return dav_handle_err(r, err, NULL);
 
3333
    }
 
3334
 
 
3335
    /* if in versioned collection, make sure parent is checked out */
 
3336
    if ((err = dav_auto_checkout(r, resource, 1 /* parent_only */,
 
3337
                                 &av_info)) != NULL) {
 
3338
        return dav_handle_err(r, err, NULL);
 
3339
    }
 
3340
 
 
3341
    /* attempt to version-control the resource */
 
3342
    if ((err = (*vsn_hooks->vsn_control)(resource, target)) != NULL) {
 
3343
        dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info);
 
3344
        err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
 
3345
                             apr_psprintf(r->pool,
 
3346
                                          "Could not VERSION-CONTROL resource %s.",
 
3347
                                          ap_escape_html(r->pool, r->uri)),
 
3348
                             err);
 
3349
        return dav_handle_err(r, err, NULL);
 
3350
    }
 
3351
 
 
3352
    /* revert writability of parent directory */
 
3353
    err = dav_auto_checkin(r, resource, 0 /*undo*/, 0 /*unlock*/, &av_info);
 
3354
    if (err != NULL) {
 
3355
        /* just log a warning */
 
3356
        err = dav_push_error(r->pool, err->status, 0,
 
3357
                             "The VERSION-CONTROL was successful, but there "
 
3358
                             "was a problem automatically checking in "
 
3359
                             "the parent collection.",
 
3360
                             err);
 
3361
        dav_log_err(r, err, APLOG_WARNING);
 
3362
    }
 
3363
 
 
3364
    /* if the resource is lockable, let lock system know of new resource */
 
3365
    if (locks_hooks != NULL
 
3366
        && (*locks_hooks->get_supportedlock)(resource) != NULL) {
 
3367
        dav_lockdb *lockdb;
 
3368
 
 
3369
        if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
 
3370
            /* The resource creation was successful, but the locking failed. */
 
3371
            err = dav_push_error(r->pool, err->status, 0,
 
3372
                                 "The VERSION-CONTROL was successful, but there "
 
3373
                                 "was a problem opening the lock database "
 
3374
                                 "which prevents inheriting locks from the "
 
3375
                                 "parent resources.",
 
3376
                                 err);
 
3377
            return dav_handle_err(r, err, NULL);
 
3378
        }
 
3379
 
 
3380
        /* notify lock system that we have created/replaced a resource */
 
3381
        err = dav_notify_created(r, lockdb, resource, resource_state, 0);
 
3382
 
 
3383
        (*locks_hooks->close_lockdb)(lockdb);
 
3384
 
 
3385
        if (err != NULL) {
 
3386
            /* The dir creation was successful, but the locking failed. */
 
3387
            err = dav_push_error(r->pool, err->status, 0,
 
3388
                                 "The VERSION-CONTROL was successful, but there "
 
3389
                                 "was a problem updating its lock "
 
3390
                                 "information.",
 
3391
                                 err);
 
3392
            return dav_handle_err(r, err, NULL);
 
3393
        }
 
3394
    }
 
3395
 
 
3396
    /* set the Cache-Control header, per the spec */
 
3397
    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
 
3398
 
 
3399
    /* return an appropriate response (HTTP_CREATED) */
 
3400
    return dav_created(r, resource->uri, "Version selector", 0 /*replaced*/);
 
3401
}
 
3402
 
 
3403
/* handle the CHECKOUT method */
 
3404
static int dav_method_checkout(request_rec *r)
 
3405
{
 
3406
    dav_resource *resource;
 
3407
    dav_resource *working_resource;
 
3408
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
3409
    dav_error *err;
 
3410
    int result;
 
3411
    apr_xml_doc *doc;
 
3412
    int apply_to_vsn = 0;
 
3413
    int is_unreserved = 0;
 
3414
    int is_fork_ok = 0;
 
3415
    int create_activity = 0;
 
3416
    apr_array_header_t *activities = NULL;
 
3417
 
 
3418
    /* If no versioning provider, decline the request */
 
3419
    if (vsn_hooks == NULL)
 
3420
        return DECLINED;
 
3421
 
 
3422
    if ((result = ap_xml_parse_input(r, &doc)) != OK)
 
3423
        return result;
 
3424
 
 
3425
    if (doc != NULL) {
 
3426
        const apr_xml_elem *aset;
 
3427
 
 
3428
        if (!dav_validate_root(doc, "checkout")) {
 
3429
            /* This supplies additional information for the default msg. */
 
3430
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3431
                          "The request body, if present, must be a "
 
3432
                          "DAV:checkout element.");
 
3433
            return HTTP_BAD_REQUEST;
 
3434
        }
 
3435
 
 
3436
        if (dav_find_child(doc->root, "apply-to-version") != NULL) {
 
3437
            if (apr_table_get(r->headers_in, "label") != NULL) {
 
3438
                /* ### we want generic 403/409 XML reporting here */
 
3439
                /* ### DAV:must-not-have-label-and-apply-to-version */
 
3440
                return dav_error_response(r, HTTP_CONFLICT,
 
3441
                                          "DAV:apply-to-version cannot be "
 
3442
                                          "used in conjunction with a "
 
3443
                                          "Label header.");
 
3444
            }
 
3445
            apply_to_vsn = 1;
 
3446
        }
 
3447
 
 
3448
        is_unreserved = dav_find_child(doc->root, "unreserved") != NULL;
 
3449
        is_fork_ok = dav_find_child(doc->root, "fork-ok") != NULL;
 
3450
 
 
3451
        if ((aset = dav_find_child(doc->root, "activity-set")) != NULL) {
 
3452
            if (dav_find_child(aset, "new") != NULL) {
 
3453
                create_activity = 1;
 
3454
            }
 
3455
            else {
 
3456
                const apr_xml_elem *child = aset->first_child;
 
3457
 
 
3458
                activities = apr_array_make(r->pool, 1, sizeof(const char *));
 
3459
 
 
3460
                for (; child != NULL; child = child->next) {
 
3461
                    if (child->ns == APR_XML_NS_DAV_ID
 
3462
                        && strcmp(child->name, "href") == 0) {
 
3463
                        const char *href;
 
3464
 
 
3465
                        href = dav_xml_get_cdata(child, r->pool,
 
3466
                                                 1 /* strip_white */);
 
3467
                        *(const char **)apr_array_push(activities) = href;
 
3468
                    }
 
3469
                }
 
3470
 
 
3471
                if (activities->nelts == 0) {
 
3472
                    /* no href's is a DTD violation:
 
3473
                       <!ELEMENT activity-set (href+ | new)>
 
3474
                    */
 
3475
 
 
3476
                    /* This supplies additional info for the default msg. */
 
3477
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3478
                                  "Within the DAV:activity-set element, the "
 
3479
                                  "DAV:new element must be used, or at least "
 
3480
                                  "one DAV:href must be specified.");
 
3481
                    return HTTP_BAD_REQUEST;
 
3482
                }
 
3483
            }
 
3484
        }
 
3485
    }
 
3486
 
 
3487
    /* Ask repository module to resolve the resource */
 
3488
    err = dav_get_resource(r, 1 /*label_allowed*/, apply_to_vsn, &resource);
 
3489
    if (err != NULL)
 
3490
        return dav_handle_err(r, err, NULL);
 
3491
 
 
3492
    if (!resource->exists) {
 
3493
        /* Apache will supply a default error for this. */
 
3494
        return HTTP_NOT_FOUND;
 
3495
    }
 
3496
 
 
3497
    /* Check the state of the resource: must be a file or collection,
 
3498
     * must be versioned, and must not already be checked out.
 
3499
     */
 
3500
    if (resource->type != DAV_RESOURCE_TYPE_REGULAR
 
3501
        && resource->type != DAV_RESOURCE_TYPE_VERSION) {
 
3502
        return dav_error_response(r, HTTP_CONFLICT,
 
3503
                                  "Cannot checkout this type of resource.");
 
3504
    }
 
3505
 
 
3506
    if (!resource->versioned) {
 
3507
        return dav_error_response(r, HTTP_CONFLICT,
 
3508
                                  "Cannot checkout unversioned resource.");
 
3509
    }
 
3510
 
 
3511
    if (resource->working) {
 
3512
        return dav_error_response(r, HTTP_CONFLICT,
 
3513
                                  "The resource is already checked out to the workspace.");
 
3514
    }
 
3515
 
 
3516
    /* ### do lock checks, once behavior is defined */
 
3517
 
 
3518
    /* Do the checkout */
 
3519
    if ((err = (*vsn_hooks->checkout)(resource, 0 /*auto_checkout*/,
 
3520
                                      is_unreserved, is_fork_ok,
 
3521
                                      create_activity, activities,
 
3522
                                      &working_resource)) != NULL) {
 
3523
        err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
 
3524
                             apr_psprintf(r->pool,
 
3525
                                          "Could not CHECKOUT resource %s.",
 
3526
                                          ap_escape_html(r->pool, r->uri)),
 
3527
                             err);
 
3528
        return dav_handle_err(r, err, NULL);
 
3529
    }
 
3530
 
 
3531
    /* set the Cache-Control header, per the spec */
 
3532
    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
 
3533
 
 
3534
    /* if no working resource created, return OK,
 
3535
     * else return CREATED with working resource URL in Location header
 
3536
     */
 
3537
    if (working_resource == NULL) {
 
3538
        /* no body */
 
3539
        ap_set_content_length(r, 0);
 
3540
        return DONE;
 
3541
    }
 
3542
 
 
3543
    return dav_created(r, working_resource->uri, "Checked-out resource", 0);
 
3544
}
 
3545
 
 
3546
/* handle the UNCHECKOUT method */
 
3547
static int dav_method_uncheckout(request_rec *r)
 
3548
{
 
3549
    dav_resource *resource;
 
3550
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
3551
    dav_error *err;
 
3552
    int result;
 
3553
 
 
3554
    /* If no versioning provider, decline the request */
 
3555
    if (vsn_hooks == NULL)
 
3556
        return DECLINED;
 
3557
 
 
3558
    if ((result = ap_discard_request_body(r)) != OK) {
 
3559
        return result;
 
3560
    }
 
3561
 
 
3562
    /* Ask repository module to resolve the resource */
 
3563
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
3564
                           &resource);
 
3565
    if (err != NULL)
 
3566
        return dav_handle_err(r, err, NULL);
 
3567
 
 
3568
    if (!resource->exists) {
 
3569
        /* Apache will supply a default error for this. */
 
3570
        return HTTP_NOT_FOUND;
 
3571
    }
 
3572
 
 
3573
    /* Check the state of the resource: must be a file or collection,
 
3574
     * must be versioned, and must be checked out.
 
3575
     */
 
3576
    if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
 
3577
        return dav_error_response(r, HTTP_CONFLICT,
 
3578
                                  "Cannot uncheckout this type of resource.");
 
3579
    }
 
3580
 
 
3581
    if (!resource->versioned) {
 
3582
        return dav_error_response(r, HTTP_CONFLICT,
 
3583
                                  "Cannot uncheckout unversioned resource.");
 
3584
    }
 
3585
 
 
3586
    if (!resource->working) {
 
3587
        return dav_error_response(r, HTTP_CONFLICT,
 
3588
                                  "The resource is not checked out to the workspace.");
 
3589
    }
 
3590
 
 
3591
    /* ### do lock checks, once behavior is defined */
 
3592
 
 
3593
    /* Do the uncheckout */
 
3594
    if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
 
3595
        err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
 
3596
                             apr_psprintf(r->pool,
 
3597
                                          "Could not UNCHECKOUT resource %s.",
 
3598
                                          ap_escape_html(r->pool, r->uri)),
 
3599
                             err);
 
3600
        return dav_handle_err(r, err, NULL);
 
3601
    }
 
3602
 
 
3603
    /* no body */
 
3604
    ap_set_content_length(r, 0);
 
3605
 
 
3606
    return DONE;
 
3607
}
 
3608
 
 
3609
/* handle the CHECKIN method */
 
3610
static int dav_method_checkin(request_rec *r)
 
3611
{
 
3612
    dav_resource *resource;
 
3613
    dav_resource *new_version;
 
3614
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
3615
    dav_error *err;
 
3616
    int result;
 
3617
    apr_xml_doc *doc;
 
3618
    int keep_checked_out = 0;
 
3619
 
 
3620
    /* If no versioning provider, decline the request */
 
3621
    if (vsn_hooks == NULL)
 
3622
        return DECLINED;
 
3623
 
 
3624
    if ((result = ap_xml_parse_input(r, &doc)) != OK)
 
3625
        return result;
 
3626
 
 
3627
    if (doc != NULL) {
 
3628
        if (!dav_validate_root(doc, "checkin")) {
 
3629
            /* This supplies additional information for the default msg. */
 
3630
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3631
                          "The request body, if present, must be a "
 
3632
                          "DAV:checkin element.");
 
3633
            return HTTP_BAD_REQUEST;
 
3634
        }
 
3635
 
 
3636
        keep_checked_out = dav_find_child(doc->root, "keep-checked-out") != NULL;
 
3637
    }
 
3638
 
 
3639
    /* Ask repository module to resolve the resource */
 
3640
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
3641
                           &resource);
 
3642
    if (err != NULL)
 
3643
        return dav_handle_err(r, err, NULL);
 
3644
 
 
3645
    if (!resource->exists) {
 
3646
        /* Apache will supply a default error for this. */
 
3647
        return HTTP_NOT_FOUND;
 
3648
    }
 
3649
 
 
3650
    /* Check the state of the resource: must be a file or collection,
 
3651
     * must be versioned, and must be checked out.
 
3652
     */
 
3653
    if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
 
3654
        return dav_error_response(r, HTTP_CONFLICT,
 
3655
                                  "Cannot checkin this type of resource.");
 
3656
    }
 
3657
 
 
3658
    if (!resource->versioned) {
 
3659
        return dav_error_response(r, HTTP_CONFLICT,
 
3660
                                  "Cannot checkin unversioned resource.");
 
3661
    }
 
3662
 
 
3663
    if (!resource->working) {
 
3664
        return dav_error_response(r, HTTP_CONFLICT,
 
3665
                                  "The resource is not checked out.");
 
3666
    }
 
3667
 
 
3668
    /* ### do lock checks, once behavior is defined */
 
3669
 
 
3670
    /* Do the checkin */
 
3671
    if ((err = (*vsn_hooks->checkin)(resource, keep_checked_out, &new_version))
 
3672
        != NULL) {
 
3673
        err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
 
3674
                             apr_psprintf(r->pool,
 
3675
                                          "Could not CHECKIN resource %s.",
 
3676
                                          ap_escape_html(r->pool, r->uri)),
 
3677
                             err);
 
3678
        return dav_handle_err(r, err, NULL);
 
3679
    }
 
3680
 
 
3681
    return dav_created(r, new_version->uri, "Version", 0);
 
3682
}
 
3683
 
 
3684
static int dav_method_update(request_rec *r)
 
3685
{
 
3686
    dav_resource *resource;
 
3687
    dav_resource *version = NULL;
 
3688
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
3689
    apr_xml_doc *doc;
 
3690
    apr_xml_elem *child;
 
3691
    int is_label = 0;
 
3692
    int depth;
 
3693
    int result;
 
3694
    apr_size_t tsize;
 
3695
    const char *target;
 
3696
    dav_response *multi_response;
 
3697
    dav_error *err;
 
3698
    dav_lookup_result lookup;
 
3699
 
 
3700
    /* If no versioning provider, or UPDATE not supported,
 
3701
     * decline the request */
 
3702
    if (vsn_hooks == NULL || vsn_hooks->update == NULL)
 
3703
        return DECLINED;
 
3704
 
 
3705
    if ((depth = dav_get_depth(r, 0)) < 0) {
 
3706
        /* dav_get_depth() supplies additional information for the
 
3707
         * default message. */
 
3708
        return HTTP_BAD_REQUEST;
 
3709
    }
 
3710
 
 
3711
    /* parse the request body */
 
3712
    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
 
3713
        return result;
 
3714
    }
 
3715
 
 
3716
    if (doc == NULL || !dav_validate_root(doc, "update")) {
 
3717
        /* This supplies additional information for the default message. */
 
3718
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3719
                      "The request body does not contain "
 
3720
                      "an \"update\" element.");
 
3721
        return HTTP_BAD_REQUEST;
 
3722
    }
 
3723
 
 
3724
    /* check for label-name or version element, but not both */
 
3725
    if ((child = dav_find_child(doc->root, "label-name")) != NULL)
 
3726
        is_label = 1;
 
3727
    else if ((child = dav_find_child(doc->root, "version")) != NULL) {
 
3728
        /* get the href element */
 
3729
        if ((child = dav_find_child(child, "href")) == NULL) {
 
3730
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3731
                          "The version element does not contain "
 
3732
                          "an \"href\" element.");
 
3733
            return HTTP_BAD_REQUEST;
 
3734
        }
 
3735
    }
 
3736
    else {
 
3737
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3738
                      "The \"update\" element does not contain "
 
3739
                      "a \"label-name\" or \"version\" element.");
 
3740
        return HTTP_BAD_REQUEST;
 
3741
    }
 
3742
 
 
3743
    /* a depth greater than zero is only allowed for a label */
 
3744
    if (!is_label && depth != 0) {
 
3745
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3746
                      "Depth must be zero for UPDATE with a version");
 
3747
        return HTTP_BAD_REQUEST;
 
3748
    }
 
3749
 
 
3750
    /* get the target value (a label or a version URI) */
 
3751
    apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
 
3752
                    &target, &tsize);
 
3753
    if (tsize == 0) {
 
3754
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3755
                      "A \"label-name\" or \"href\" element does not contain "
 
3756
                      "any content.");
 
3757
        return HTTP_BAD_REQUEST;
 
3758
    }
 
3759
 
 
3760
    /* Ask repository module to resolve the resource */
 
3761
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
3762
                           &resource);
 
3763
    if (err != NULL)
 
3764
        return dav_handle_err(r, err, NULL);
 
3765
 
 
3766
    if (!resource->exists) {
 
3767
        /* Apache will supply a default error for this. */
 
3768
        return HTTP_NOT_FOUND;
 
3769
    }
 
3770
 
 
3771
    /* ### need a general mechanism for reporting precondition violations
 
3772
     * ### (should be returning XML document for 403/409 responses)
 
3773
     */
 
3774
    if (resource->type != DAV_RESOURCE_TYPE_REGULAR
 
3775
        || !resource->versioned || resource->working) {
 
3776
        return dav_error_response(r, HTTP_CONFLICT,
 
3777
                                  "<DAV:must-be-checked-in-version-controlled-resource>");
 
3778
    }
 
3779
 
 
3780
    /* if target is a version, resolve the version resource */
 
3781
    /* ### dav_lookup_uri only allows absolute URIs; is that OK? */
 
3782
    if (!is_label) {
 
3783
        lookup = dav_lookup_uri(target, r, 0 /* must_be_absolute */);
 
3784
        if (lookup.rnew == NULL) {
 
3785
            if (lookup.err.status == HTTP_BAD_REQUEST) {
 
3786
                /* This supplies additional information for the default message. */
 
3787
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3788
                              "%s", lookup.err.desc);
 
3789
                return HTTP_BAD_REQUEST;
 
3790
            }
 
3791
 
 
3792
            /* ### this assumes that dav_lookup_uri() only generates a status
 
3793
             * ### that Apache can provide a status line for!! */
 
3794
 
 
3795
            return dav_error_response(r, lookup.err.status, lookup.err.desc);
 
3796
        }
 
3797
        if (lookup.rnew->status != HTTP_OK) {
 
3798
            /* ### how best to report this... */
 
3799
            return dav_error_response(r, lookup.rnew->status,
 
3800
                                      "Version URI had an error.");
 
3801
        }
 
3802
 
 
3803
        /* resolve version resource */
 
3804
        err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
 
3805
                               0 /* use_checked_in */, &version);
 
3806
        if (err != NULL)
 
3807
            return dav_handle_err(r, err, NULL);
 
3808
 
 
3809
        /* NULL out target, since we're using a version resource */
 
3810
        target = NULL;
 
3811
    }
 
3812
 
 
3813
    /* do the UPDATE operation */
 
3814
    err = (*vsn_hooks->update)(resource, version, target, depth, &multi_response);
 
3815
 
 
3816
    if (err != NULL) {
 
3817
        err = dav_push_error(r->pool, err->status, 0,
 
3818
                             apr_psprintf(r->pool,
 
3819
                                          "Could not UPDATE %s.",
 
3820
                                          ap_escape_html(r->pool, r->uri)),
 
3821
                             err);
 
3822
        return dav_handle_err(r, err, multi_response);
 
3823
    }
 
3824
 
 
3825
    /* set the Cache-Control header, per the spec */
 
3826
    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
 
3827
 
 
3828
    /* no body */
 
3829
    ap_set_content_length(r, 0);
 
3830
 
 
3831
    return DONE;
 
3832
}
 
3833
 
 
3834
/* context maintained during LABEL treewalk */
 
3835
typedef struct dav_label_walker_ctx
 
3836
{
 
3837
    /* input: */
 
3838
    dav_walk_params w;
 
3839
 
 
3840
    /* label being manipulated */
 
3841
    const char *label;
 
3842
 
 
3843
    /* label operation */
 
3844
    int label_op;
 
3845
#define DAV_LABEL_ADD           1
 
3846
#define DAV_LABEL_SET           2
 
3847
#define DAV_LABEL_REMOVE        3
 
3848
 
 
3849
    /* version provider hooks */
 
3850
    const dav_hooks_vsn *vsn_hooks;
 
3851
 
 
3852
} dav_label_walker_ctx;
 
3853
 
 
3854
static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype)
 
3855
{
 
3856
    dav_label_walker_ctx *ctx = wres->walk_ctx;
 
3857
    dav_error *err = NULL;
 
3858
 
 
3859
    /* Check the state of the resource: must be a version or
 
3860
     * non-checkedout version selector
 
3861
     */
 
3862
    /* ### need a general mechanism for reporting precondition violations
 
3863
     * ### (should be returning XML document for 403/409 responses)
 
3864
     */
 
3865
    if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION &&
 
3866
        (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR
 
3867
         || !wres->resource->versioned)) {
 
3868
        err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
 
3869
                            "<DAV:must-be-version-or-version-selector/>");
 
3870
    }
 
3871
    else if (wres->resource->working) {
 
3872
        err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0,
 
3873
                            "<DAV:must-not-be-checked-out/>");
 
3874
    }
 
3875
    else {
 
3876
        /* do the label operation */
 
3877
        if (ctx->label_op == DAV_LABEL_REMOVE)
 
3878
            err = (*ctx->vsn_hooks->remove_label)(wres->resource, ctx->label);
 
3879
        else
 
3880
            err = (*ctx->vsn_hooks->add_label)(wres->resource, ctx->label,
 
3881
                                               ctx->label_op == DAV_LABEL_SET);
 
3882
    }
 
3883
 
 
3884
    if (err != NULL) {
 
3885
        /* ### need utility routine to add response with description? */
 
3886
        dav_add_response(wres, err->status, NULL);
 
3887
        wres->response->desc = err->desc;
 
3888
    }
 
3889
 
 
3890
    return NULL;
 
3891
}
 
3892
 
 
3893
static int dav_method_label(request_rec *r)
 
3894
{
 
3895
    dav_resource *resource;
 
3896
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
3897
    apr_xml_doc *doc;
 
3898
    apr_xml_elem *child;
 
3899
    int depth;
 
3900
    int result;
 
3901
    apr_size_t tsize;
 
3902
    dav_error *err;
 
3903
    dav_label_walker_ctx ctx = { { 0 } };
 
3904
    dav_response *multi_status;
 
3905
 
 
3906
    /* If no versioning provider, or the provider doesn't support
 
3907
     * labels, decline the request */
 
3908
    if (vsn_hooks == NULL || vsn_hooks->add_label == NULL)
 
3909
        return DECLINED;
 
3910
 
 
3911
    /* Ask repository module to resolve the resource */
 
3912
    err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */,
 
3913
                           &resource);
 
3914
    if (err != NULL)
 
3915
        return dav_handle_err(r, err, NULL);
 
3916
    if (!resource->exists) {
 
3917
        /* Apache will supply a default error for this. */
 
3918
        return HTTP_NOT_FOUND;
 
3919
    }
 
3920
 
 
3921
    if ((depth = dav_get_depth(r, 0)) < 0) {
 
3922
        /* dav_get_depth() supplies additional information for the
 
3923
         * default message. */
 
3924
        return HTTP_BAD_REQUEST;
 
3925
    }
 
3926
 
 
3927
    /* parse the request body */
 
3928
    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
 
3929
        return result;
 
3930
    }
 
3931
 
 
3932
    if (doc == NULL || !dav_validate_root(doc, "label")) {
 
3933
        /* This supplies additional information for the default message. */
 
3934
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3935
                      "The request body does not contain "
 
3936
                      "a \"label\" element.");
 
3937
        return HTTP_BAD_REQUEST;
 
3938
    }
 
3939
 
 
3940
    /* check for add, set, or remove element */
 
3941
    if ((child = dav_find_child(doc->root, "add")) != NULL) {
 
3942
        ctx.label_op = DAV_LABEL_ADD;
 
3943
    }
 
3944
    else if ((child = dav_find_child(doc->root, "set")) != NULL) {
 
3945
        ctx.label_op = DAV_LABEL_SET;
 
3946
    }
 
3947
    else if ((child = dav_find_child(doc->root, "remove")) != NULL) {
 
3948
        ctx.label_op = DAV_LABEL_REMOVE;
 
3949
    }
 
3950
    else {
 
3951
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3952
                      "The \"label\" element does not contain "
 
3953
                      "an \"add\", \"set\", or \"remove\" element.");
 
3954
        return HTTP_BAD_REQUEST;
 
3955
    }
 
3956
 
 
3957
    /* get the label string */
 
3958
    if ((child = dav_find_child(child, "label-name")) == NULL) {
 
3959
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3960
                      "The label command element does not contain "
 
3961
                      "a \"label-name\" element.");
 
3962
        return HTTP_BAD_REQUEST;
 
3963
    }
 
3964
 
 
3965
    apr_xml_to_text(r->pool, child, APR_XML_X2T_INNER, NULL, NULL,
 
3966
                    &ctx.label, &tsize);
 
3967
    if (tsize == 0) {
 
3968
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
3969
                      "A \"label-name\" element does not contain "
 
3970
                      "a label name.");
 
3971
        return HTTP_BAD_REQUEST;
 
3972
    }
 
3973
 
 
3974
    /* do the label operation walk */
 
3975
    ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
 
3976
    ctx.w.func = dav_label_walker;
 
3977
    ctx.w.walk_ctx = &ctx;
 
3978
    ctx.w.pool = r->pool;
 
3979
    ctx.w.root = resource;
 
3980
    ctx.vsn_hooks = vsn_hooks;
 
3981
 
 
3982
    err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status);
 
3983
 
 
3984
    if (err != NULL) {
 
3985
        /* some sort of error occurred which terminated the walk */
 
3986
        err = dav_push_error(r->pool, err->status, 0,
 
3987
                             "The LABEL operation was terminated prematurely.",
 
3988
                             err);
 
3989
        return dav_handle_err(r, err, multi_status);
 
3990
    }
 
3991
 
 
3992
    if (multi_status != NULL) {
 
3993
        /* One or more resources had errors. If depth was zero, convert
 
3994
         * response to simple error, else make sure there is an
 
3995
         * overall error to pass to dav_handle_err()
 
3996
         */
 
3997
        if (depth == 0) {
 
3998
            err = dav_new_error(r->pool, multi_status->status, 0, multi_status->desc);
 
3999
            multi_status = NULL;
 
4000
        }
 
4001
        else {
 
4002
            err = dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
 
4003
                                "Errors occurred during the LABEL operation.");
 
4004
        }
 
4005
 
 
4006
        return dav_handle_err(r, err, multi_status);
 
4007
    }
 
4008
 
 
4009
    /* set the Cache-Control header, per the spec */
 
4010
    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
 
4011
 
 
4012
    /* no body */
 
4013
    ap_set_content_length(r, 0);
 
4014
 
 
4015
    return DONE;
 
4016
}
 
4017
 
 
4018
static int dav_method_report(request_rec *r)
 
4019
{
 
4020
    dav_resource *resource;
 
4021
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
4022
    int result;
 
4023
    int label_allowed;
 
4024
    apr_xml_doc *doc;
 
4025
    dav_error *err;
 
4026
 
 
4027
    /* If no versioning provider, decline the request */
 
4028
    if (vsn_hooks == NULL)
 
4029
        return DECLINED;
 
4030
 
 
4031
    if ((result = ap_xml_parse_input(r, &doc)) != OK)
 
4032
        return result;
 
4033
    if (doc == NULL) {
 
4034
        /* This supplies additional information for the default msg. */
 
4035
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
4036
                      "The request body must specify a report.");
 
4037
        return HTTP_BAD_REQUEST;
 
4038
    }
 
4039
 
 
4040
    /* Ask repository module to resolve the resource.
 
4041
     * First determine whether a Target-Selector header is allowed
 
4042
     * for this report.
 
4043
     */
 
4044
    label_allowed = (*vsn_hooks->report_label_header_allowed)(doc);
 
4045
    err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */,
 
4046
                           &resource);
 
4047
    if (err != NULL)
 
4048
        return dav_handle_err(r, err, NULL);
 
4049
 
 
4050
    if (!resource->exists) {
 
4051
        /* Apache will supply a default error for this. */
 
4052
        return HTTP_NOT_FOUND;
 
4053
    }
 
4054
 
 
4055
    /* set up defaults for the report response */
 
4056
    r->status = HTTP_OK;
 
4057
    ap_set_content_type(r, DAV_XML_CONTENT_TYPE);
 
4058
 
 
4059
    /* run report hook */
 
4060
    if ((err = (*vsn_hooks->deliver_report)(r, resource, doc,
 
4061
                                            r->output_filters)) != NULL) {
 
4062
        if (! r->sent_bodyct)
 
4063
          /* No data has been sent to client yet;  throw normal error. */
 
4064
          return dav_handle_err(r, err, NULL);
 
4065
 
 
4066
        /* If an error occurred during the report delivery, there's
 
4067
           basically nothing we can do but abort the connection and
 
4068
           log an error.  This is one of the limitations of HTTP; it
 
4069
           needs to "know" the entire status of the response before
 
4070
           generating it, which is just impossible in these streamy
 
4071
           response situations. */
 
4072
        err = dav_push_error(r->pool, err->status, 0,
 
4073
                             "Provider encountered an error while streaming"
 
4074
                             " a REPORT response.", err);
 
4075
        dav_log_err(r, err, APLOG_ERR);
 
4076
        r->connection->aborted = 1;
 
4077
        return DONE;
 
4078
    }
 
4079
 
 
4080
    return DONE;
 
4081
}
 
4082
 
 
4083
static int dav_method_make_workspace(request_rec *r)
 
4084
{
 
4085
    dav_resource *resource;
 
4086
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
4087
    dav_error *err;
 
4088
    apr_xml_doc *doc;
 
4089
    int result;
 
4090
 
 
4091
    /* if no versioning provider, or the provider does not support workspaces,
 
4092
     * decline the request
 
4093
     */
 
4094
    if (vsn_hooks == NULL || vsn_hooks->make_workspace == NULL)
 
4095
        return DECLINED;
 
4096
 
 
4097
    /* ask repository module to resolve the resource */
 
4098
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
4099
                           &resource);
 
4100
    if (err != NULL)
 
4101
        return dav_handle_err(r, err, NULL);
 
4102
 
 
4103
    /* parse the request body (must be a mkworkspace element) */
 
4104
    if ((result = ap_xml_parse_input(r, &doc)) != OK) {
 
4105
        return result;
 
4106
    }
 
4107
 
 
4108
    if (doc == NULL
 
4109
        || !dav_validate_root(doc, "mkworkspace")) {
 
4110
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
4111
                      "The request body does not contain "
 
4112
                      "a \"mkworkspace\" element.");
 
4113
        return HTTP_BAD_REQUEST;
 
4114
    }
 
4115
 
 
4116
    /* Check request preconditions */
 
4117
 
 
4118
    /* ### need a general mechanism for reporting precondition violations
 
4119
     * ### (should be returning XML document for 403/409 responses)
 
4120
     */
 
4121
 
 
4122
    /* resource must not already exist */
 
4123
    if (resource->exists) {
 
4124
        err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
 
4125
                            "<DAV:resource-must-be-null/>");
 
4126
        return dav_handle_err(r, err, NULL);
 
4127
    }
 
4128
 
 
4129
    /* ### what about locking? */
 
4130
 
 
4131
    /* attempt to create the workspace */
 
4132
    if ((err = (*vsn_hooks->make_workspace)(resource, doc)) != NULL) {
 
4133
        err = dav_push_error(r->pool, err->status, 0,
 
4134
                             apr_psprintf(r->pool,
 
4135
                                          "Could not create workspace %s.",
 
4136
                                          ap_escape_html(r->pool, r->uri)),
 
4137
                             err);
 
4138
        return dav_handle_err(r, err, NULL);
 
4139
    }
 
4140
 
 
4141
    /* set the Cache-Control header, per the spec */
 
4142
    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
 
4143
 
 
4144
    /* return an appropriate response (HTTP_CREATED) */
 
4145
    return dav_created(r, resource->uri, "Workspace", 0 /*replaced*/);
 
4146
}
 
4147
 
 
4148
static int dav_method_make_activity(request_rec *r)
 
4149
{
 
4150
    dav_resource *resource;
 
4151
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
4152
    dav_error *err;
 
4153
    int result;
 
4154
 
 
4155
    /* if no versioning provider, or the provider does not support activities,
 
4156
     * decline the request
 
4157
     */
 
4158
    if (vsn_hooks == NULL || vsn_hooks->make_activity == NULL)
 
4159
        return DECLINED;
 
4160
 
 
4161
    /* ask repository module to resolve the resource */
 
4162
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
4163
                           &resource);
 
4164
    if (err != NULL)
 
4165
        return dav_handle_err(r, err, NULL);
 
4166
 
 
4167
    /* MKACTIVITY does not have a defined request body. */
 
4168
    if ((result = ap_discard_request_body(r)) != OK) {
 
4169
        return result;
 
4170
    }
 
4171
 
 
4172
    /* Check request preconditions */
 
4173
 
 
4174
    /* ### need a general mechanism for reporting precondition violations
 
4175
     * ### (should be returning XML document for 403/409 responses)
 
4176
     */
 
4177
 
 
4178
    /* resource must not already exist */
 
4179
    if (resource->exists) {
 
4180
        err = dav_new_error(r->pool, HTTP_CONFLICT, 0,
 
4181
                            "<DAV:resource-must-be-null/>");
 
4182
        return dav_handle_err(r, err, NULL);
 
4183
    }
 
4184
 
 
4185
    /* the provider must say whether the resource can be created as
 
4186
       an activity, i.e. whether the location is ok.  */
 
4187
    if (vsn_hooks->can_be_activity != NULL
 
4188
        && !(*vsn_hooks->can_be_activity)(resource)) {
 
4189
      err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
 
4190
                          "<DAV:activity-location-ok/>");
 
4191
      return dav_handle_err(r, err, NULL);
 
4192
    }
 
4193
 
 
4194
    /* ### what about locking? */
 
4195
 
 
4196
    /* attempt to create the activity */
 
4197
    if ((err = (*vsn_hooks->make_activity)(resource)) != NULL) {
 
4198
        err = dav_push_error(r->pool, err->status, 0,
 
4199
                             apr_psprintf(r->pool,
 
4200
                                          "Could not create activity %s.",
 
4201
                                          ap_escape_html(r->pool, r->uri)),
 
4202
                             err);
 
4203
        return dav_handle_err(r, err, NULL);
 
4204
    }
 
4205
 
 
4206
    /* set the Cache-Control header, per the spec */
 
4207
    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
 
4208
 
 
4209
    /* return an appropriate response (HTTP_CREATED) */
 
4210
    return dav_created(r, resource->uri, "Activity", 0 /*replaced*/);
 
4211
}
 
4212
 
 
4213
static int dav_method_baseline_control(request_rec *r)
 
4214
{
 
4215
    /* ### */
 
4216
    return HTTP_METHOD_NOT_ALLOWED;
 
4217
}
 
4218
 
 
4219
static int dav_method_merge(request_rec *r)
 
4220
{
 
4221
    dav_resource *resource;
 
4222
    dav_resource *source_resource;
 
4223
    const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
 
4224
    dav_error *err;
 
4225
    int result;
 
4226
    apr_xml_doc *doc;
 
4227
    apr_xml_elem *source_elem;
 
4228
    apr_xml_elem *href_elem;
 
4229
    apr_xml_elem *prop_elem;
 
4230
    const char *source;
 
4231
    int no_auto_merge;
 
4232
    int no_checkout;
 
4233
    dav_lookup_result lookup;
 
4234
 
 
4235
    /* If no versioning provider, decline the request */
 
4236
    if (vsn_hooks == NULL)
 
4237
        return DECLINED;
 
4238
 
 
4239
    if ((result = ap_xml_parse_input(r, &doc)) != OK)
 
4240
        return result;
 
4241
 
 
4242
    if (doc == NULL || !dav_validate_root(doc, "merge")) {
 
4243
        /* This supplies additional information for the default msg. */
 
4244
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
4245
                      "The request body must be present and must be a "
 
4246
                      "DAV:merge element.");
 
4247
        return HTTP_BAD_REQUEST;
 
4248
    }
 
4249
 
 
4250
    if ((source_elem = dav_find_child(doc->root, "source")) == NULL) {
 
4251
        /* This supplies additional information for the default msg. */
 
4252
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
4253
                      "The DAV:merge element must contain a DAV:source "
 
4254
                      "element.");
 
4255
        return HTTP_BAD_REQUEST;
 
4256
    }
 
4257
    if ((href_elem = dav_find_child(source_elem, "href")) == NULL) {
 
4258
        /* This supplies additional information for the default msg. */
 
4259
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
4260
                      "The DAV:source element must contain a DAV:href "
 
4261
                      "element.");
 
4262
        return HTTP_BAD_REQUEST;
 
4263
    }
 
4264
    source = dav_xml_get_cdata(href_elem, r->pool, 1 /* strip_white */);
 
4265
 
 
4266
    /* get a subrequest for the source, so that we can get a dav_resource
 
4267
       for that source. */
 
4268
    lookup = dav_lookup_uri(source, r, 0 /* must_be_absolute */);
 
4269
    if (lookup.rnew == NULL) {
 
4270
        if (lookup.err.status == HTTP_BAD_REQUEST) {
 
4271
            /* This supplies additional information for the default message. */
 
4272
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
4273
                          "%s", lookup.err.desc);
 
4274
            return HTTP_BAD_REQUEST;
 
4275
        }
 
4276
 
 
4277
        /* ### this assumes that dav_lookup_uri() only generates a status
 
4278
         * ### that Apache can provide a status line for!! */
 
4279
 
 
4280
        return dav_error_response(r, lookup.err.status, lookup.err.desc);
 
4281
    }
 
4282
    if (lookup.rnew->status != HTTP_OK) {
 
4283
        /* ### how best to report this... */
 
4284
        return dav_error_response(r, lookup.rnew->status,
 
4285
                                  "Merge source URI had an error.");
 
4286
    }
 
4287
    err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
 
4288
                           0 /* use_checked_in */, &source_resource);
 
4289
    if (err != NULL)
 
4290
        return dav_handle_err(r, err, NULL);
 
4291
 
 
4292
    no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL;
 
4293
    no_checkout = dav_find_child(doc->root, "no-checkout") != NULL;
 
4294
 
 
4295
    prop_elem = dav_find_child(doc->root, "prop");
 
4296
 
 
4297
    /* ### check RFC. I believe the DAV:merge element may contain any
 
4298
       ### element also allowed within DAV:checkout. need to extract them
 
4299
       ### here, and pass them along.
 
4300
       ### if so, then refactor the CHECKOUT method handling so we can reuse
 
4301
       ### the code. maybe create a structure to hold CHECKOUT parameters
 
4302
       ### which can be passed to the checkout() and merge() hooks. */
 
4303
 
 
4304
    /* Ask repository module to resolve the resource */
 
4305
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
4306
                           &resource);
 
4307
    if (err != NULL)
 
4308
        return dav_handle_err(r, err, NULL);
 
4309
    if (!resource->exists) {
 
4310
        /* Apache will supply a default error for this. */
 
4311
        return HTTP_NOT_FOUND;
 
4312
    }
 
4313
 
 
4314
    /* ### check the source and target resources flags/types */
 
4315
 
 
4316
    /* ### do lock checks, once behavior is defined */
 
4317
 
 
4318
    /* set the Cache-Control header, per the spec */
 
4319
    /* ### correct? */
 
4320
    apr_table_setn(r->headers_out, "Cache-Control", "no-cache");
 
4321
 
 
4322
    /* Initialize these values for a standard MERGE response. If the MERGE
 
4323
       is going to do something different (i.e. an error), then it must
 
4324
       return a dav_error, and we'll reset these values properly. */
 
4325
    r->status = HTTP_OK;
 
4326
    ap_set_content_type(r, "text/xml");
 
4327
 
 
4328
    /* ### should we do any preliminary response generation? probably not,
 
4329
       ### because we may have an error, thus demanding something else in
 
4330
       ### the response body. */
 
4331
 
 
4332
    /* Do the merge, including any response generation. */
 
4333
    if ((err = (*vsn_hooks->merge)(resource, source_resource,
 
4334
                                   no_auto_merge, no_checkout,
 
4335
                                   prop_elem,
 
4336
                                   r->output_filters)) != NULL) {
 
4337
        /* ### is err->status the right error here? */
 
4338
        err = dav_push_error(r->pool, err->status, 0,
 
4339
                             apr_psprintf(r->pool,
 
4340
                                          "Could not MERGE resource \"%s\" "
 
4341
                                          "into \"%s\".",
 
4342
                                          ap_escape_html(r->pool, source),
 
4343
                                          ap_escape_html(r->pool, r->uri)),
 
4344
                             err);
 
4345
        return dav_handle_err(r, err, NULL);
 
4346
    }
 
4347
 
 
4348
    /* the response was fully generated by the merge() hook. */
 
4349
    /* ### urk. does this prevent logging? need to check... */
 
4350
    return DONE;
 
4351
}
 
4352
 
 
4353
static int dav_method_bind(request_rec *r)
 
4354
{
 
4355
    dav_resource *resource;
 
4356
    dav_resource *binding;
 
4357
    dav_auto_version_info av_info;
 
4358
    const dav_hooks_binding *binding_hooks = DAV_GET_HOOKS_BINDING(r);
 
4359
    const char *dest;
 
4360
    dav_error *err;
 
4361
    dav_error *err2;
 
4362
    dav_response *multi_response = NULL;
 
4363
    dav_lookup_result lookup;
 
4364
    int overwrite;
 
4365
 
 
4366
    /* If no bindings provider, decline the request */
 
4367
    if (binding_hooks == NULL)
 
4368
        return DECLINED;
 
4369
 
 
4370
    /* Ask repository module to resolve the resource */
 
4371
    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
 
4372
                           &resource);
 
4373
    if (err != NULL)
 
4374
        return dav_handle_err(r, err, NULL);
 
4375
 
 
4376
    if (!resource->exists) {
 
4377
        /* Apache will supply a default error for this. */
 
4378
        return HTTP_NOT_FOUND;
 
4379
    }
 
4380
 
 
4381
    /* get the destination URI */
 
4382
    dest = apr_table_get(r->headers_in, "Destination");
 
4383
    if (dest == NULL) {
 
4384
        /* This supplies additional information for the default message. */
 
4385
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
4386
                      "The request is missing a Destination header.");
 
4387
        return HTTP_BAD_REQUEST;
 
4388
    }
 
4389
 
 
4390
    lookup = dav_lookup_uri(dest, r, 0 /* must_be_absolute */);
 
4391
    if (lookup.rnew == NULL) {
 
4392
        if (lookup.err.status == HTTP_BAD_REQUEST) {
 
4393
            /* This supplies additional information for the default message. */
 
4394
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
4395
                          "%s", lookup.err.desc);
 
4396
            return HTTP_BAD_REQUEST;
 
4397
        }
 
4398
        else if (lookup.err.status == HTTP_BAD_GATEWAY) {
 
4399
            /* ### Bindings protocol draft 02 says to return 507
 
4400
             * ### (Cross Server Binding Forbidden); Apache already defines 507
 
4401
             * ### as HTTP_INSUFFICIENT_STORAGE. So, for now, we'll return
 
4402
             * ### HTTP_FORBIDDEN
 
4403
             */
 
4404
             return dav_error_response(r, HTTP_FORBIDDEN,
 
4405
                                       "Cross server bindings are not "
 
4406
                                       "allowed by this server.");
 
4407
        }
 
4408
 
 
4409
        /* ### this assumes that dav_lookup_uri() only generates a status
 
4410
         * ### that Apache can provide a status line for!! */
 
4411
 
 
4412
        return dav_error_response(r, lookup.err.status, lookup.err.desc);
 
4413
    }
 
4414
    if (lookup.rnew->status != HTTP_OK) {
 
4415
        /* ### how best to report this... */
 
4416
        return dav_error_response(r, lookup.rnew->status,
 
4417
                                  "Destination URI had an error.");
 
4418
    }
 
4419
 
 
4420
    /* resolve binding resource */
 
4421
    err = dav_get_resource(lookup.rnew, 0 /* label_allowed */,
 
4422
                           0 /* use_checked_in */, &binding);
 
4423
    if (err != NULL)
 
4424
        return dav_handle_err(r, err, NULL);
 
4425
 
 
4426
    /* are the two resources handled by the same repository? */
 
4427
    if (resource->hooks != binding->hooks) {
 
4428
        /* ### this message exposes some backend config, but screw it... */
 
4429
        return dav_error_response(r, HTTP_BAD_GATEWAY,
 
4430
                                  "Destination URI is handled by a "
 
4431
                                  "different repository than the source URI. "
 
4432
                                  "BIND between repositories is not possible.");
 
4433
    }
 
4434
 
 
4435
    /* get and parse the overwrite header value */
 
4436
    if ((overwrite = dav_get_overwrite(r)) < 0) {
 
4437
        /* dav_get_overwrite() supplies additional information for the
 
4438
         * default message. */
 
4439
        return HTTP_BAD_REQUEST;
 
4440
    }
 
4441
 
 
4442
    /* quick failure test: if dest exists and overwrite is false. */
 
4443
    if (binding->exists && !overwrite) {
 
4444
        return dav_error_response(r, HTTP_PRECONDITION_FAILED,
 
4445
                                  "Destination is not empty and "
 
4446
                                  "Overwrite is not \"T\"");
 
4447
    }
 
4448
 
 
4449
    /* are the source and destination the same? */
 
4450
    if ((*resource->hooks->is_same_resource)(resource, binding)) {
 
4451
        return dav_error_response(r, HTTP_FORBIDDEN,
 
4452
                                  "Source and Destination URIs are the same.");
 
4453
    }
 
4454
 
 
4455
    /*
 
4456
     * Check If-Headers and existing locks for destination. Note that we
 
4457
     * use depth==infinity since the target (hierarchy) will be deleted
 
4458
     * before the move/copy is completed.
 
4459
     *
 
4460
     * Note that we are overwriting the target, which implies a DELETE, so
 
4461
     * we are subject to the error/response rules as a DELETE. Namely, we
 
4462
     * will return a 424 error if any of the validations fail.
 
4463
     * (see dav_method_delete() for more information)
 
4464
     */
 
4465
    if ((err = dav_validate_request(lookup.rnew, binding, DAV_INFINITY, NULL,
 
4466
                                    &multi_response,
 
4467
                                    DAV_VALIDATE_PARENT
 
4468
                                    | DAV_VALIDATE_USE_424, NULL)) != NULL) {
 
4469
        err = dav_push_error(r->pool, err->status, 0,
 
4470
                             apr_psprintf(r->pool,
 
4471
                                          "Could not BIND %s due to a "
 
4472
                                          "failed precondition on the "
 
4473
                                          "destination (e.g. locks).",
 
4474
                                          ap_escape_html(r->pool, r->uri)),
 
4475
                             err);
 
4476
        return dav_handle_err(r, err, multi_response);
 
4477
    }
 
4478
 
 
4479
    /* guard against creating circular bindings */
 
4480
    if (resource->collection
 
4481
        && (*resource->hooks->is_parent_resource)(resource, binding)) {
 
4482
        return dav_error_response(r, HTTP_FORBIDDEN,
 
4483
                                  "Source collection contains the Destination.");
 
4484
    }
 
4485
    if (resource->collection
 
4486
        && (*resource->hooks->is_parent_resource)(binding, resource)) {
 
4487
        /* The destination must exist (since it contains the source), and
 
4488
         * a condition above implies Overwrite==T. Obviously, we cannot
 
4489
         * delete the Destination before the BIND, as that would
 
4490
         * delete the Source.
 
4491
         */
 
4492
 
 
4493
        return dav_error_response(r, HTTP_FORBIDDEN,
 
4494
                                  "Destination collection contains the Source and "
 
4495
                                  "Overwrite has been specified.");
 
4496
    }
 
4497
 
 
4498
    /* prepare the destination collection for modification */
 
4499
    if ((err = dav_auto_checkout(r, binding, 1 /* parent_only */,
 
4500
                                 &av_info)) != NULL) {
 
4501
        /* could not make destination writable */
 
4502
        return dav_handle_err(r, err, NULL);
 
4503
    }
 
4504
 
 
4505
    /* If target exists, remove it first (we know Ovewrite must be TRUE).
 
4506
     * Then try to bind to the resource.
 
4507
     */
 
4508
    if (binding->exists)
 
4509
        err = (*resource->hooks->remove_resource)(binding, &multi_response);
 
4510
 
 
4511
    if (err == NULL) {
 
4512
        err = (*binding_hooks->bind_resource)(resource, binding);
 
4513
    }
 
4514
 
 
4515
    /* restore parent collection states */
 
4516
    err2 = dav_auto_checkin(r, NULL,
 
4517
                            err != NULL /* undo if error */,
 
4518
                            0 /* unlock */, &av_info);
 
4519
 
 
4520
    /* check for error from remove/bind operations */
 
4521
    if (err != NULL) {
 
4522
        err = dav_push_error(r->pool, err->status, 0,
 
4523
                             apr_psprintf(r->pool,
 
4524
                                          "Could not BIND %s.",
 
4525
                                          ap_escape_html(r->pool, r->uri)),
 
4526
                             err);
 
4527
        return dav_handle_err(r, err, multi_response);
 
4528
    }
 
4529
 
 
4530
    /* check for errors from reverting writability */
 
4531
    if (err2 != NULL) {
 
4532
        /* just log a warning */
 
4533
        err = dav_push_error(r->pool, err2->status, 0,
 
4534
                             "The BIND was successful, but there was a "
 
4535
                             "problem automatically checking in the "
 
4536
                             "source parent collection.",
 
4537
                             err2);
 
4538
        dav_log_err(r, err, APLOG_WARNING);
 
4539
    }
 
4540
 
 
4541
    /* return an appropriate response (HTTP_CREATED) */
 
4542
    /* ### spec doesn't say what happens when destination was replaced */
 
4543
    return dav_created(r, lookup.rnew->uri, "Binding", 0);
 
4544
}
 
4545
 
 
4546
 
 
4547
/*
 
4548
 * Response handler for DAV resources
 
4549
 */
 
4550
static int dav_handler(request_rec *r)
 
4551
{
 
4552
    if (strcmp(r->handler, DAV_HANDLER_NAME) != 0)
 
4553
        return DECLINED;
 
4554
 
 
4555
    /* Reject requests with an unescaped hash character, as these may
 
4556
     * be more destructive than the user intended. */
 
4557
    if (r->parsed_uri.fragment != NULL) {
 
4558
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
 
4559
                     "buggy client used un-escaped hash in Request-URI");
 
4560
        return dav_error_response(r, HTTP_BAD_REQUEST,
 
4561
                                  "The request was invalid: the URI included "
 
4562
                                  "an un-escaped hash character");
 
4563
    }
 
4564
 
 
4565
    /* ### do we need to do anything with r->proxyreq ?? */
 
4566
 
 
4567
    /*
 
4568
     * ### anything else to do here? could another module and/or
 
4569
     * ### config option "take over" the handler here? i.e. how do
 
4570
     * ### we lock down this hierarchy so that we are the ultimate
 
4571
     * ### arbiter? (or do we simply depend on the administrator
 
4572
     * ### to avoid conflicting configurations?)
 
4573
     */
 
4574
 
 
4575
    /*
 
4576
     * Set up the methods mask, since that's one of the reasons this handler
 
4577
     * gets called, and lower-level things may need the info.
 
4578
     *
 
4579
     * First, set the mask to the methods we handle directly.  Since by
 
4580
     * definition we own our managed space, we unconditionally set
 
4581
     * the r->allowed field rather than ORing our values with anything
 
4582
     * any other module may have put in there.
 
4583
     *
 
4584
     * These are the HTTP-defined methods that we handle directly.
 
4585
     */
 
4586
    r->allowed = 0
 
4587
        | (AP_METHOD_BIT << M_GET)
 
4588
        | (AP_METHOD_BIT << M_PUT)
 
4589
        | (AP_METHOD_BIT << M_DELETE)
 
4590
        | (AP_METHOD_BIT << M_OPTIONS)
 
4591
        | (AP_METHOD_BIT << M_INVALID);
 
4592
 
 
4593
    /*
 
4594
     * These are the DAV methods we handle.
 
4595
     */
 
4596
    r->allowed |= 0
 
4597
        | (AP_METHOD_BIT << M_COPY)
 
4598
        | (AP_METHOD_BIT << M_LOCK)
 
4599
        | (AP_METHOD_BIT << M_UNLOCK)
 
4600
        | (AP_METHOD_BIT << M_MKCOL)
 
4601
        | (AP_METHOD_BIT << M_MOVE)
 
4602
        | (AP_METHOD_BIT << M_PROPFIND)
 
4603
        | (AP_METHOD_BIT << M_PROPPATCH);
 
4604
 
 
4605
    /*
 
4606
     * These are methods that we don't handle directly, but let the
 
4607
     * server's default handler do for us as our agent.
 
4608
     */
 
4609
    r->allowed |= 0
 
4610
        | (AP_METHOD_BIT << M_POST);
 
4611
 
 
4612
    /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
 
4613
     * ### is sent; it will need the other allowed states; since the default
 
4614
     * ### handler is not called on error, then it doesn't add the other
 
4615
     * ### allowed states, so we must
 
4616
     */
 
4617
 
 
4618
    /* ### we might need to refine this for just where we return the error.
 
4619
     * ### also, there is the issue with other methods (see ISSUES)
 
4620
     */
 
4621
 
 
4622
    /* dispatch the appropriate method handler */
 
4623
    if (r->method_number == M_GET) {
 
4624
        return dav_method_get(r);
 
4625
    }
 
4626
 
 
4627
    if (r->method_number == M_PUT) {
 
4628
        return dav_method_put(r);
 
4629
    }
 
4630
 
 
4631
    if (r->method_number == M_POST) {
 
4632
        return dav_method_post(r);
 
4633
    }
 
4634
 
 
4635
    if (r->method_number == M_DELETE) {
 
4636
        return dav_method_delete(r);
 
4637
    }
 
4638
 
 
4639
    if (r->method_number == M_OPTIONS) {
 
4640
        return dav_method_options(r);
 
4641
    }
 
4642
 
 
4643
    if (r->method_number == M_PROPFIND) {
 
4644
        return dav_method_propfind(r);
 
4645
    }
 
4646
 
 
4647
    if (r->method_number == M_PROPPATCH) {
 
4648
        return dav_method_proppatch(r);
 
4649
    }
 
4650
 
 
4651
    if (r->method_number == M_MKCOL) {
 
4652
        return dav_method_mkcol(r);
 
4653
    }
 
4654
 
 
4655
    if (r->method_number == M_COPY) {
 
4656
        return dav_method_copymove(r, DAV_DO_COPY);
 
4657
    }
 
4658
 
 
4659
    if (r->method_number == M_MOVE) {
 
4660
        return dav_method_copymove(r, DAV_DO_MOVE);
 
4661
    }
 
4662
 
 
4663
    if (r->method_number == M_LOCK) {
 
4664
        return dav_method_lock(r);
 
4665
    }
 
4666
 
 
4667
    if (r->method_number == M_UNLOCK) {
 
4668
        return dav_method_unlock(r);
 
4669
    }
 
4670
 
 
4671
    if (r->method_number == M_VERSION_CONTROL) {
 
4672
        return dav_method_vsn_control(r);
 
4673
    }
 
4674
 
 
4675
    if (r->method_number == M_CHECKOUT) {
 
4676
        return dav_method_checkout(r);
 
4677
    }
 
4678
 
 
4679
    if (r->method_number == M_UNCHECKOUT) {
 
4680
        return dav_method_uncheckout(r);
 
4681
    }
 
4682
 
 
4683
    if (r->method_number == M_CHECKIN) {
 
4684
        return dav_method_checkin(r);
 
4685
    }
 
4686
 
 
4687
    if (r->method_number == M_UPDATE) {
 
4688
        return dav_method_update(r);
 
4689
    }
 
4690
 
 
4691
    if (r->method_number == M_LABEL) {
 
4692
        return dav_method_label(r);
 
4693
    }
 
4694
 
 
4695
    if (r->method_number == M_REPORT) {
 
4696
        return dav_method_report(r);
 
4697
    }
 
4698
 
 
4699
    if (r->method_number == M_MKWORKSPACE) {
 
4700
        return dav_method_make_workspace(r);
 
4701
    }
 
4702
 
 
4703
    if (r->method_number == M_MKACTIVITY) {
 
4704
        return dav_method_make_activity(r);
 
4705
    }
 
4706
 
 
4707
    if (r->method_number == M_BASELINE_CONTROL) {
 
4708
        return dav_method_baseline_control(r);
 
4709
    }
 
4710
 
 
4711
    if (r->method_number == M_MERGE) {
 
4712
        return dav_method_merge(r);
 
4713
    }
 
4714
 
 
4715
    /* BIND method */
 
4716
    if (r->method_number == dav_methods[DAV_M_BIND]) {
 
4717
        return dav_method_bind(r);
 
4718
    }
 
4719
 
 
4720
    /* DASL method */
 
4721
    if (r->method_number == dav_methods[DAV_M_SEARCH]) {
 
4722
        return dav_method_search(r);
 
4723
    }
 
4724
 
 
4725
    /* ### add'l methods for Advanced Collections, ACLs */
 
4726
 
 
4727
    return DECLINED;
 
4728
}
 
4729
 
 
4730
static int dav_fixups(request_rec *r)
 
4731
{
 
4732
    dav_dir_conf *conf;
 
4733
 
 
4734
    /* quickly ignore any HTTP/0.9 requests which aren't subreqs. */
 
4735
    if (r->assbackwards && !r->main) {
 
4736
        return DECLINED;
 
4737
    }
 
4738
 
 
4739
    conf = (dav_dir_conf *)ap_get_module_config(r->per_dir_config,
 
4740
                                                &dav_module);
 
4741
 
 
4742
    /* if DAV is not enabled, then we've got nothing to do */
 
4743
    if (conf->provider == NULL) {
 
4744
        return DECLINED;
 
4745
    }
 
4746
 
 
4747
    /* We are going to handle almost every request. In certain cases,
 
4748
       the provider maps to the filesystem (thus, handle_get is
 
4749
       FALSE), and core Apache will handle it. a For that case, we
 
4750
       just return right away.  */
 
4751
    if (r->method_number == M_GET) {
 
4752
        /*
 
4753
         * ### need some work to pull Content-Type and Content-Language
 
4754
         * ### from the property database.
 
4755
         */
 
4756
 
 
4757
        /*
 
4758
         * If the repository hasn't indicated that it will handle the
 
4759
         * GET method, then just punt.
 
4760
         *
 
4761
         * ### this isn't quite right... taking over the response can break
 
4762
         * ### things like mod_negotiation. need to look into this some more.
 
4763
         */
 
4764
        if (!conf->provider->repos->handle_get) {
 
4765
            return DECLINED;
 
4766
        }
 
4767
    }
 
4768
 
 
4769
    /* ### this is wrong.  We should only be setting the r->handler for the
 
4770
     * requests that mod_dav knows about.  If we set the handler for M_POST
 
4771
     * requests, then CGI scripts that use POST will return the source for the
 
4772
     * script.  However, mod_dav DOES handle POST, so something else needs
 
4773
     * to be fixed.
 
4774
     */
 
4775
    if (r->method_number != M_POST) {
 
4776
 
 
4777
        /* We are going to be handling the response for this resource. */
 
4778
        r->handler = DAV_HANDLER_NAME;
 
4779
        return OK;
 
4780
    }
 
4781
 
 
4782
    return DECLINED;
 
4783
}
 
4784
 
 
4785
static void register_hooks(apr_pool_t *p)
 
4786
{
 
4787
    ap_hook_handler(dav_handler, NULL, NULL, APR_HOOK_MIDDLE);
 
4788
    ap_hook_post_config(dav_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
 
4789
    ap_hook_fixups(dav_fixups, NULL, NULL, APR_HOOK_MIDDLE);
 
4790
 
 
4791
    dav_hook_find_liveprop(dav_core_find_liveprop, NULL, NULL, APR_HOOK_LAST);
 
4792
    dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops,
 
4793
                                  NULL, NULL, APR_HOOK_MIDDLE);
 
4794
 
 
4795
    dav_core_register_uris(p);
 
4796
}
 
4797
 
 
4798
/*---------------------------------------------------------------------------
 
4799
 *
 
4800
 * Configuration info for the module
 
4801
 */
 
4802
 
 
4803
static const command_rec dav_cmds[] =
 
4804
{
 
4805
    /* per directory/location */
 
4806
    AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF,
 
4807
                  "specify the DAV provider for a directory or location"),
 
4808
 
 
4809
    /* per directory/location, or per server */
 
4810
    AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL,
 
4811
                  ACCESS_CONF|RSRC_CONF,
 
4812
                  "specify minimum allowed timeout"),
 
4813
 
 
4814
    /* per directory/location, or per server */
 
4815
    AP_INIT_FLAG("DAVDepthInfinity", dav_cmd_davdepthinfinity, NULL,
 
4816
                 ACCESS_CONF|RSRC_CONF,
 
4817
                 "allow Depth infinity PROPFIND requests"),
 
4818
 
 
4819
    { NULL }
 
4820
};
 
4821
 
 
4822
module DAV_DECLARE_DATA dav_module =
 
4823
{
 
4824
    STANDARD20_MODULE_STUFF,
 
4825
    dav_create_dir_config,      /* dir config creater */
 
4826
    dav_merge_dir_config,       /* dir merger --- default is to override */
 
4827
    dav_create_server_config,   /* server config */
 
4828
    dav_merge_server_config,    /* merge server config */
 
4829
    dav_cmds,                   /* command table */
 
4830
    register_hooks,             /* register hooks */
 
4831
};
 
4832
 
 
4833
APR_HOOK_STRUCT(
 
4834
    APR_HOOK_LINK(gather_propsets)
 
4835
    APR_HOOK_LINK(find_liveprop)
 
4836
    APR_HOOK_LINK(insert_all_liveprops)
 
4837
    )
 
4838
 
 
4839
APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets,
 
4840
                                 (apr_array_header_t *uris),
 
4841
                                 (uris))
 
4842
 
 
4843
APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, find_liveprop,
 
4844
                                      (const dav_resource *resource,
 
4845
                                       const char *ns_uri, const char *name,
 
4846
                                       const dav_hooks_liveprop **hooks),
 
4847
                                      (resource, ns_uri, name, hooks), 0)
 
4848
 
 
4849
APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops,
 
4850
                                 (request_rec *r, const dav_resource *resource,
 
4851
                                  dav_prop_insert what, apr_text_header *phdr),
 
4852
                                 (r, resource, what, phdr))