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

« back to all changes in this revision

Viewing changes to modules/dav/fs/repos.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 filesystem-based repository provider
 
19
*/
 
20
 
 
21
#include "apr.h"
 
22
#include "apr_file_io.h"
 
23
#include "apr_strings.h"
 
24
#include "apr_buckets.h"
 
25
 
 
26
#if APR_HAVE_STDIO_H
 
27
#include <stdio.h>              /* for sprintf() */
 
28
#endif
 
29
 
 
30
#include "httpd.h"
 
31
#include "http_log.h"
 
32
#include "http_protocol.h"      /* for ap_set_* (in dav_fs_set_headers) */
 
33
#include "http_request.h"       /* for ap_update_mtime() */
 
34
 
 
35
#include "mod_dav.h"
 
36
#include "repos.h"
 
37
 
 
38
 
 
39
/* to assist in debugging mod_dav's GET handling */
 
40
#define DEBUG_GET_HANDLER       0
 
41
 
 
42
#define DAV_FS_COPY_BLOCKSIZE   16384   /* copy 16k at a time */
 
43
 
 
44
/* context needed to identify a resource */
 
45
struct dav_resource_private {
 
46
    apr_pool_t *pool;        /* memory storage pool associated with request */
 
47
    const char *pathname;   /* full pathname to resource */
 
48
    apr_finfo_t finfo;       /* filesystem info */
 
49
};
 
50
 
 
51
/* private context for doing a filesystem walk */
 
52
typedef struct {
 
53
    /* the input walk parameters */
 
54
    const dav_walk_params *params;
 
55
 
 
56
    /* reused as we walk */
 
57
    dav_walk_resource wres;
 
58
 
 
59
    dav_resource res1;
 
60
    dav_resource_private info1;
 
61
    dav_buffer path1;
 
62
    dav_buffer uri_buf;
 
63
 
 
64
    /* MOVE/COPY need a secondary path */
 
65
    dav_resource res2;
 
66
    dav_resource_private info2;
 
67
    dav_buffer path2;
 
68
 
 
69
    dav_buffer locknull_buf;
 
70
 
 
71
} dav_fs_walker_context;
 
72
 
 
73
typedef struct {
 
74
    int is_move;                /* is this a MOVE? */
 
75
    dav_buffer work_buf;        /* handy buffer for copymove_file() */
 
76
 
 
77
    /* CALLBACK: this is a secondary resource managed specially for us */
 
78
    const dav_resource *res_dst;
 
79
 
 
80
    /* copied from dav_walk_params (they are invariant across the walk) */
 
81
    const dav_resource *root;
 
82
    apr_pool_t *pool;
 
83
 
 
84
} dav_fs_copymove_walk_ctx;
 
85
 
 
86
/* an internal WALKTYPE to walk hidden files (the .DAV directory) */
 
87
#define DAV_WALKTYPE_HIDDEN     0x4000
 
88
 
 
89
/* an internal WALKTYPE to call collections (again) after their contents */
 
90
#define DAV_WALKTYPE_POSTFIX    0x8000
 
91
 
 
92
#define DAV_CALLTYPE_POSTFIX    1000    /* a private call type */
 
93
 
 
94
 
 
95
/* pull this in from the other source file */
 
96
extern const dav_hooks_locks dav_hooks_locks_fs;
 
97
 
 
98
/* forward-declare the hook structures */
 
99
static const dav_hooks_repository dav_hooks_repository_fs;
 
100
static const dav_hooks_liveprop dav_hooks_liveprop_fs;
 
101
 
 
102
/*
 
103
** The namespace URIs that we use. This list and the enumeration must
 
104
** stay in sync.
 
105
*/
 
106
static const char * const dav_fs_namespace_uris[] =
 
107
{
 
108
    "DAV:",
 
109
    "http://apache.org/dav/props/",
 
110
 
 
111
    NULL        /* sentinel */
 
112
};
 
113
enum {
 
114
    DAV_FS_URI_DAV,            /* the DAV: namespace URI */
 
115
    DAV_FS_URI_MYPROPS         /* the namespace URI for our custom props */
 
116
};
 
117
 
 
118
/*
 
119
** Does this platform support an executable flag?
 
120
**
 
121
** ### need a way to portably abstract this query
 
122
*/
 
123
#ifndef WIN32
 
124
#define DAV_FS_HAS_EXECUTABLE
 
125
#endif
 
126
 
 
127
/*
 
128
** The single property that we define (in the DAV_FS_URI_MYPROPS namespace)
 
129
*/
 
130
#define DAV_PROPID_FS_executable        1
 
131
 
 
132
static const dav_liveprop_spec dav_fs_props[] =
 
133
{
 
134
    /* standard DAV properties */
 
135
    {
 
136
        DAV_FS_URI_DAV,
 
137
        "creationdate",
 
138
        DAV_PROPID_creationdate,
 
139
        0
 
140
    },
 
141
    {
 
142
        DAV_FS_URI_DAV,
 
143
        "getcontentlength",
 
144
        DAV_PROPID_getcontentlength,
 
145
        0
 
146
    },
 
147
    {
 
148
        DAV_FS_URI_DAV,
 
149
        "getetag",
 
150
        DAV_PROPID_getetag,
 
151
        0
 
152
    },
 
153
    {
 
154
        DAV_FS_URI_DAV,
 
155
        "getlastmodified",
 
156
        DAV_PROPID_getlastmodified,
 
157
        0
 
158
    },
 
159
 
 
160
    /* our custom properties */
 
161
    {
 
162
        DAV_FS_URI_MYPROPS,
 
163
        "executable",
 
164
        DAV_PROPID_FS_executable,
 
165
        0       /* handled special in dav_fs_is_writable */
 
166
    },
 
167
 
 
168
    { 0 }        /* sentinel */
 
169
};
 
170
 
 
171
static const dav_liveprop_group dav_fs_liveprop_group =
 
172
{
 
173
    dav_fs_props,
 
174
    dav_fs_namespace_uris,
 
175
    &dav_hooks_liveprop_fs
 
176
};
 
177
 
 
178
 
 
179
/* define the dav_stream structure for our use */
 
180
struct dav_stream {
 
181
    apr_pool_t *p;
 
182
    apr_file_t *f;
 
183
    const char *pathname;       /* we may need to remove it at close time */
 
184
};
 
185
 
 
186
/* returns an appropriate HTTP status code given an APR status code for a
 
187
 * failed I/O operation.  ### use something besides 500? */
 
188
#define MAP_IO2HTTP(e) (APR_STATUS_IS_ENOSPC(e) ? HTTP_INSUFFICIENT_STORAGE : \
 
189
                        HTTP_INTERNAL_SERVER_ERROR)
 
190
 
 
191
/* forward declaration for internal treewalkers */
 
192
static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
 
193
                               dav_response **response);
 
194
static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
 
195
                                        int depth, int is_move,
 
196
                                        const dav_resource *root_dst,
 
197
                                        dav_response **response);
 
198
 
 
199
/* --------------------------------------------------------------------
 
200
**
 
201
** PRIVATE REPOSITORY FUNCTIONS
 
202
*/
 
203
apr_pool_t *dav_fs_pool(const dav_resource *resource)
 
204
{
 
205
    return resource->info->pool;
 
206
}
 
207
 
 
208
const char *dav_fs_pathname(const dav_resource *resource)
 
209
{
 
210
    return resource->info->pathname;
 
211
}
 
212
 
 
213
dav_error * dav_fs_dir_file_name(
 
214
    const dav_resource *resource,
 
215
    const char **dirpath_p,
 
216
    const char **fname_p)
 
217
{
 
218
    dav_resource_private *ctx = resource->info;
 
219
 
 
220
    if (resource->collection) {
 
221
        *dirpath_p = ctx->pathname;
 
222
        if (fname_p != NULL)
 
223
            *fname_p = NULL;
 
224
    }
 
225
    else {
 
226
        const char *testpath, *rootpath;
 
227
        char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
 
228
        apr_size_t dirlen = strlen(dirpath);
 
229
        apr_status_t rv = APR_SUCCESS;
 
230
 
 
231
        testpath = dirpath;
 
232
        if (dirlen > 0) {
 
233
            rv = apr_filepath_root(&rootpath, &testpath, 0, ctx->pool);
 
234
        }
 
235
 
 
236
        /* remove trailing slash from dirpath, unless it's a root path
 
237
         */
 
238
        if ((rv == APR_SUCCESS && testpath && *testpath)
 
239
            || rv == APR_ERELATIVE) {
 
240
            if (dirpath[dirlen - 1] == '/') {
 
241
                dirpath[dirlen - 1] = '\0';
 
242
            }
 
243
        }
 
244
 
 
245
        /* ###: Looks like a response could be appropriate
 
246
         *
 
247
         * APR_SUCCESS     here tells us the dir is a root
 
248
         * APR_ERELATIVE   told us we had no root (ok)
 
249
         * APR_EINCOMPLETE an incomplete testpath told us
 
250
         *                 there was no -file- name here!
 
251
         * APR_EBADPATH    or other errors tell us this file
 
252
         *                 path is undecipherable
 
253
         */
 
254
 
 
255
        if (rv == APR_SUCCESS || rv == APR_ERELATIVE) {
 
256
            *dirpath_p = dirpath;
 
257
            if (fname_p != NULL)
 
258
                *fname_p = ctx->pathname + dirlen;
 
259
        }
 
260
        else {
 
261
            return dav_new_error(ctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
 
262
                                 "An incomplete/bad path was found in "
 
263
                                 "dav_fs_dir_file_name.");
 
264
        }
 
265
    }
 
266
 
 
267
    return NULL;
 
268
}
 
269
 
 
270
/* Note: picked up from ap_gm_timestr_822() */
 
271
/* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
 
272
static void dav_format_time(int style, apr_time_t sec, char *buf)
 
273
{
 
274
    apr_time_exp_t tms;
 
275
 
 
276
    /* ### what to do if fails? */
 
277
    (void) apr_time_exp_gmt(&tms, sec);
 
278
 
 
279
    if (style == DAV_STYLE_ISO8601) {
 
280
        /* ### should we use "-00:00" instead of "Z" ?? */
 
281
 
 
282
        /* 20 chars plus null term */
 
283
        sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
 
284
               tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
 
285
               tms.tm_hour, tms.tm_min, tms.tm_sec);
 
286
        return;
 
287
    }
 
288
 
 
289
    /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
 
290
 
 
291
    /* 29 chars plus null term */
 
292
    sprintf(buf,
 
293
            "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
 
294
           apr_day_snames[tms.tm_wday],
 
295
           tms.tm_mday, apr_month_snames[tms.tm_mon],
 
296
           tms.tm_year + 1900,
 
297
           tms.tm_hour, tms.tm_min, tms.tm_sec);
 
298
}
 
299
 
 
300
/* Copy or move src to dst; src_finfo is used to propagate permissions
 
301
 * bits across if non-NULL; dst_finfo must be non-NULL iff dst already
 
302
 * exists. */
 
303
static dav_error * dav_fs_copymove_file(
 
304
    int is_move,
 
305
    apr_pool_t * p,
 
306
    const char *src,
 
307
    const char *dst,
 
308
    const apr_finfo_t *src_finfo,
 
309
    const apr_finfo_t *dst_finfo,
 
310
    dav_buffer *pbuf)
 
