]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/list.c
Update OpenSSL to 1.1.1.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_repos / list.c
1 /* list.c : listing repository contents
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include <apr_pools.h>
24 #include <apr_fnmatch.h>
25
26 #include "svn_pools.h"
27 #include "svn_error.h"
28 #include "svn_dirent_uri.h"
29 #include "svn_time.h"
30
31 #include "private/svn_repos_private.h"
32 #include "private/svn_sorts_private.h"
33 #include "private/svn_utf_private.h"
34 #include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
35
36 #include "repos.h"
37
38 \f
39
40 /* Utility function.  Given DIRENT->KIND, set all other elements of *DIRENT
41  * with the values retrieved for PATH under ROOT.  Allocate them in POOL.
42  */
43 static svn_error_t *
44 fill_dirent(svn_dirent_t *dirent,
45             svn_fs_root_t *root,
46             const char *path,
47             apr_pool_t *scratch_pool)
48 {
49   const char *datestring;
50
51   if (dirent->kind == svn_node_file)
52     SVN_ERR(svn_fs_file_length(&(dirent->size), root, path, scratch_pool));
53   else
54     dirent->size = SVN_INVALID_FILESIZE;
55
56   SVN_ERR(svn_fs_node_has_props(&dirent->has_props, root, path,
57                                 scratch_pool));
58
59   SVN_ERR(svn_repos_get_committed_info(&(dirent->created_rev),
60                                        &datestring,
61                                        &(dirent->last_author),
62                                        root, path, scratch_pool));
63   if (datestring)
64     SVN_ERR(svn_time_from_cstring(&(dirent->time), datestring,
65                                   scratch_pool));
66
67   return SVN_NO_ERROR;
68 }
69
70 svn_error_t *
71 svn_repos_stat(svn_dirent_t **dirent,
72                svn_fs_root_t *root,
73                const char *path,
74                apr_pool_t *pool)
75 {
76   svn_node_kind_t kind;
77   svn_dirent_t *ent;
78
79   SVN_ERR(svn_fs_check_path(&kind, root, path, pool));
80
81   if (kind == svn_node_none)
82     {
83       *dirent = NULL;
84       return SVN_NO_ERROR;
85     }
86
87   ent = svn_dirent_create(pool);
88   ent->kind = kind;
89
90   SVN_ERR(fill_dirent(ent, root, path, pool));
91
92   *dirent = ent;
93   return SVN_NO_ERROR;
94 }
95
96 /* Return TRUE of DIRNAME matches any of the const char * in PATTERNS.
97  * Note that any DIRNAME will match if PATTERNS is empty.
98  * Use SCRATCH_BUFFER for temporary string contents. */
99 static svn_boolean_t
100 matches_any(const char *dirname,
101             const apr_array_header_t *patterns,
102             svn_membuf_t *scratch_buffer)
103 {
104   return patterns
105        ? svn_utf__fuzzy_glob_match(dirname, patterns, scratch_buffer)
106        : TRUE;
107 }
108
109 /* Utility to prevent code duplication.
110  *
111  * Construct a svn_dirent_t for PATH of type KIND under ROOT and, if
112  * PATH_INFO_ONLY is not set, fill it.  Call RECEIVER with the result
113  * and RECEIVER_BATON.
114  *
115  * Use SCRATCH_POOL for temporary allocations.
116  */
117 static svn_error_t *
118 report_dirent(svn_fs_root_t *root,
119               const char *path,
120               svn_node_kind_t kind,
121               svn_boolean_t path_info_only,
122               svn_repos_dirent_receiver_t receiver,
123               void *receiver_baton,
124               apr_pool_t *scratch_pool)
125 {
126   svn_dirent_t dirent = { 0 };
127
128   /* Fetch the details to report - if required. */
129   dirent.kind = kind;
130   if (!path_info_only)
131     SVN_ERR(fill_dirent(&dirent, root, path, scratch_pool));
132
133   /* Report the entry. */
134   SVN_ERR(receiver(path, &dirent, receiver_baton, scratch_pool));
135
136   return SVN_NO_ERROR;
137 }
138
139 /* Utility data struct, used to attach a filter result flag to a dirent. */
140 typedef struct filtered_dirent_t
141 {
142   /* Actual dirent, never NULL. */
143   svn_fs_dirent_t *dirent;
144
145   /* DIRENT passed the filter. */
146   svn_boolean_t is_match;
147 } filtered_dirent_t;
148
149 /* Implement a standard sort function for filtered_dirent_t *, sorting them
150  * by entry name. */
151 static int
152 compare_filtered_dirent(const void *lhs,
153                         const void *rhs)
154 {
155   const filtered_dirent_t *lhs_dirent = (const filtered_dirent_t *)lhs;
156   const filtered_dirent_t *rhs_dirent = (const filtered_dirent_t *)rhs;
157
158   return strcmp(lhs_dirent->dirent->name, rhs_dirent->dirent->name);
159 }
160
161 /* Core of svn_repos_list with the same parameter list.
162  *
163  * However, DEPTH is not svn_depth_empty and PATH has already been reported.
164  * Therefore, we can call this recursively.
165  *
166  * Uses SCRATCH_BUFFER for temporary string contents.
167  */
168 static svn_error_t *
169 do_list(svn_fs_root_t *root,
170         const char *path,
171         const apr_array_header_t *patterns,
172         svn_depth_t depth,
173         svn_boolean_t path_info_only,
174         svn_repos_authz_func_t authz_read_func,
175         void *authz_read_baton,
176         svn_repos_dirent_receiver_t receiver,
177         void *receiver_baton,
178         svn_cancel_func_t cancel_func,
179         void *cancel_baton,
180         svn_membuf_t *scratch_buffer,
181         apr_pool_t *scratch_pool)
182 {
183   apr_hash_t *entries;
184   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
185   apr_hash_index_t *hi;
186   apr_array_header_t *sorted;
187   int i;
188
189   /* Fetch all directory entries, filter and sort them.
190    *
191    * Performance trade-off:
192    * Constructing a full path vs. faster sort due to authz filtering.
193    * We filter according to DEPTH and PATTERNS only because constructing
194    * the full path required for authz is somewhat expensive and we don't
195    * want to do this twice while authz will rarely filter paths out.
196    */
197   SVN_ERR(svn_fs_dir_entries(&entries, root, path, scratch_pool));
198   sorted = apr_array_make(scratch_pool, apr_hash_count(entries),
199                           sizeof(filtered_dirent_t));
200   for (hi = apr_hash_first(scratch_pool, entries); hi; hi = apr_hash_next(hi))
201     {
202       filtered_dirent_t filtered;
203       svn_pool_clear(iterpool);
204
205       filtered.dirent = apr_hash_this_val(hi);
206
207       /* Skip directories if we want to report files only. */
208       if (filtered.dirent->kind == svn_node_dir && depth == svn_depth_files)
209         continue;
210
211       /* We can skip files that don't match any of the search patterns. */
212       filtered.is_match = matches_any(filtered.dirent->name, patterns,
213                                       scratch_buffer);
214       if (!filtered.is_match && filtered.dirent->kind == svn_node_file)
215         continue;
216
217       APR_ARRAY_PUSH(sorted, filtered_dirent_t) = filtered;
218     }
219
220   svn_sort__array(sorted, compare_filtered_dirent);
221
222   /* Iterate over all remaining directory entries and report them.
223    * Recurse into sub-directories if requested. */
224   for (i = 0; i < sorted->nelts; ++i)
225     {
226       const char *sub_path;
227       filtered_dirent_t *filtered;
228       svn_fs_dirent_t *dirent;
229
230       svn_pool_clear(iterpool);
231
232       filtered = &APR_ARRAY_IDX(sorted, i, filtered_dirent_t);
233       dirent = filtered->dirent;
234
235       /* Skip paths that we don't have access to? */
236       sub_path = svn_dirent_join(path, dirent->name, iterpool);
237       if (authz_read_func)
238         {
239           svn_boolean_t has_access;
240           SVN_ERR(authz_read_func(&has_access, root, sub_path,
241                                   authz_read_baton, iterpool));
242           if (!has_access)
243             continue;
244         }
245
246       /* Report entry, if it passed the filter. */
247       if (filtered->is_match)
248         SVN_ERR(report_dirent(root, sub_path, dirent->kind, path_info_only,
249                               receiver, receiver_baton, iterpool));
250
251       /* Check for cancellation before recursing down.  This should be
252        * slightly more responsive for deep trees. */
253       if (cancel_func)
254         SVN_ERR(cancel_func(cancel_baton));
255
256       /* Recurse on directories. */
257       if (depth == svn_depth_infinity && dirent->kind == svn_node_dir)
258         SVN_ERR(do_list(root, sub_path, patterns, svn_depth_infinity,
259                         path_info_only, authz_read_func, authz_read_baton,
260                         receiver, receiver_baton, cancel_func,
261                         cancel_baton, scratch_buffer, iterpool));
262     }
263
264   svn_pool_destroy(iterpool);
265
266   return SVN_NO_ERROR;
267 }
268
269 svn_error_t *
270 svn_repos_list(svn_fs_root_t *root,
271                const char *path,
272                const apr_array_header_t *patterns,
273                svn_depth_t depth,
274                svn_boolean_t path_info_only,
275                svn_repos_authz_func_t authz_read_func,
276                void *authz_read_baton,
277                svn_repos_dirent_receiver_t receiver,
278                void *receiver_baton,
279                svn_cancel_func_t cancel_func,
280                void *cancel_baton,
281                apr_pool_t *scratch_pool)
282 {
283   svn_membuf_t scratch_buffer;
284
285   /* Parameter check. */
286   svn_node_kind_t kind;
287   if (depth < svn_depth_empty)
288     return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
289                              "Invalid depth '%d' in svn_repos_list", depth);
290
291   /* Do we have access this sub-tree? */
292   if (authz_read_func)
293     {
294       svn_boolean_t has_access;
295       SVN_ERR(authz_read_func(&has_access, root, path, authz_read_baton,
296                               scratch_pool));
297       if (!has_access)
298         return SVN_NO_ERROR;
299     }
300
301   /* Does the sub-tree even exist?
302    *
303    * Note that we must do this after the authz check to not indirectly
304    * confirm the existence of PATH. */
305   SVN_ERR(svn_fs_check_path(&kind, root, path, scratch_pool));
306   if (kind == svn_node_file)
307     {
308       /* There is no recursion on files. */
309       depth = svn_depth_empty;
310     }
311   else if (kind != svn_node_dir)
312     {
313       return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
314                                _("Path '%s' not found"), path);
315     }
316
317   /* Special case: Empty pattern list.
318    * We don't want the server to waste time here. */
319   if (patterns && patterns->nelts == 0)
320     return SVN_NO_ERROR;
321
322   /* We need a scratch buffer for temporary string data.
323    * Create one with a reasonable initial size. */
324   svn_membuf__create(&scratch_buffer, 256, scratch_pool);
325
326   /* Actually report PATH, if it passes the filters. */
327   if (matches_any(svn_dirent_dirname(path, scratch_pool), patterns,
328                   &scratch_buffer))
329     SVN_ERR(report_dirent(root, path, kind, path_info_only,
330                           receiver, receiver_baton, scratch_pool));
331
332   /* Report directory contents if requested. */
333   if (depth > svn_depth_empty)
334     SVN_ERR(do_list(root, path, patterns, depth,
335                     path_info_only, authz_read_func, authz_read_baton,
336                     receiver, receiver_baton, cancel_func, cancel_baton,
337                     &scratch_buffer, scratch_pool));
338
339   return SVN_NO_ERROR;
340 }