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