311
{
 
312
    dav_buffer work_buf = { 0 };
 
313
    apr_file_t *inf = NULL;
 
314
    apr_file_t *outf = NULL;
 
315
    apr_status_t status;
 
316
    apr_fileperms_t perms;
 
317
 
 
318
    if (pbuf == NULL)
 
319
        pbuf = &work_buf;
 
320
 
 
321
    /* Determine permissions to use for destination */
 
322
    if (src_finfo && src_finfo->valid & APR_FINFO_PROT
 
323
        && src_finfo->protection & APR_UEXECUTE) {
 
324
        perms = src_finfo->protection;
 
325
 
 
326
        if (dst_finfo != NULL) {
 
327
            /* chmod it if it already exist */
 
328
            if (apr_file_perms_set(dst, perms)) {
 
329
                return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
330
                                     "Could not set permissions on destination");
 
331
            }
 
332
        }
 
333
    }
 
334
    else {
 
335
        perms = APR_OS_DEFAULT;
 
336
    }
 
337
 
 
338
    dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
 
339
 
 
340
    if ((apr_file_open(&inf, src, APR_READ | APR_BINARY, APR_OS_DEFAULT, p))
 
341
            != APR_SUCCESS) {
 
342
        /* ### use something besides 500? */
 
343
        return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
344
                             "Could not open file for reading");
 
345
    }
 
346
 
 
347
    /* ### do we need to deal with the umask? */
 
348
    status = apr_file_open(&outf, dst, APR_WRITE | APR_CREATE | APR_TRUNCATE
 
349
                           | APR_BINARY, perms, p);
 
350
    if (status != APR_SUCCESS) {
 
351
        apr_file_close(inf);
 
352
 
 
353
        return dav_new_error(p, MAP_IO2HTTP(status), 0,
 
354
                             "Could not open file for writing");
 
355
    }
 
356
 
 
357
    while (1) {
 
358
        apr_size_t len = DAV_FS_COPY_BLOCKSIZE;
 
359
 
 
360
        status = apr_file_read(inf, pbuf->buf, &len);
 
361
        if (status != APR_SUCCESS && status != APR_EOF) {
 
362
            apr_file_close(inf);
 
363
            apr_file_close(outf);
 
364
 
 
365
            if (apr_file_remove(dst, p) != APR_SUCCESS) {
 
366
                /* ### ACK! Inconsistent state... */
 
367
 
 
368
                /* ### use something besides 500? */
 
369
                return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
370
                                     "Could not delete output after read "
 
371
                                     "failure. Server is now in an "
 
372
                                     "inconsistent state.");
 
373
            }
 
374
 
 
375
            /* ### use something besides 500? */
 
376
            return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
377
                                 "Could not read input file");
 
378
        }
 
379
 
 
380
        if (status == APR_EOF)
 
381
            break;
 
382
 
 
383
        /* write any bytes that were read */
 
384
        status = apr_file_write_full(outf, pbuf->buf, len, NULL);
 
385
        if (status != APR_SUCCESS) {
 
386
            apr_file_close(inf);
 
387
            apr_file_close(outf);
 
388
 
 
389
            if (apr_file_remove(dst, p) != APR_SUCCESS) {
 
390
                /* ### ACK! Inconsistent state... */
 
391
 
 
392
                /* ### use something besides 500? */
 
393
                return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
394
                                     "Could not delete output after write "
 
395
                                     "failure. Server is now in an "
 
396
                                     "inconsistent state.");
 
397
            }
 
398
 
 
399
            return dav_new_error(p, MAP_IO2HTTP(status), 0,
 
400
                                 "Could not write output file");
 
401
        }
 
402
    }
 
403
 
 
404
    apr_file_close(inf);
 
405
    apr_file_close(outf);
 
406
 
 
407
    if (is_move && apr_file_remove(src, p) != APR_SUCCESS) {
 
408
        dav_error *err;
 
409
        int save_errno = errno;   /* save the errno that got us here */
 
410
 
 
411
        if (apr_file_remove(dst, p) != APR_SUCCESS) {
 
412
            /* ### ACK. this creates an inconsistency. do more!? */
 
413
 
 
414
            /* ### use something besides 500? */
 
415
            /* Note that we use the latest errno */
 
416
            return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
417
                                 "Could not remove source or destination "
 
418
                                 "file. Server is now in an inconsistent "
 
419
                                 "state.");
 
420
        }
 
421
 
 
422
        /* ### use something besides 500? */
 
423
        err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
424
                            "Could not remove source file after move. "
 
425
                            "Destination was removed to ensure consistency.");
 
426
        err->save_errno = save_errno;
 
427
        return err;
 
428
    }
 
429
 
 
430
    return NULL;
 
431
}
 
432
 
 
433
/* copy/move a file from within a state dir to another state dir */
 
434
/* ### need more buffers to replace the pool argument */
 
435
static dav_error * dav_fs_copymove_state(
 
436
    int is_move,
 
437
    apr_pool_t * p,
 
438
    const char *src_dir, const char *src_file,
 
439
    const char *dst_dir, const char *dst_file,
 
440
    dav_buffer *pbuf)
 
441
{
 
442
    apr_finfo_t src_finfo;        /* finfo for source file */
 
443
    apr_finfo_t dst_state_finfo;        /* finfo for STATE directory */
 
444
    apr_status_t rv;
 
445
    const char *src;
 
446
    const char *dst;
 
447
 
 
448
    /* build the propset pathname for the source file */
 
449
    src = apr_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
 
450
 
 
451
    /* the source file doesn't exist */
 
452
    rv = apr_stat(&src_finfo, src, APR_FINFO_NORM, p);
 
453
    if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
 
454
        return NULL;
 
455
    }
 
456
 
 
457
    /* build the pathname for the destination state dir */
 
458
    dst = apr_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
 
459
 
 
460
    /* ### do we need to deal with the umask? */
 
461
 
 
462
    /* ensure that it exists */
 
463
    rv = apr_dir_make(dst, APR_OS_DEFAULT, p);
 
464
    if (rv != APR_SUCCESS) {
 
465
        if (!APR_STATUS_IS_EEXIST(rv)) {
 
466
            /* ### use something besides 500? */
 
467
            return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
468
                                 "Could not create internal state directory");
 
469
        }
 
470
    }
 
471
 
 
472
    /* get info about the state directory */
 
473
    rv = apr_stat(&dst_state_finfo, dst, APR_FINFO_NORM, p);
 
474
    if (rv != APR_SUCCESS && rv != APR_INCOMPLETE) {
 
475
        /* Ack! Where'd it go? */
 
476
        /* ### use something besides 500? */
 
477
        return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
478
                             "State directory disappeared");
 
479
    }
 
480
 
 
481
    /* The mkdir() may have failed because a *file* exists there already */
 
482
    if (dst_state_finfo.filetype != APR_DIR) {
 
483
        /* ### try to recover by deleting this file? (and mkdir again) */
 
484
        /* ### use something besides 500? */
 
485
        return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
486
                             "State directory is actually a file");
 
487
    }
 
488
 
 
489
    /* append the target file to the state directory pathname */
 
490
    dst = apr_pstrcat(p, dst, "/", dst_file, NULL);
 
491
 
 
492
    /* copy/move the file now */
 
493
    if (is_move && src_finfo.device == dst_state_finfo.device) {
 
494
        /* simple rename is possible since it is on the same device */
 
495
        if (apr_file_rename(src, dst, p) != APR_SUCCESS) {
 
496
            /* ### use something besides 500? */
 
497
            return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
498
                                 "Could not move state file.");
 
499
        }
 
500
    }
 
501
    else
 
502
    {
 
503
        /* gotta copy (and delete) */
 
504
        return dav_fs_copymove_file(is_move, p, src, dst, NULL, NULL, pbuf);
 
505
    }
 
506
 
 
507
    return NULL;
 
508
}
 
509
 
 
510
static dav_error *dav_fs_copymoveset(int is_move, apr_pool_t *p,
 
511
                                     const dav_resource *src,
 
512
                                     const dav_resource *dst,
 
513
                                     dav_buffer *pbuf)
 
514
{
 
515
    const char *src_dir;
 
516
    const char *src_file;
 
517
    const char *src_state1;
 
518
    const char *src_state2;
 
519
    const char *dst_dir;
 
520
    const char *dst_file;
 
521
    const char *dst_state1;
 
522
    const char *dst_state2;
 
523
    dav_error *err;
 
524
 
 
525
    /* Get directory and filename for resources */
 
526
    /* ### should test these result values... */
 
527
    (void) dav_fs_dir_file_name(src, &src_dir, &src_file);
 
528
    (void) dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
 
529
 
 
530
    /* Get the corresponding state files for each resource */
 
531
    dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
 
532
    dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
 
533
#if DAV_DEBUG
 
534
    if ((src_state2 != NULL && dst_state2 == NULL) ||
 
535
        (src_state2 == NULL && dst_state2 != NULL)) {
 
536
        return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
537
                             "DESIGN ERROR: dav_dbm_get_statefiles() "
 
538
                             "returned inconsistent results.");
 
539
    }
 
540
#endif
 
541
 
 
542
    err = dav_fs_copymove_state(is_move, p,
 
543
                                src_dir, src_state1,
 
544
                                dst_dir, dst_state1,
 
545
                                pbuf);
 
546
 
 
547
    if (err == NULL && src_state2 != NULL) {
 
548
        err = dav_fs_copymove_state(is_move, p,
 
549
                                    src_dir, src_state2,
 
550
                                    dst_dir, dst_state2,
 
551
                                    pbuf);
 
552
 
 
553
        if (err != NULL) {
 
554
            /* ### CRAP. inconsistency. */
 
555
            /* ### should perform some cleanup at the target if we still
 
556
               ### have the original files */
 
557
 
 
558
            /* Change the error to reflect the bad server state. */
 
559
            err->status = HTTP_INTERNAL_SERVER_ERROR;
 
560
            err->desc =
 
561
                "Could not fully copy/move the properties. "
 
562
                "The server is now in an inconsistent state.";
 
563
        }
 
564
    }
 
565
 
 
566
    return err;
 
567
}
 
568
 
 
569
static dav_error *dav_fs_deleteset(apr_pool_t *p, const dav_resource *resource)
 
570
{
 
571
    const char *dirpath;
 
572
    const char *fname;
 
573
    const char *state1;
 
574
    const char *state2;
 
575
    const char *pathname;
 
576
    apr_status_t status;
 
577
 
 
578
    /* Get directory, filename, and state-file names for the resource */
 
579
    /* ### should test this result value... */
 
580
    (void) dav_fs_dir_file_name(resource, &dirpath, &fname);
 
581
    dav_dbm_get_statefiles(p, fname, &state1, &state2);
 
582
 
 
583
    /* build the propset pathname for the file */
 
584
    pathname = apr_pstrcat(p,
 
585
                          dirpath,
 
586
                          "/" DAV_FS_STATE_DIR "/",
 
587
                          state1,
 
588
                          NULL);
 
589
 
 
590
    /* note: we may get ENOENT if the state dir is not present */
 
591
    if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
 
592
        && !APR_STATUS_IS_ENOENT(status)) {
 
593
        return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
594
                             "Could not remove properties.");
 
595
    }
 
