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