596
 
 
597
    if (state2 != NULL) {
 
598
        /* build the propset pathname for the file */
 
599
        pathname = apr_pstrcat(p,
 
600
                              dirpath,
 
601
                              "/" DAV_FS_STATE_DIR "/",
 
602
                              state2,
 
603
                              NULL);
 
604
 
 
605
        if ((status = apr_file_remove(pathname, p)) != APR_SUCCESS
 
606
            && !APR_STATUS_IS_ENOENT(status)) {
 
607
            /* ### CRAP. only removed half. */
 
608
            return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
609
                                 "Could not fully remove properties. "
 
610
                                 "The server is now in an inconsistent "
 
611
                                 "state.");
 
612
        }
 
613
    }
 
614
 
 
615
    return NULL;
 
616
}
 
617
 
 
618
/* --------------------------------------------------------------------
 
619
**
 
620
** REPOSITORY HOOK FUNCTIONS
 
621
*/
 
622
 
 
623
static dav_error * dav_fs_get_resource(
 
624
    request_rec *r,
 
625
    const char *root_dir,
 
626
    const char *label,
 
627
    int use_checked_in,
 
628
    dav_resource **result_resource)
 
629
{
 
630
    dav_resource_private *ctx;
 
631
    dav_resource *resource;
 
632
    char *s;
 
633
    char *filename;
 
634
    apr_size_t len;
 
635
 
 
636
    /* ### optimize this into a single allocation! */
 
637
 
 
638
    /* Create private resource context descriptor */
 
639
    ctx = apr_pcalloc(r->pool, sizeof(*ctx));
 
640
    ctx->finfo = r->finfo;
 
641
 
 
642
    /* ### this should go away */
 
643
    ctx->pool = r->pool;
 
644
 
 
645
    /* Preserve case on OSes which fold canonical filenames */
 
646
#if 0
 
647
    /* ### not available in Apache 2.0 yet */
 
648
    filename = r->case_preserved_filename;
 
649
#else
 
650
    filename = r->filename;
 
651
#endif
 
652
 
 
653
    /*
 
654
    ** If there is anything in the path_info, then this indicates that the
 
655
    ** entire path was not used to specify the file/dir. We want to append
 
656
    ** it onto the filename so that we get a "valid" pathname for null
 
657
    ** resources.
 
658
    */
 
659
    s = apr_pstrcat(r->pool, filename, r->path_info, NULL);
 
660
 
 
661
    /* make sure the pathname does not have a trailing "/" */
 
662
    len = strlen(s);
 
663
    if (len > 1 && s[len - 1] == '/') {
 
664
        s[len - 1] = '\0';
 
665
    }
 
666
    ctx->pathname = s;
 
667
 
 
668
    /* Create resource descriptor */
 
669
    resource = apr_pcalloc(r->pool, sizeof(*resource));
 
670
    resource->type = DAV_RESOURCE_TYPE_REGULAR;
 
671
    resource->info = ctx;
 
672
    resource->hooks = &dav_hooks_repository_fs;
 
673
    resource->pool = r->pool;
 
674
 
 
675
    /* make sure the URI does not have a trailing "/" */
 
676
    len = strlen(r->uri);
 
677
    if (len > 1 && r->uri[len - 1] == '/') {
 
678
        s = apr_pstrdup(r->pool, r->uri);
 
679
        s[len - 1] = '\0';
 
680
        resource->uri = s;
 
681
    }
 
682
    else {
 
683
        resource->uri = r->uri;
 
684
    }
 
685
 
 
686
    if (r->finfo.filetype != 0) {
 
687
        resource->exists = 1;
 
688
        resource->collection = r->finfo.filetype == APR_DIR;
 
689
 
 
690
        /* unused info in the URL will indicate a null resource */
 
691
 
 
692
        if (r->path_info != NULL && *r->path_info != '\0') {
 
693
            if (resource->collection) {
 
694
                /* only a trailing "/" is allowed */
 
695
                if (*r->path_info != '/' || r->path_info[1] != '\0') {
 
696
 
 
697
                    /*
 
698
                    ** This URL/filename represents a locknull resource or
 
699
                    ** possibly a destination of a MOVE/COPY
 
700
                    */
 
701
                    resource->exists = 0;
 
702
                    resource->collection = 0;
 
703
                }
 
704
            }
 
705
            else
 
706
            {
 
707
                /*
 
708
                ** The base of the path refers to a file -- nothing should
 
709
                ** be in path_info. The resource is simply an error: it
 
710
                ** can't be a null or a locknull resource.
 
711
                */
 
712
                return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
 
713
                                     "The URL contains extraneous path "
 
714
                                     "components. The resource could not "
 
715
                                     "be identified.");
 
716
            }
 
717
 
 
718
            /* retain proper integrity across the structures */
 
719
            if (!resource->exists) {
 
720
                ctx->finfo.filetype = 0;
 
721
            }
 
722
        }
 
723
    }
 
724
 
 
725
    *result_resource = resource;
 
726
    return NULL;
 
727
}
 
728
 
 
729
static dav_error * dav_fs_get_parent_resource(const dav_resource *resource,
 
730
                                              dav_resource **result_parent)
 
731
{
 
732
    dav_resource_private *ctx = resource->info;
 
733
    dav_resource_private *parent_ctx;
 
734
    dav_resource *parent_resource;
 
735
    apr_status_t rv;
 
736
    char *dirpath;
 
737
    const char *testroot;
 
738
    const char *testpath;
 
739
 
 
740
    /* If we're at the root of the URL space, then there is no parent. */
 
741
    if (strcmp(resource->uri, "/") == 0) {
 
742
        *result_parent = NULL;
 
743
        return NULL;
 
744
    }
 
745
 
 
746
    /* If given resource is root, then there is no parent.
 
747
     * Unless we can retrieve the filepath root, this is
 
748
     * intendend to fail.  If we split the root and
 
749
     * no path info remains, then we also fail.
 
750
     */
 
751
    testpath = ctx->pathname;
 
752
    rv = apr_filepath_root(&testroot, &testpath, 0, ctx->pool);
 
753
    if ((rv != APR_SUCCESS && rv != APR_ERELATIVE)
 
754
        || !testpath || !*testpath) {
 
755
        *result_parent = NULL;
 
756
        return NULL;
 
757
    }
 
758
 
 
759
    /* ### optimize this into a single allocation! */
 
760
 
 
761
    /* Create private resource context descriptor */
 
762
    parent_ctx = apr_pcalloc(ctx->pool, sizeof(*parent_ctx));
 
763
 
 
764
    /* ### this should go away */
 
765
    parent_ctx->pool = ctx->pool;
 
766
 
 
767
    dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
 
768
    if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/')
 
769
        dirpath[strlen(dirpath) - 1] = '\0';
 
770
    parent_ctx->pathname = dirpath;
 
771
 
 
772
    parent_resource = apr_pcalloc(ctx->pool, sizeof(*parent_resource));
 
773
    parent_resource->info = parent_ctx;
 
774
    parent_resource->collection = 1;
 
775
    parent_resource->hooks = &dav_hooks_repository_fs;
 
776
    parent_resource->pool = resource->pool;
 
777
 
 
778
    if (resource->uri != NULL) {
 
779
        char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
 
780
        if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
 
781
            uri[strlen(uri) - 1] = '\0';
 
782
        parent_resource->uri = uri;
 
783
    }
 
784
 
 
785
    rv = apr_stat(&parent_ctx->finfo, parent_ctx->pathname,
 
786
                  APR_FINFO_NORM, ctx->pool);
 
787
    if (rv == APR_SUCCESS || rv == APR_INCOMPLETE) {
 
788
        parent_resource->exists = 1;
 
789
    }
 
790
 
 
791
    *result_parent = parent_resource;
 
792
    return NULL;
 
793
}
 
794
 
 
795
static int dav_fs_is_same_resource(
 
796
    const dav_resource *res1,
 
797
    const dav_resource *res2)
 
798
{
 
799
    dav_resource_private *ctx1 = res1->info;
 
800
    dav_resource_private *ctx2 = res2->info;
 
801
 
 
802
    if (res1->hooks != res2->hooks)
 
803
        return 0;
 
804
 
 
805
    if ((ctx1->finfo.filetype != 0) && (ctx2->finfo.filetype != 0)
 
806
        && (ctx1->finfo.valid & ctx2->finfo.valid & APR_FINFO_INODE)) {
 
807
        return ctx1->finfo.inode == ctx2->finfo.inode;
 
808
    }
 
809
    else {
 
810
        return strcmp(ctx1->pathname, ctx2->pathname) == 0;
 
811
    }
 
812
}
 
813
 
 
814
static int dav_fs_is_parent_resource(
 
815
    const dav_resource *res1,
 
816
    const dav_resource *res2)
 
817
{
 
818
    dav_resource_private *ctx1 = res1->info;
 
819
    dav_resource_private *ctx2 = res2->info;
 
820
    apr_size_t len1 = strlen(ctx1->pathname);
 
821
    apr_size_t len2;
 
822
 
 
823
    if (res1->hooks != res2->hooks)
 
824
        return 0;
 
825
 
 
826
    /* it is safe to use ctx2 now */
 
827
    len2 = strlen(ctx2->pathname);
 
828
 
 
829
    return (len2 > len1
 
830
            && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
 
831
            && ctx2->pathname[len1] == '/');
 
832
}
 
833
 
 
834
static dav_error * dav_fs_open_stream(const dav_resource *resource,
 
835
                                      dav_stream_mode mode,
 
836
                                      dav_stream **stream)
 
837
{
 
838
    apr_pool_t *p = resource->info->pool;
 
839
    dav_stream *ds = apr_pcalloc(p, sizeof(*ds));
 
840
    apr_int32_t flags;
 
841
    apr_status_t rv;
 
842
 
 
843
    switch (mode) {
 
844
    default:
 
845
        flags = APR_READ | APR_BINARY;
 
846
        break;
 
847
 
 
848
    case DAV_MODE_WRITE_TRUNC:
 
849
        flags = APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BINARY;
 
850
        break;
 
851
    case DAV_MODE_WRITE_SEEKABLE:
 
852
        flags = APR_WRITE | APR_CREATE | APR_BINARY;
 
853
        break;
 
854
    }
 
855
 
 
856
    ds->p = p;
 
857
    ds->pathname = resource->info->pathname;
 
858
    rv = apr_file_open(&ds->f, ds->pathname, flags, APR_OS_DEFAULT, ds->p);
 
859
    if (rv != APR_SUCCESS) {
 
860
        return dav_new_error(p, MAP_IO2HTTP(rv), 0,
 
861
                             "An error occurred while opening a resource.");
 
862
    }
 
863
 
 
864
    /* (APR registers cleanups for the fd with the pool) */
 
865
 
 
866
    *stream = ds;
 
867
    return NULL;
 
868
}
 
869
 
 
870
static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
 
871
{
 
872
    apr_file_close(stream->f);
 
873
 
 
874
    if (!commit) {
 
875
        if (apr_file_remove(stream->pathname, stream->p) != APR_SUCCESS) {
 
876
            /* ### use a better description? */
 
877
            return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
878
                                 "There was a problem removing (rolling "
 
879
                                 "back) the resource "
 
880
                                 "when it was being closed.");
 
881
        }
 
882
    }
 
883
 
 
884
    return NULL;
 
885
}
 
886
 
 
887
static dav_error * dav_fs_write_stream(dav_stream *stream,
 
888
                                       const void *buf, apr_size_t bufsize)
 
889
{
 
890
    apr_status_t status;
 
891
 
 
892
    status = apr_file_write_full(stream->f, buf, bufsize, NULL);
 
893
    if (APR_STATUS_IS_ENOSPC(status)) {
 
894
        return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0,
 
895
                             "There is not enough storage to write to "
 
896
                             "this resource.");
 
897
    }
 
898
    else if (status != APR_SUCCESS) {
 
899
        /* ### use something besides 500? */
 
900
        return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
901
                             "An error occurred while writing to a "
 
902
                             "resource.");
 
903
    }
 
904
    return NULL;
 
905
}
 
906
 
 
907
static dav_error * dav_fs_seek_stream(dav_stream *stream, apr_off_t abs_pos)
 
908
{
 
909
    if (apr_file_seek(stream->f, APR_SET, &abs_pos) != APR_SUCCESS) {
 
910
        /* ### should check whether apr_file_seek set abs_pos was set to the
 
911
         * correct position? */
 
912
        /* ### use something besides 500? */
 
913
        return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
 
914
                             "Could not seek to specified position in the "
 
915
                             "resource.");
 
916
    }
 
917
    return NULL;
 
918
}
 
919
 
 
920
 
 
921
#if DEBUG_GET_HANDLER
 
922
 
 
923
/* only define set_headers() and deliver() for debug purposes */
 
924
 
 
925
 
 
926
static dav_error * dav_fs_set_headers(request_rec *r,
 
927
                                      const dav_resource *resource)
 
928
{
 
929
    /* ### this function isn't really used since we have a get_pathname */
 
930
    if (!resource->exists)
 
931
        return NULL;
 
932
 
 
933
    /* make sure the proper mtime is in the request record */
 
934
    ap_update_mtime(r, resource->info->finfo.mtime);
 
935
 
 
936
    /* ### note that these use r->filename rather than <resource> */
 
937
    ap_set_last_modified(r);
 
938
    ap_set_etag(r);
 
939
 
 
940
    /* we accept byte-ranges */
 
941
    apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
 
942
 
 
943
    /* set up the Content-Length header */
 
944
    ap_set_content_length(r, resource->info->finfo.size);
 
945
 
 
946
    /* ### how to set the content type? */
 
947
    /* ### until this is resolved, the Content-Type header is busted */
 
948
 
 
949
    return NULL;
 
950
}
 
951
 
 
952
static dav_error * dav_fs_deliver(const dav_resource *resource,
 
953
                                  ap_filter_t *output)
 
954
{
 
955
    apr_pool_t *pool = resource->pool;
 
956
    apr_bucket_brigade *bb;
 
957
    apr_file_t *fd;
 
958
    apr_status_t status;
 
959
    apr_bucket *bkt;
 
960
 
 
961
    /* Check resource type */
 
962
    if (resource->type != DAV_RESOURCE_TYPE_REGULAR
 
963
        && resource->type != DAV_RESOURCE_TYPE_VERSION
 
964
        && resource->type != DAV_RESOURCE_TYPE_WORKING) {
 
965
        return dav_new_error(pool, HTTP_CONFLICT, 0,
 
966
                             "Cannot GET this type of resource.");
 
967
    }
 
968
    if (resource->collection) {
 
969
        return dav_new_error(pool, HTTP_CONFLICT, 0,
 
970
                             "There is no default response to GET for a "
 
971
                             "collection.");
 
972
    }
 
973
 
 
974
    if ((status = apr_file_open(&fd, resource->info->pathname,
 
975
                                APR_READ | APR_BINARY, 0,
 
976
                                pool)) != APR_SUCCESS) {
 
977
        return dav_new_error(pool, HTTP_FORBIDDEN, 0,
 
978
                             "File permissions deny server access.");
 
979
    }
 
980
 
 
981
    bb = apr_brigade_create(pool, output->c->bucket_alloc);
 
982
 
 
983
    /* ### this does not handle large files. but this is test code anyway */
 
984
    bkt = apr_bucket_file_create(fd, 0,
 
985
                                 (apr_size_t)resource->info->finfo.size,
 
986
                                 pool, output->c->bucket_alloc);
 
987
    APR_BRIGADE_INSERT_TAIL(bb, bkt);
 
988
 
 
989
    bkt = apr_bucket_eos_create(output->c->bucket_alloc);
 
990
    APR_BRIGADE_INSERT_TAIL(bb, bkt);
 
991
 
 
992
    if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) {
 
993
        return dav_new_error(pool, HTTP_FORBIDDEN, 0,
 
994
                             "Could not write contents to filter.");
 
995
    }
 
996
 
 
997
    return NULL;
 
998
}
 
999
 
 
1000
#endif /* DEBUG_GET_HANDLER */
 
1001
 
 
1002
 
 
1003
static dav_error * dav_fs_create_collection(dav_resource *resource)
 
1004
{
 
1005
    dav_resource_private *ctx = resource->info;
 
1006
    apr_status_t status;
 
1007
 
 
1008
    status = apr_dir_make(ctx->pathname, APR_OS_DEFAULT, ctx->pool);
 
1009
    if (APR_STATUS_IS_ENOSPC(status)) {
 
1010
        return dav_new_error(ctx->pool, HTTP_INSUFFICIENT_STORAGE, 0,
 
1011
                             "There is not enough storage to create "
 
1012
                             "this collection.");
 
1013
    }
 
1014
    else if (APR_STATUS_IS_ENOENT(status)) {
 
1015
        return dav_new_error(ctx->pool, HTTP_CONFLICT, 0,
 
1016
                             "Cannot create collection; intermediate "
 
1017
                             "collection does not exist.");
 
1018
    }
 
1019
    else if (status != APR_SUCCESS) {
 
1020
        /* ### refine this error message? */
 
1021
        return dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0,
 
1022
                             "Unable to create collection.");
 
1023
    }
 
1024
 
 
1025
    /* update resource state to show it exists as a collection */
 
1026
    resource->exists = 1;
 
1027
    resource->collection = 1;
 
1028
 
 
1029
    return NULL;
 
1030
}
 
1031
 
 
1032
static dav_error * dav_fs_copymove_walker(dav_walk_resource *wres,
 
1033
                                          int calltype)
 
1034
{
 
1035
    dav_fs_copymove_walk_ctx *ctx = wres->walk_ctx;
 
1036
    dav_resource_private *srcinfo = wres->resource->info;
 
1037
    dav_resource_private *dstinfo = ctx->res_dst->info;
 
1038
    dav_error *err = NULL;
 
1039
 
 
1040
    if (wres->resource->collection) {
 
1041
        if (calltype == DAV_CALLTYPE_POSTFIX) {
 
1042
            /* Postfix call for MOVE. delete the source dir.
 
1043
             * Note: when copying, we do not enable the postfix-traversal.
 
1044
             */
 
1045
            /* ### we are ignoring any error here; what should we do? */
 
1046
            (void) apr_dir_remove(srcinfo->pathname, ctx->pool);
 
1047
        }
 
1048
        else {
 
1049
            /* copy/move of a collection. Create the new, target collection */
 
1050
            if (apr_dir_make(dstinfo->pathname, APR_OS_DEFAULT,
 
1051
                             ctx->pool) != APR_SUCCESS) {
 
1052
                /* ### assume it was a permissions problem */
 
1053
                /* ### need a description here */
 
1054
                err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, NULL);
 
1055
            }
 
1056
        }
 
1057
    }
 
1058
    else {
 
1059
        err = dav_fs_copymove_file(ctx->is_move, ctx->pool,
 
1060
                                   srcinfo->pathname, dstinfo->pathname,
 
1061
                                   &srcinfo->finfo,
 
1062
                                   ctx->res_dst->exists ? &dstinfo->finfo : NULL,
 
1063
                                   &ctx->work_buf);
 
1064
        /* ### push a higher-level description? */
 
1065
    }
 
1066
 
 
1067
    /*
 
1068
    ** If we have a "not so bad" error, then it might need to go into a
 
1069
    ** multistatus response.
 
1070
    **
 
1071
    ** For a MOVE, it will always go into the multistatus. It could be
 
1072
    ** that everything has been moved *except* for the root. Using a
 
1073
    ** multistatus (with no errors for the other resources) will signify
 
1074
    ** this condition.
 
1075
    **
 
1076
    ** For a COPY, we are traversing in a prefix fashion. If the root fails,
 
1077
    ** then we can just bail out now.
 
1078
    */
 
1079
    if (err != NULL
 
1080
        && !ap_is_HTTP_SERVER_ERROR(err->status)
 
1081
        && (ctx->is_move
 
1082
            || !dav_fs_is_same_resource(wres->resource, ctx->root))) {
 
1083
        /* ### use errno to generate DAV:responsedescription? */
 
1084
        dav_add_response(wres, err->status, NULL);
 
1085
 
 
1086
        /* the error is in the multistatus now. do not stop the traversal. */
 
1087
        return NULL;
 
1088
    }
 
1089
 
 
1090
    return err;
 
1091
}
 
1092
 
 
1093
static dav_error *dav_fs_copymove_resource(
 
1094
    int is_move,
 
1095
    const dav_resource *src,
 
1096
    const dav_resource *dst,
 
1097
    int depth,
 
1098
    dav_response **response)
 
1099
{
 
1100
    dav_error *err = NULL;
 
1101
    dav_buffer work_buf = { 0 };
 
1102
 
 
1103
    *response = NULL;
 
1104
 
 
1105
    /* if a collection, recursively copy/move it and its children,
 
1106
     * including the state dirs
 
1107
     */
 
1108
    if (src->collection) {
 
1109
        dav_walk_params params = { 0 };
 
1110
        dav_response *multi_status;
 
1111
 
 
1112
        params.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_HIDDEN;
 
1113
        params.func = dav_fs_copymove_walker;
 
1114
        params.pool = src->info->pool;
 
1115
        params.root = src;
 
1116
 
 
1117
        /* params.walk_ctx is managed by dav_fs_internal_walk() */
 
1118
 
 
1119
        /* postfix is needed for MOVE to delete source dirs */
 
1120
        if (is_move)
 
1121
            params.walk_type |= DAV_WALKTYPE_POSTFIX;
 
1122
 
 
1123
        /* note that we return the error OR the multistatus. never both */
 
1124
 
 
1125
        if ((err = dav_fs_internal_walk(&params, depth, is_move, dst,
 
1126
                                        &multi_status)) != NULL) {
 
1127
            /* on a "real" error, then just punt. nothing else to do. */
 
1128
            return err;
 
1129
        }
 
1130
 
 
1131
        if ((*response = multi_status) != NULL) {
 
1132
            /* some multistatus responses exist. wrap them in a 207 */
 
1133
            return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0,
 
1134
                                 "Error(s) occurred on some resources during "
 
1135
                                 "the COPY/MOVE process.");
 
1136
        }
 
1137
 
 
1138
        return NULL;
 
1139
    }
 
1140
 
 
1141
    /* not a collection */
 
1142
    if ((err = dav_fs_copymove_file(is_move, src->info->pool,
 
1143
                                    src->info->pathname, dst->info->pathname,
 
1144
                                    &src->info->finfo,
 
1145
                                    dst->exists ? &dst->info->finfo : NULL,
 
1146
                                    &work_buf)) != NULL) {
 
1147
        /* ### push a higher-level description? */
 
1148
        return err;
 
1149
    }
 
1150
 
 
1151
    /* copy/move properties as well */
 
1152
    return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
 
1153
}
 
1154
 
 
1155
static dav_error * dav_fs_copy_resource(
 
1156
    const dav_resource *src,
 
1157
    dav_resource *dst,
 
1158
    int depth,
 
1159
    dav_response **response)
 
1160
{
 
1161
    dav_error *err;
 
1162
 
 
1163
#if DAV_DEBUG
 
1164
    if (src->hooks != dst->hooks) {
 
1165
        /*
 
1166
        ** ### strictly speaking, this is a design error; we should not
 
1167
        ** ### have reached this point.
 
1168
        */
 
1169
        return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
 
1170
                             "DESIGN ERROR: a mix of repositories "
 
1171
                             "was passed to copy_resource.");
 
1172
    }
 
1173
#endif
 
1174
 
 
1175
    if ((err = dav_fs_copymove_resource(0, src, dst, depth,
 
1176
                                        response)) == NULL) {
 
1177
 
 
1178
        /* update state of destination resource to show it exists */
 
1179
        dst->exists = 1;
 
1180
        dst->collection = src->collection;
 
1181
    }
 
1182
 
 
1183
    return err;
 
1184
}
 
1185
 
 
1186
static dav_error * dav_fs_move_resource(
 
1187
    dav_resource *src,
 
1188
    dav_resource *dst,
 
1189
    dav_response **response)
 
1190
{
 
1191
    dav_resource_private *srcinfo = src->info;
 
1192
    dav_resource_private *dstinfo = dst->info;
 
1193
    dav_error *err;
 
1194
    int can_rename = 0;
 
1195
 
 
1196
#if DAV_DEBUG
 
1197
    if (src->hooks != dst->hooks) {
 
1198
        /*
 
1199
        ** ### strictly speaking, this is a design error; we should not
 
1200
        ** ### have reached this point.
 
1201
        */
 
1202
        return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
 
1203
                             "DESIGN ERROR: a mix of repositories "
 
1204
                             "was passed to move_resource.");
 
1205
    }
 
1206
#endif
 
1207
 
 
1208
    /* determine whether a simple rename will work.
 
1209
     * Assume source exists, else we wouldn't get called.
 
1210
     */
 
1211
    if (dstinfo->finfo.filetype != 0) {
 
1212
        if (dstinfo->finfo.device == srcinfo->finfo.device) {
 
1213
            /* target exists and is on the same device. */
 
1214
            can_rename = 1;
 
1215
        }
 
1216
    }
 
1217
    else {
 
1218
        const char *dirpath;
 
1219
        apr_finfo_t finfo;
 
1220
        apr_status_t rv;
 
1221
 
 
1222
        /* destination does not exist, but the parent directory should,
 
1223
         * so try it
 
1224
         */
 
1225
        dirpath = ap_make_dirstr_parent(dstinfo->pool, dstinfo->pathname);
 
1226
        /*
 
1227
         * XXX: If missing dev ... then what test?
 
1228
         * Really need a try and failover for those platforms.
 
1229
         *
 
1230
         */
 
1231
        rv = apr_stat(&finfo, dirpath, APR_FINFO_DEV, dstinfo->pool);
 
1232
        if ((rv == APR_SUCCESS || rv == APR_INCOMPLETE)
 
1233
            && (finfo.valid & srcinfo->finfo.valid & APR_FINFO_DEV)
 
1234
            && (finfo.device == srcinfo->finfo.device)) {
 
1235
            can_rename = 1;
 
1236
        }
 
1237
    }
 
1238
 
 
1239
    /* if we can't simply rename, then do it the hard way... */
 
1240
    if (!can_rename) {
 
1241
        if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY,
 
1242
                                            response)) == NULL) {
 
1243
            /* update resource states */
 
1244
            dst->exists = 1;
 
1245
            dst->collection = src->collection;
 
1246
            src->exists = 0;
 
1247
            src->collection = 0;
 
1248
        }
 
1249
 
 
1250
        return err;
 
1251
    }
 
1252
 
 
1253
    /* a rename should work. do it, and move properties as well */
 
1254
 
 
1255
    /* no multistatus response */
 
1256
    *response = NULL;
 
1257
 
 
1258
    /* ### APR has no rename? */
 
1259
    if (apr_file_rename(srcinfo->pathname, dstinfo->pathname,
 
1260
                       srcinfo->pool) != APR_SUCCESS) {
 
1261
        /* ### should have a better error than this. */
 
1262
        return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
 
1263
                             "Could not rename resource.");
 
1264
    }
 
1265
 
 
1266
    /* update resource states */
 
1267
    dst->exists = 1;
 
1268
    dst->collection = src->collection;
 
1269
    src->exists = 0;
 
1270
    src->collection = 0;
 
1271
 
 
1272
    if ((err = dav_fs_copymoveset(1, src->info->pool,
 
1273
                                  src, dst, NULL)) == NULL) {
 
1274
        /* no error. we're done. go ahead and return now. */
 
1275
        return NULL;
 
1276
    }
 
1277
 
 
1278
    /* error occurred during properties move; try to put resource back */
 
1279
    if (apr_file_rename(dstinfo->pathname, srcinfo->pathname,
 
1280
                       srcinfo->pool) != APR_SUCCESS) {
 
1281
        /* couldn't put it back! */
 
1282
        return dav_push_error(srcinfo->pool,
 
1283
                              HTTP_INTERNAL_SERVER_ERROR, 0,
 
1284
                              "The resource was moved, but a failure "
 
1285
                              "occurred during the move of its "
 
1286
                              "properties. The resource could not be "
 
1287
                              "restored to its original location. The "
 
1288
                              "server is now in an inconsistent state.",
 
1289
                              err);
 
1290
    }
 
1291
 
 
1292
    /* update resource states again */
 
1293
    src->exists = 1;
 
1294
    src->collection = dst->collection;
 
1295
    dst->exists = 0;
 
1296
    dst->collection = 0;
 
1297
 
 
1298
    /* resource moved back, but properties may be inconsistent */
 
1299
    return dav_push_error(srcinfo->pool,
 
1300
                          HTTP_INTERNAL_SERVER_ERROR, 0,
 
1301
                          "The resource was moved, but a failure "
 
1302
                          "occurred during the move of its properties. "
 
1303
                          "The resource was moved back to its original "
 
1304
                          "location, but its properties may have been "
 
1305
                          "partially moved. The server may be in an "
 
1306
                          "inconsistent state.",
 
1307
                          err);
 
1308
}
 
1309
 
 
1310
static dav_error * dav_fs_delete_walker(dav_walk_resource *wres, int calltype)
 
1311
{
 
1312
    dav_resource_private *info = wres->resource->info;
 
1313
 
 
1314
    /* do not attempt to remove a null resource,
 
1315
     * or a collection with children
 
1316
     */
 
1317
    if (wres->resource->exists &&
 
1318
        (!wres->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
 
1319
        /* try to remove the resource */
 
1320
        apr_status_t result;
 
1321
 
 
1322
        result = wres->resource->collection
 
1323
            ? apr_dir_remove(info->pathname, wres->pool)
 
1324
            : apr_file_remove(info->pathname, wres->pool);
 
1325
 
 
1326
        /*
 
1327
        ** If an error occurred, then add it to multistatus response.
 
1328
        ** Note that we add it for the root resource, too. It is quite
 
1329
        ** possible to delete the whole darn tree, yet fail on the root.
 
1330
        **
 
1331
        ** (also: remember we are deleting via a postfix traversal)
 
1332
        */
 
1333
        if (result != APR_SUCCESS) {
 
1334
            /* ### assume there is a permissions problem */
 
1335
 
 
1336
            /* ### use errno to generate DAV:responsedescription? */
 
1337
            dav_add_response(wres, HTTP_FORBIDDEN, NULL);
 
1338
        }
 
1339
    }
 
1340
 
 
1341
    return NULL;
 
1342
}
 
1343
 
 
1344
static dav_error * dav_fs_remove_resource(dav_resource *resource,
 
1345
                                          dav_response **response)
 
1346
{
 
1347
    dav_resource_private *info = resource->info;
 
1348
 
 
1349
    *response = NULL;
 
1350
 
 
1351
    /* if a collection, recursively remove it and its children,
 
1352
     * including the state dirs
 
1353
     */
 
1354
    if (resource->collection) {
 
1355
        dav_walk_params params = { 0 };
 
1356
        dav_error *err = NULL;
 
1357
        dav_response *multi_status;
 
1358
 
 
1359
        params.walk_type = (DAV_WALKTYPE_NORMAL
 
1360
                            | DAV_WALKTYPE_HIDDEN
 
1361
                            | DAV_WALKTYPE_POSTFIX);
 
1362
        params.func = dav_fs_delete_walker;
 
1363
        params.pool = info->pool;
 
1364
        params.root = resource;
 
1365
 
 
1366
        if ((err = dav_fs_walk(&params, DAV_INFINITY,
 
1367
                               &multi_status)) != NULL) {
 
1368
            /* on a "real" error, then just punt. nothing else to do. */
 
1369
            return err;
 
1370
        }
 
1371
 
 
1372
        if ((*response = multi_status) != NULL) {
 
1373
            /* some multistatus responses exist. wrap them in a 207 */
 
1374
            return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0,
 
1375
                                 "Error(s) occurred on some resources during "
 
1376
                                 "the deletion process.");
 
1377
        }
 
1378
 
 
1379
        /* no errors... update resource state */
 
1380
        resource->exists = 0;
 
1381
        resource->collection = 0;
 
1382
 
 
1383
        return NULL;
 
1384
    }
 
1385
 
 
1386
    /* not a collection; remove the file and its properties */
 
1387
    if (apr_file_remove(info->pathname, info->pool) != APR_SUCCESS) {
 
1388
        /* ### put a description in here */
 
1389
        return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, NULL);
 
1390
    }
 
1391
 
 
1392
    /* update resource state */
 
1393
    resource->exists = 0;
 
1394
    resource->collection = 0;
 
1395
 
 
1396
    /* remove properties and return its result */
 
1397
    return dav_fs_deleteset(info->pool, resource);
 
1398
}
 
1399
 
 
1400
/* ### move this to dav_util? */
 
1401
/* Walk recursively down through directories, *
 
1402
 * including lock-null resources as we go.    */
 
1403
static dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
 
1404
{
 
1405
    const dav_walk_params *params = fsctx->params;
 
1406
    apr_pool_t *pool = params->pool;
 
1407
    dav_error *err = NULL;
 
1408
    int isdir = fsctx->res1.collection;
 
1409
    apr_finfo_t dirent;
 
1410
    apr_dir_t *dirp;
 
1411
 
 
1412
    /* ensure the context is prepared properly, then call the func */
 
1413
    err = (*params->func)(&fsctx->wres,
 
1414
                          isdir
 
1415
                          ? DAV_CALLTYPE_COLLECTION
 
1416
                          : DAV_CALLTYPE_MEMBER);
 
1417
    if (err != NULL) {
 
1418
        return err;
 
1419
    }
 
1420
 
 
1421
    if (depth == 0 || !isdir) {
 
1422
        return NULL;
 
1423
    }
 
1424
 
 
1425
    /* put a trailing slash onto the directory, in preparation for appending
 
1426
     * files to it as we discovery them within the directory */
 
1427
    dav_check_bufsize(pool, &fsctx->path1, DAV_BUFFER_PAD);
 
1428
    fsctx->path1.buf[fsctx->path1.cur_len++] = '/';
 
1429
    fsctx->path1.buf[fsctx->path1.cur_len] = '\0';        /* in pad area */
 
1430
 
 
1431
    /* if a secondary path is present, then do that, too */
 
1432
    if (fsctx->path2.buf != NULL) {
 
1433
        dav_check_bufsize(pool, &fsctx->path2, DAV_BUFFER_PAD);
 
1434
        fsctx->path2.buf[fsctx->path2.cur_len++] = '/';
 
1435
        fsctx->path2.buf[fsctx->path2.cur_len] = '\0';        /* in pad area */
 
1436
    }
 
1437
 
 
1438
    /* Note: the URI should ALREADY have a trailing "/" */
 
1439
 
 
1440
    /* for this first pass of files, all resources exist */
 
1441
    fsctx->res1.exists = 1;
 
1442
 
 
1443
    /* a file is the default; we'll adjust if we hit a directory */
 
1444
    fsctx->res1.collection = 0;
 
1445
    fsctx->res2.collection = 0;
 
1446
 
 
1447
    /* open and scan the directory */
 
1448
    if ((apr_dir_open(&dirp, fsctx->path1.buf, pool)) != APR_SUCCESS) {
 
1449
        /* ### need a better error */
 
1450
        return dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
 
1451
    }
 
1452
    while ((apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp)) == APR_SUCCESS) {
 
1453
        apr_size_t len;
 
1454
        apr_status_t status;
 
1455
 
 
1456
        len = strlen(dirent.name);
 
1457
 
 
1458
        /* avoid recursing into our current, parent, or state directories */
 
1459
        if (dirent.name[0] == '.'
 
1460
              && (len == 1 || (dirent.name[1] == '.' && len == 2))) {
 
1461
            continue;
 
1462
        }
 
1463
 
 
1464
        if (params->walk_type & DAV_WALKTYPE_AUTH) {
 
1465
            /* ### need to authorize each file */
 
1466
            /* ### example: .htaccess is normally configured to fail auth */
 
1467
 
 
1468
            /* stuff in the state directory is never authorized! */
 
1469
            if (!strcmp(dirent.name, DAV_FS_STATE_DIR)) {
 
1470
                continue;
 
1471
            }
 
1472
        }
 
1473
        /* skip the state dir unless a HIDDEN is performed */
 
1474
        if (!(params->walk_type & DAV_WALKTYPE_HIDDEN)
 
1475
            && !strcmp(dirent.name, DAV_FS_STATE_DIR)) {
 
1476
            continue;
 
1477
        }
 
1478
 
 
1479
        /* append this file onto the path buffer (copy null term) */
 
1480
        dav_buffer_place_mem(pool, &fsctx->path1, dirent.name, len + 1, 0);
 
1481
 
 
1482
 
 
1483
        /* ### Optimize me, dirent can give us what we need! */
 
1484
        status = apr_stat(&fsctx->info1.finfo, fsctx->path1.buf,
 
1485
                          APR_FINFO_NORM | APR_FINFO_LINK, pool);
 
1486
        if (status != APR_SUCCESS && status != APR_INCOMPLETE) {
 
1487
            /* woah! where'd it go? */
 
1488
            /* ### should have a better error here */
 
1489
            err = dav_new_error(pool, HTTP_NOT_FOUND, 0, NULL);
 
1490
            break;
 
1491
        }
 
1492
 
 
1493
        /* copy the file to the URI, too. NOTE: we will pad an extra byte
 
1494
           for the trailing slash later. */
 
1495
        dav_buffer_place_mem(pool, &fsctx->uri_buf, dirent.name, len + 1, 1);
 
1496
 
 
1497
        /* if there is a secondary path, then do that, too */
 
1498
        if (fsctx->path2.buf != NULL) {
 
1499
            dav_buffer_place_mem(pool, &fsctx->path2, dirent.name, len + 1, 0);
 
1500
        }
 
1501
 
 
1502
        /* set up the (internal) pathnames for the two resources */
 
1503
        fsctx->info1.pathname = fsctx->path1.buf;
 
1504
        fsctx->info2.pathname = fsctx->path2.buf;
 
1505
 
 
1506
        /* set up the URI for the current resource */
 
1507
        fsctx->res1.uri = fsctx->uri_buf.buf;
 
1508
 
 
1509
        /* ### for now, only process regular files (e.g. skip symlinks) */
 
1510
        if (fsctx->info1.finfo.filetype == APR_REG) {
 
1511
            /* call the function for the specified dir + file */
 
1512
            if ((err = (*params->func)(&fsctx->wres,
 
1513
                                       DAV_CALLTYPE_MEMBER)) != NULL) {
 
1514
                /* ### maybe add a higher-level description? */
 
1515
                break;
 
1516
            }
 
1517
        }
 
1518
        else if (fsctx->info1.finfo.filetype == APR_DIR) {
 
1519
            apr_size_t save_path_len = fsctx->path1.cur_len;
 
1520
            apr_size_t save_uri_len = fsctx->uri_buf.cur_len;
 
1521
            apr_size_t save_path2_len = fsctx->path2.cur_len;
 
1522
 
 
1523
            /* adjust length to incorporate the subdir name */
 
1524
            fsctx->path1.cur_len += len;
 
1525
            fsctx->path2.cur_len += len;
 
1526
 
 
1527
            /* adjust URI length to incorporate subdir and a slash */
 
1528
            fsctx->uri_buf.cur_len += len + 1;
 
1529
            fsctx->uri_buf.buf[fsctx->uri_buf.cur_len - 1] = '/';
 
1530
            fsctx->uri_buf.buf[fsctx->uri_buf.cur_len] = '\0';
 
1531
 
 
1532
            /* switch over to a collection */
 
1533
            fsctx->res1.collection = 1;
 
1534
            fsctx->res2.collection = 1;
 
1535
 
 
1536
            /* recurse on the subdir */
 
1537
            /* ### don't always want to quit on error from single child */
 
1538
            if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) {
 
1539
                /* ### maybe add a higher-level description? */
 
1540
                break;
 
1541
            }
 
1542
 
 
1543
            /* put the various information back */
 
1544
            fsctx->path1.cur_len = save_path_len;
 
1545
            fsctx->path2.cur_len = save_path2_len;
 
1546
            fsctx->uri_buf.cur_len = save_uri_len;
 
1547
 
 
1548
            fsctx->res1.collection = 0;
 
1549
            fsctx->res2.collection = 0;
 
1550
 
 
1551
            /* assert: res1.exists == 1 */
 
1552
        }
 
1553
    }
 
1554
 
 
1555
    /* ### check the return value of this? */
 
1556
    apr_dir_close(dirp);
 
1557
 
 
1558
    if (err != NULL)
 
1559
        return err;
 
1560
 
 
1561
    if (params->walk_type & DAV_WALKTYPE_LOCKNULL) {
 
1562
        apr_size_t offset = 0;
 
1563
 
 
1564
        /* null terminate the directory name */
 
1565
        fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0';
 
1566
 
 
1567
        /* Include any lock null resources found in this collection */
 
1568
        fsctx->res1.collection = 1;
 
1569
        if ((err = dav_fs_get_locknull_members(&fsctx->res1,
 
1570
                                               &fsctx->locknull_buf)) != NULL) {
 
1571
            /* ### maybe add a higher-level description? */
 
1572
            return err;
 
1573
        }
 
1574
 
 
1575
        /* put a slash back on the end of the directory */
 
1576
        fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
 
1577
 
 
1578
        /* these are all non-existant (files) */
 
1579
        fsctx->res1.exists = 0;
 
1580
        fsctx->res1.collection = 0;
 
1581
        memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
 
1582
 
 
1583
        while (offset < fsctx->locknull_buf.cur_len) {
 
1584
            apr_size_t len = strlen(fsctx->locknull_buf.buf + offset);
 
1585
            dav_lock *locks = NULL;
 
1586
 
 
1587
            /*
 
1588
            ** Append the locknull file to the paths and the URI. Note that
 
1589
            ** we don't have to pad the URI for a slash since a locknull
 
1590
            ** resource is not a collection.
 
1591
            */
 
1592
            dav_buffer_place_mem(pool, &fsctx->path1,
 
1593
                                 fsctx->locknull_buf.buf + offset, len + 1, 0);
 
1594
            dav_buffer_place_mem(pool, &fsctx->uri_buf,
 
1595
                                 fsctx->locknull_buf.buf + offset, len + 1, 0);
 
1596
            if (fsctx->path2.buf != NULL) {
 
1597
                dav_buffer_place_mem(pool, &fsctx->path2,
 
1598
                                     fsctx->locknull_buf.buf + offset,
 
1599
                                     len + 1, 0);
 
1600
            }
 
1601
 
 
1602
            /* set up the (internal) pathnames for the two resources */
 
1603
            fsctx->info1.pathname = fsctx->path1.buf;
 
1604
            fsctx->info2.pathname = fsctx->path2.buf;
 
1605
 
 
1606
            /* set up the URI for the current resource */
 
1607
            fsctx->res1.uri = fsctx->uri_buf.buf;
 
1608
 
 
1609
            /*
 
1610
            ** To prevent a PROPFIND showing an expired locknull
 
1611
            ** resource, query the lock database to force removal
 
1612
            ** of both the lock entry and .locknull, if necessary..
 
1613
            ** Sure, the query in PROPFIND would do this.. after
 
1614
            ** the locknull resource was already included in the
 
1615
            ** return.
 
1616
            **
 
1617
            ** NOTE: we assume the caller has opened the lock database
 
1618
            **       if they have provided DAV_WALKTYPE_LOCKNULL.
 
1619
            */
 
1620
            /* ### we should also look into opening it read-only and
 
1621
               ### eliding timed-out items from the walk, yet leaving
 
1622
               ### them in the locknull database until somebody opens
 
1623
               ### the thing writable.
 
1624
               */
 
1625
            /* ### probably ought to use has_locks. note the problem
 
1626
               ### mentioned above, though... we would traverse this as
 
1627
               ### a locknull, but then a PROPFIND would load the lock
 
1628
               ### info, causing a timeout and the locks would not be
 
1629
               ### reported. Therefore, a null resource would be returned
 
1630
               ### in the PROPFIND.
 
1631
               ###
 
1632
               ### alternative: just load unresolved locks. any direct
 
1633
               ### locks will be timed out (correct). any indirect will
 
1634
               ### not (correct; consider if a parent timed out -- the
 
1635
               ### timeout routines do not walk and remove indirects;
 
1636
               ### even the resolve func would probably fail when it
 
1637
               ### tried to find a timed-out direct lock).
 
1638
            */
 
1639
            if ((err = dav_lock_query(params->lockdb, &fsctx->res1,
 
1640
                                      &locks)) != NULL) {
 
1641
                /* ### maybe add a higher-level description? */
 
1642
                return err;
 
1643
            }
 
1644
 
 
1645
            /* call the function for the specified dir + file */
 
1646
            if (locks != NULL &&
 
1647
                (err = (*params->func)(&fsctx->wres,
 
1648
                                       DAV_CALLTYPE_LOCKNULL)) != NULL) {
 
1649
                /* ### maybe add a higher-level description? */
 
1650
                return err;
 
1651
            }
 
1652
 
 
1653
            offset += len + 1;
 
1654
        }
 
1655
 
 
1656
        /* reset the exists flag */
 
1657
        fsctx->res1.exists = 1;
 
1658
    }
 
1659
 
 
1660
    if (params->walk_type & DAV_WALKTYPE_POSTFIX) {
 
1661
        /* replace the dirs' trailing slashes with null terms */
 
1662
        fsctx->path1.buf[--fsctx->path1.cur_len] = '\0';
 
1663
        fsctx->uri_buf.buf[--fsctx->uri_buf.cur_len] = '\0';
 
1664
        if (fsctx->path2.buf != NULL) {
 
1665
            fsctx->path2.buf[--fsctx->path2.cur_len] = '\0';
 
1666
        }
 
1667
 
 
1668
        /* this is a collection which exists */
 
1669
        fsctx->res1.collection = 1;
 
1670
 
 
1671
        return (*params->func)(&fsctx->wres, DAV_CALLTYPE_POSTFIX);
 
1672
    }
 
1673
 
 
1674
    return NULL;
 
1675
}
 
1676
 
 
1677
static dav_error * dav_fs_internal_walk(const dav_walk_params *params,
 
1678
                                        int depth, int is_move,
 
1679
                                        const dav_resource *root_dst,
 
1680
                                        dav_response **response)
 
1681
{
 
1682
    dav_fs_walker_context fsctx = { 0 };
 
1683
    dav_error *err;
 
1684
    dav_fs_copymove_walk_ctx cm_ctx = { 0 };
 
1685
 
 
1686
#if DAV_DEBUG
 
1687
    if ((params->walk_type & DAV_WALKTYPE_LOCKNULL) != 0
 
1688
        && params->lockdb == NULL) {
 
1689
        return dav_new_error(params->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
 
1690
                             "DESIGN ERROR: walker called to walk locknull "
 
1691
                             "resources, but a lockdb was not provided.");
 
1692
    }
 
1693
#endif
 
1694
 
 
1695
    fsctx.params = params;
 
1696
    fsctx.wres.walk_ctx = params->walk_ctx;
 
1697
    fsctx.wres.pool = params->pool;
 
1698
 
 
1699
    /* ### zero out versioned, working, baselined? */
 
1700
 
 
1701
    fsctx.res1 = *params->root;
 
1702
    fsctx.res1.pool = params->pool;
 
1703
 
 
1704
    fsctx.res1.info = &fsctx.info1;
 
1705
    fsctx.info1 = *params->root->info;
 
1706
 
 
1707
    /* the pathname is stored in the path1 buffer */
 
1708
    dav_buffer_init(params->pool, &fsctx.path1, fsctx.info1.pathname);
 
1709
    fsctx.info1.pathname = fsctx.path1.buf;
 
1710
 
 
1711
    if (root_dst != NULL) {
 
1712
        /* internal call from the COPY/MOVE code. set it up. */
 
1713
 
 
1714
        fsctx.wres.walk_ctx = &cm_ctx;
 
1715
        cm_ctx.is_move = is_move;
 
1716
        cm_ctx.res_dst = &fsctx.res2;
 
1717
        cm_ctx.root = params->root;
 
1718
        cm_ctx.pool = params->pool;
 
1719
 
 
1720
        fsctx.res2 = *root_dst;
 
1721
        fsctx.res2.exists = 0;
 
1722
        fsctx.res2.collection = 0;
 
1723
        fsctx.res2.uri = NULL;          /* we don't track this */
 
1724
        fsctx.res2.pool = params->pool;
 
1725
 
 
1726
        fsctx.res2.info = &fsctx.info2;
 
1727
        fsctx.info2 = *root_dst->info;
 
1728
 
 
1729
        /* res2 does not exist -- clear its finfo structure */
 
1730
        memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo));
 
1731
 
 
1732
        /* the pathname is stored in the path2 buffer */
 
1733
        dav_buffer_init(params->pool, &fsctx.path2, fsctx.info2.pathname);
 
1734
        fsctx.info2.pathname = fsctx.path2.buf;
 
1735
    }
 
1736
 
 
1737
    /* prep the URI buffer */
 
1738
    dav_buffer_init(params->pool, &fsctx.uri_buf, params->root->uri);
 
1739
 
 
1740
    /* if we have a directory, then ensure the URI has a trailing "/" */
 
1741
    if (fsctx.res1.collection
 
1742
        && fsctx.uri_buf.buf[fsctx.uri_buf.cur_len - 1] != '/') {
 
1743
 
 
1744
        /* this will fall into the pad area */
 
1745
        fsctx.uri_buf.buf[fsctx.uri_buf.cur_len++] = '/';
 
1746
        fsctx.uri_buf.buf[fsctx.uri_buf.cur_len] = '\0';
 
1747
    }
 
1748
 
 
1749
    /* the current resource's URI is stored in the uri_buf buffer */
 
1750
    fsctx.res1.uri = fsctx.uri_buf.buf;
 
1751
 
 
1752
    /* point the callback's resource at our structure */
 
1753
    fsctx.wres.resource = &fsctx.res1;
 
1754
 
 
1755
    /* always return the error, and any/all multistatus responses */
 
1756
    err = dav_fs_walker(&fsctx, depth);
 
1757
    *response = fsctx.wres.response;
 
1758
    return err;
 
1759
}
 
1760
 
 
1761
static dav_error * dav_fs_walk(const dav_walk_params *params, int depth,
 
1762
                               dav_response **response)
 
1763
{
 
1764
    /* always return the error, and any/all multistatus responses */
 
1765
    return dav_fs_internal_walk(params, depth, 0, NULL, response);
 
1766
}
 
1767
 
 
1768
/* dav_fs_etag:  Stolen from ap_make_etag.  Creates a strong etag
 
1769
 *    for file path.
 
1770
 * ### do we need to return weak tags sometimes?
 
1771
 */
 
1772
static const char *dav_fs_getetag(const dav_resource *resource)
 
1773
{
 
1774
    dav_resource_private *ctx = resource->info;
 
1775
 
 
1776
    if (!resource->exists)
 
1777
        return apr_pstrdup(ctx->pool, "");
 
1778
 
 
1779
    if (ctx->finfo.filetype != 0) {
 
1780
        return apr_psprintf(ctx->pool, "\"%lx-%lx-%lx\"",
 
1781
                           (unsigned long) ctx->finfo.inode,
 
1782
                           (unsigned long) ctx->finfo.size,
 
1783
                           (unsigned long) ctx->finfo.mtime);
 
1784
    }
 
1785
 
 
1786
    return apr_psprintf(ctx->pool, "\"%lx\"", (unsigned long) ctx->finfo.mtime);
 
1787
}
 
1788
 
 
1789
static const dav_hooks_repository dav_hooks_repository_fs =
 
1790
{
 
1791
    DEBUG_GET_HANDLER,   /* normally: special GET handling not required */
 
1792
    dav_fs_get_resource,
 
1793
    dav_fs_get_parent_resource,
 
1794
    dav_fs_is_same_resource,
 
1795
    dav_fs_is_parent_resource,
 
1796
    dav_fs_open_stream,
 
1797
    dav_fs_close_stream,
 
1798
    dav_fs_write_stream,
 
1799
    dav_fs_seek_stream,
 
1800
#if DEBUG_GET_HANDLER
 
1801
    dav_fs_set_headers,
 
1802
    dav_fs_deliver,
 
1803
#else
 
1804
    NULL,
 
1805
    NULL,
 
1806
#endif
 
1807
    dav_fs_create_collection,
 
1808
    dav_fs_copy_resource,
 
1809
    dav_fs_move_resource,
 
1810
    dav_fs_remove_resource,
 
1811
    dav_fs_walk,
 
1812
    dav_fs_getetag,
 
1813
};
 
1814
 
 
1815
static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
 
1816
                                          int propid, dav_prop_insert what,
 
1817
                                          apr_text_header *phdr)
 
1818
{
 
1819
    const char *value;
 
1820
    const char *s;
 
1821
    apr_pool_t *p = resource->info->pool;
 
1822
    const dav_liveprop_spec *info;
 
1823
    int global_ns;
 
1824
 
 
1825
    /* an HTTP-date can be 29 chars plus a null term */
 
1826
    /* a 64-bit size can be 20 chars plus a null term */
 
1827
    char buf[DAV_TIMEBUF_SIZE];
 
1828
 
 
1829
    /*
 
1830
    ** None of FS provider properties are defined if the resource does not
 
1831
    ** exist. Just bail for this case.
 
1832
    **
 
1833
    ** Even though we state that the FS properties are not defined, the
 
1834
    ** client cannot store dead values -- we deny that thru the is_writable
 
1835
    ** hook function.
 
1836
    */
 
1837
    if (!resource->exists)
 
1838
        return DAV_PROP_INSERT_NOTDEF;
 
1839
 
 
1840
    switch (propid) {
 
1841
    case DAV_PROPID_creationdate:
 
1842
        /*
 
1843
        ** Closest thing to a creation date. since we don't actually
 
1844
        ** perform the operations that would modify ctime (after we
 
1845
        ** create the file), then we should be pretty safe here.
 
1846
        */
 
1847
        dav_format_time(DAV_STYLE_ISO8601,
 
1848
                        resource->info->finfo.ctime,
 
1849
                        buf);
 
1850
        value = buf;
 
1851
        break;
 
1852
 
 
1853
    case DAV_PROPID_getcontentlength:
 
1854
        /* our property, but not defined on collection resources */
 
1855
        if (resource->collection)
 
1856
            return DAV_PROP_INSERT_NOTDEF;
 
1857
 
 
1858
        (void) sprintf(buf, "%" APR_OFF_T_FMT, resource->info->finfo.size);
 
1859
        value = buf;
 
1860
        break;
 
1861
 
 
1862
    case DAV_PROPID_getetag:
 
1863
        value = dav_fs_getetag(resource);
 
1864
        break;
 
1865
 
 
1866
    case DAV_PROPID_getlastmodified:
 
1867
        dav_format_time(DAV_STYLE_RFC822,
 
1868
                        resource->info->finfo.mtime,
 
1869
                        buf);
 
1870
        value = buf;
 
1871
        break;
 
1872
 
 
1873
    case DAV_PROPID_FS_executable:
 
1874
        /* our property, but not defined on collection resources */
 
1875
        if (resource->collection)
 
1876
            return DAV_PROP_INSERT_NOTDEF;
 
1877
 
 
1878
        /* our property, but not defined on this platform */
 
1879
        if (!(resource->info->finfo.valid & APR_FINFO_UPROT))
 
1880
            return DAV_PROP_INSERT_NOTDEF;
 
1881
 
 
1882
        /* the files are "ours" so we only need to check owner exec privs */
 
1883
        if (resource->info->finfo.protection & APR_UEXECUTE)
 
1884
            value = "T";
 
1885
        else
 
1886
            value = "F";
 
1887
        break;
 
1888
 
 
1889
    default:
 
1890
        /* ### what the heck was this property? */
 
1891
        return DAV_PROP_INSERT_NOTDEF;
 
1892
    }
 
1893
 
 
1894
    /* assert: value != NULL */
 
1895
 
 
1896
    /* get the information and global NS index for the property */
 
1897
    global_ns = dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
 
1898
 
 
1899
    /* assert: info != NULL && info->name != NULL */
 
1900
 
 
1901
    /* DBG3("FS: inserting lp%d:%s  (local %d)", ns, scan->name, scan->ns); */
 
1902
 
 
1903
    if (what == DAV_PROP_INSERT_VALUE) {
 
1904
        s = apr_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
 
1905
                         global_ns, info->name, value, global_ns, info->name);
 
1906
    }
 
1907
    else if (what == DAV_PROP_INSERT_NAME) {
 
1908
        s = apr_psprintf(p, "<lp%d:%s/>" DEBUG_CR, global_ns, info->name);
 
1909
    }
 
1910
    else {
 
1911
        /* assert: what == DAV_PROP_INSERT_SUPPORTED */
 
1912
        s = apr_psprintf(p,
 
1913
                         "<D:supported-live-property D:name=\"%s\" "
 
1914
                         "D:namespace=\"%s\"/>" DEBUG_CR,
 
1915
                         info->name, dav_fs_namespace_uris[info->ns]);
 
1916
    }
 
1917
    apr_text_append(p, phdr, s);
 
1918
 
 
1919
    /* we inserted what was asked for */
 
1920
    return what;
 
1921
}
 
1922
 
 
1923
static int dav_fs_is_writable(const dav_resource *resource, int propid)
 
1924
{
 
1925
    const dav_liveprop_spec *info;
 
1926
 
 
1927
#ifdef DAV_FS_HAS_EXECUTABLE
 
1928
    /* if we have the executable property, and this isn't a collection,
 
1929
       then the property is writable. */
 
1930
    if (propid == DAV_PROPID_FS_executable && !resource->collection)
 
1931
        return 1;
 
1932
#endif
 
1933
 
 
1934
    (void) dav_get_liveprop_info(propid, &dav_fs_liveprop_group, &info);
 
1935
    return info->is_writable;
 
1936
}
 
1937
 
 
1938
static dav_error *dav_fs_patch_validate(const dav_resource *resource,
 
1939
                                        const apr_xml_elem *elem,
 
1940
                                        int operation,
 
1941
                                        void **context,
 
1942
                                        int *defer_to_dead)
 
1943
{
 
1944
    const apr_text *cdata;
 
1945
    const apr_text *f_cdata;
 
1946
    char value;
 
1947
    dav_elem_private *priv = elem->priv;
 
1948
 
 
1949
    if (priv->propid != DAV_PROPID_FS_executable) {
 
1950
        *defer_to_dead = 1;
 
1951
        return NULL;
 
1952
    }
 
1953
 
 
1954
    if (operation == DAV_PROP_OP_DELETE) {
 
1955
        return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
 
1956
                             "The 'executable' property cannot be removed.");
 
1957
    }
 
1958
 
 
1959
    cdata = elem->first_cdata.first;
 
1960
 
 
1961
    /* ### hmm. this isn't actually looking at all the possible text items */
 
1962
    f_cdata = elem->first_child == NULL
 
1963
        ? NULL
 
1964
        : elem->first_child->following_cdata.first;
 
1965
 
 
1966
    /* DBG3("name=%s  cdata=%s  f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */
 
1967
 
 
1968
    if (cdata == NULL) {
 
1969
        if (f_cdata == NULL) {
 
1970
            return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
 
1971
                                 "The 'executable' property expects a single "
 
1972
                                 "character, valued 'T' or 'F'. There was no "
 
1973
                                 "value submitted.");
 
1974
        }
 
1975
        cdata = f_cdata;
 
1976
    }
 
1977
    else if (f_cdata != NULL)
 
1978
        goto too_long;
 
1979
 
 
1980
    if (cdata->next != NULL || strlen(cdata->text) != 1)
 
1981
        goto too_long;
 
1982
 
 
1983
    value = cdata->text[0];
 
1984
    if (value != 'T' && value != 'F') {
 
1985
        return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
 
1986
                             "The 'executable' property expects a single "
 
1987
                             "character, valued 'T' or 'F'. The value "
 
1988
                             "submitted is invalid.");
 
1989
    }
 
1990
 
 
1991
    *context = (void *)((long)(value == 'T'));
 
1992
 
 
1993
    return NULL;
 
1994
 
 
1995
  too_long:
 
1996
    return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
 
1997
                         "The 'executable' property expects a single "
 
1998
                         "character, valued 'T' or 'F'. The value submitted "
 
1999
                         "has too many characters.");
 
2000
 
 
2001
}
 
2002
 
 
2003
static dav_error *dav_fs_patch_exec(const dav_resource *resource,
 
2004
                                    const apr_xml_elem *elem,
 
2005
                                    int operation,
 
2006
                                    void *context,
 
2007
                                    dav_liveprop_rollback **rollback_ctx)
 
2008
{
 
2009
    long value = context != NULL;
 
2010
    apr_fileperms_t perms = resource->info->finfo.protection;
 
2011
    long old_value = (perms & APR_UEXECUTE) != 0;
 
2012
 
 
2013
    /* assert: prop == executable. operation == SET. */
 
2014
 
 
2015
    /* don't do anything if there is no change. no rollback info either. */
 
2016
    /* DBG2("new value=%d  (old=%d)", value, old_value); */
 
2017
    if (value == old_value)
 
2018
        return NULL;
 
2019
 
 
2020
    perms &= ~APR_UEXECUTE;
 
2021
    if (value)
 
2022
        perms |= APR_UEXECUTE;
 
2023
 
 
2024
    if (apr_file_perms_set(resource->info->pathname, perms) != APR_SUCCESS) {
 
2025
        return dav_new_error(resource->info->pool,
 
2026
                             HTTP_INTERNAL_SERVER_ERROR, 0,
 
2027
                             "Could not set the executable flag of the "
 
2028
                             "target resource.");
 
2029
    }
 
2030
 
 
2031
    /* update the resource and set up the rollback context */
 
2032
    resource->info->finfo.protection = perms;
 
2033
    *rollback_ctx = (dav_liveprop_rollback *)old_value;
 
2034
 
 
2035
    return NULL;
 
2036
}
 
2037
 
 
2038
static void dav_fs_patch_commit(const dav_resource *resource,
 
2039
                                int operation,
 
2040
                                void *context,
 
2041
                                dav_liveprop_rollback *rollback_ctx)
 
2042
{
 
2043
    /* nothing to do */
 
2044
}
 
2045
 
 
2046
static dav_error *dav_fs_patch_rollback(const dav_resource *resource,
 
2047
                                        int operation,
 
2048
                                        void *context,
 
2049
                                        dav_liveprop_rollback *rollback_ctx)
 
2050
{
 
2051
    apr_fileperms_t perms = resource->info->finfo.protection & ~APR_UEXECUTE;
 
2052
    int value = rollback_ctx != NULL;
 
2053
 
 
2054
    /* assert: prop == executable. operation == SET. */
 
2055
 
 
2056
    /* restore the executable bit */
 
2057
    if (value)
 
2058
        perms |= APR_UEXECUTE;
 
2059
 
 
2060
    if (apr_file_perms_set(resource->info->pathname, perms) != APR_SUCCESS) {
 
2061
        return dav_new_error(resource->info->pool,
 
2062
                             HTTP_INTERNAL_SERVER_ERROR, 0,
 
2063
                             "After a failure occurred, the resource's "
 
2064
                             "executable flag could not be restored.");
 
2065
    }
 
2066
 
 
2067
    /* restore the resource's state */
 
2068
    resource->info->finfo.protection = perms;
 
2069
 
 
2070
    return NULL;
 
2071
}
 
2072
 
 
2073
 
 
2074
static const dav_hooks_liveprop dav_hooks_liveprop_fs =
 
2075
{
 
2076
    dav_fs_insert_prop,
 
2077
    dav_fs_is_writable,
 
2078
    dav_fs_namespace_uris,
 
2079
    dav_fs_patch_validate,
 
2080
    dav_fs_patch_exec,
 
2081
    dav_fs_patch_commit,
 
2082
    dav_fs_patch_rollback
 
2083
};
 
2084
 
 
2085
static const dav_provider dav_fs_provider =
 
2086
{
 
2087
    &dav_hooks_repository_fs,
 
2088
    &dav_hooks_db_dbm,
 
2089
    &dav_hooks_locks_fs,
 
2090
    NULL,               /* vsn */
 
2091
    NULL,               /* binding */
 
2092
    NULL,               /* search */
 
2093
 
 
2094
    NULL                /* ctx */
 
2095
};
 
2096
 
 
2097
void dav_fs_gather_propsets(apr_array_header_t *uris)
 
2098
{
 
2099
#ifdef DAV_FS_HAS_EXECUTABLE
 
2100
    *(const char **)apr_array_push(uris) =
 
2101
        "<http://apache.org/dav/propset/fs/1>";
 
2102
#endif
 
2103
}
 
2104
 
 
2105
int dav_fs_find_liveprop(const dav_resource *resource,
 
2106
                         const char *ns_uri, const char *name,
 
2107
                         const dav_hooks_liveprop **hooks)
 
2108
{
 
2109
    /* don't try to find any liveprops if this isn't "our" resource */
 
2110
    if (resource->hooks != &dav_hooks_repository_fs)
 
2111
        return 0;
 
2112
    return dav_do_find_liveprop(ns_uri, name, &dav_fs_liveprop_group, hooks);
 
2113
}
 
2114
 
 
2115
void dav_fs_insert_all_liveprops(request_rec *r, const dav_resource *resource,
 
2116
                                 dav_prop_insert what, apr_text_header *phdr)
 
2117
{
 
2118
    /* don't insert any liveprops if this isn't "our" resource */
 
2119
    if (resource->hooks != &dav_hooks_repository_fs)
 
2120
        return;
 
2121
 
 
2122
    if (!resource->exists) {
 
2123
        /* a lock-null resource */
 
2124
        /*
 
2125
        ** ### technically, we should insert empty properties. dunno offhand
 
2126
        ** ### what part of the spec said this, but it was essentially thus:
 
2127
        ** ### "the properties should be defined, but may have no value".
 
2128
        */
 
2129
        return;
 
2130
    }
 
2131
 
 
2132
    (void) dav_fs_insert_prop(resource, DAV_PROPID_creationdate,
 
2133
                              what, phdr);
 
2134
    (void) dav_fs_insert_prop(resource, DAV_PROPID_getcontentlength,
 
2135
                              what, phdr);
 
2136
    (void) dav_fs_insert_prop(resource, DAV_PROPID_getlastmodified,
 
2137
                              what, phdr);
 
2138
    (void) dav_fs_insert_prop(resource, DAV_PROPID_getetag,
 
2139
                              what, phdr);
 
2140
 
 
2141
#ifdef DAV_FS_HAS_EXECUTABLE
 
2142
    /* Only insert this property if it is defined for this platform. */
 
2143
    (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable,
 
2144
                              what, phdr);
 
2145
#endif
 
2146
 
 
2147
    /* ### we know the others aren't defined as liveprops */
 
2148
}
 
2149
 
 
2150
void dav_fs_register(apr_pool_t *p)
 
2151
{
 
2152
    /* register the namespace URIs */
 
2153
    dav_register_liveprop_group(p, &dav_fs_liveprop_group);
 
2154
 
 
2155
    /* register the repository provider */
 
2156
    dav_register_provider(p, "filesystem", &dav_fs_provider);
 
2157
}