1 | /* |
---|
2 | * repos.c: mod_dav_svn repository provider functions for Subversion |
---|
3 | * |
---|
4 | * ==================================================================== |
---|
5 | * Copyright (c) 2000-2007 CollabNet. All rights reserved. |
---|
6 | * |
---|
7 | * This software is licensed as described in the file COPYING, which |
---|
8 | * you should have received as part of this distribution. The terms |
---|
9 | * are also available at http://subversion.tigris.org/license-1.html. |
---|
10 | * If newer versions of this license are posted there, you may use a |
---|
11 | * newer version instead, at your option. |
---|
12 | * |
---|
13 | * This software consists of voluntary contributions made by many |
---|
14 | * individuals. For exact contribution history, see the revision |
---|
15 | * history and logs, available at http://subversion.tigris.org/. |
---|
16 | * ==================================================================== |
---|
17 | */ |
---|
18 | |
---|
19 | #define APR_WANT_STRFUNC |
---|
20 | #include <apr_want.h> |
---|
21 | #include <apr_strings.h> |
---|
22 | #include <apr_hash.h> |
---|
23 | #include <apr_lib.h> |
---|
24 | |
---|
25 | #include <httpd.h> |
---|
26 | #include <http_request.h> |
---|
27 | #include <http_protocol.h> |
---|
28 | #include <http_log.h> |
---|
29 | #include <http_core.h> /* for ap_construct_url */ |
---|
30 | #include <mod_dav.h> |
---|
31 | |
---|
32 | #include "svn_types.h" |
---|
33 | #include "svn_pools.h" |
---|
34 | #include "svn_error.h" |
---|
35 | #include "svn_time.h" |
---|
36 | #include "svn_fs.h" |
---|
37 | #include "svn_repos.h" |
---|
38 | #include "svn_dav.h" |
---|
39 | #include "svn_sorts.h" |
---|
40 | #include "svn_version.h" |
---|
41 | #include "svn_props.h" |
---|
42 | #include "mod_dav_svn.h" |
---|
43 | #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */ |
---|
44 | #include "private/svn_log.h" |
---|
45 | |
---|
46 | #include "dav_svn.h" |
---|
47 | |
---|
48 | |
---|
49 | #define DEFAULT_ACTIVITY_DB "dav/activities.d" |
---|
50 | |
---|
51 | |
---|
52 | struct dav_stream { |
---|
53 | const dav_resource *res; |
---|
54 | |
---|
55 | /* for reading from the FS */ |
---|
56 | svn_stream_t *rstream; |
---|
57 | |
---|
58 | /* for writing to the FS. we use wstream OR the handler/baton. */ |
---|
59 | svn_stream_t *wstream; |
---|
60 | svn_txdelta_window_handler_t delta_handler; |
---|
61 | void *delta_baton; |
---|
62 | }; |
---|
63 | |
---|
64 | |
---|
65 | /* Convenience structure that facilitates combined memory allocation of |
---|
66 | a dav_resource and dav_resource_private pair. */ |
---|
67 | typedef struct { |
---|
68 | dav_resource res; |
---|
69 | dav_resource_private priv; |
---|
70 | } dav_resource_combined; |
---|
71 | |
---|
72 | |
---|
73 | /* Helper-wrapper around svn_fs_check_path(), which takes the same |
---|
74 | arguments. But: if we attempt to stat a path like "file1/file2", |
---|
75 | then still return 'svn_node_none' to signal nonexistence, rather |
---|
76 | than a full-blown filesystem error. This allows mod_dav to throw |
---|
77 | 404 instead of 500. */ |
---|
78 | static dav_error * |
---|
79 | fs_check_path(svn_node_kind_t *kind, |
---|
80 | svn_fs_root_t *root, |
---|
81 | const char *path, |
---|
82 | apr_pool_t *pool) |
---|
83 | { |
---|
84 | svn_error_t *serr; |
---|
85 | svn_node_kind_t my_kind; |
---|
86 | |
---|
87 | serr = svn_fs_check_path(&my_kind, root, path, pool); |
---|
88 | |
---|
89 | /* Possibly trap other fs-errors here someday -- errors which may |
---|
90 | simply indicate the path's nonexistence, rather than a critical |
---|
91 | problem. */ |
---|
92 | if (serr && serr->apr_err == SVN_ERR_FS_NOT_DIRECTORY) |
---|
93 | { |
---|
94 | svn_error_clear(serr); |
---|
95 | *kind = svn_node_none; |
---|
96 | return NULL; |
---|
97 | } |
---|
98 | else if (serr) |
---|
99 | { |
---|
100 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
101 | apr_psprintf(pool, "Error checking kind of " |
---|
102 | "path '%s' in repository", |
---|
103 | path), |
---|
104 | pool); |
---|
105 | } |
---|
106 | |
---|
107 | *kind = my_kind; |
---|
108 | return NULL; |
---|
109 | } |
---|
110 | |
---|
111 | |
---|
112 | static int |
---|
113 | parse_version_uri(dav_resource_combined *comb, |
---|
114 | const char *path, |
---|
115 | const char *label, |
---|
116 | int use_checked_in) |
---|
117 | { |
---|
118 | const char *slash; |
---|
119 | const char *created_rev_str; |
---|
120 | |
---|
121 | /* format: CREATED_REV/REPOS_PATH */ |
---|
122 | |
---|
123 | /* ### what to do with LABEL and USE_CHECKED_IN ?? */ |
---|
124 | |
---|
125 | comb->res.type = DAV_RESOURCE_TYPE_VERSION; |
---|
126 | comb->res.versioned = TRUE; |
---|
127 | |
---|
128 | slash = ap_strchr_c(path, '/'); |
---|
129 | if (slash == NULL) |
---|
130 | { |
---|
131 | /* http://host.name/repos/$svn/ver/0 |
---|
132 | |
---|
133 | This URL form refers to the root path of the repository. |
---|
134 | */ |
---|
135 | created_rev_str = apr_pstrndup(comb->res.pool, path, strlen(path)); |
---|
136 | comb->priv.root.rev = SVN_STR_TO_REV(created_rev_str); |
---|
137 | comb->priv.repos_path = "/"; |
---|
138 | } |
---|
139 | else if (slash == path) |
---|
140 | { |
---|
141 | /* the CREATED_REV was missing(?) |
---|
142 | |
---|
143 | ### not sure this can happen, though, because it would imply two |
---|
144 | ### slashes, yet those are cleaned out within get_resource |
---|
145 | */ |
---|
146 | return TRUE; |
---|
147 | } |
---|
148 | else |
---|
149 | { |
---|
150 | apr_size_t len = slash - path; |
---|
151 | |
---|
152 | created_rev_str = apr_pstrndup(comb->res.pool, path, len); |
---|
153 | comb->priv.root.rev = SVN_STR_TO_REV(created_rev_str); |
---|
154 | comb->priv.repos_path = slash; |
---|
155 | } |
---|
156 | |
---|
157 | /* if the CREATED_REV parsing blew, then propagate it. */ |
---|
158 | if (comb->priv.root.rev == SVN_INVALID_REVNUM) |
---|
159 | return TRUE; |
---|
160 | |
---|
161 | return FALSE; |
---|
162 | } |
---|
163 | |
---|
164 | |
---|
165 | static int |
---|
166 | parse_history_uri(dav_resource_combined *comb, |
---|
167 | const char *path, |
---|
168 | const char *label, |
---|
169 | int use_checked_in) |
---|
170 | { |
---|
171 | /* format: ??? */ |
---|
172 | |
---|
173 | /* ### what to do with LABEL and USE_CHECKED_IN ?? */ |
---|
174 | |
---|
175 | comb->res.type = DAV_RESOURCE_TYPE_HISTORY; |
---|
176 | |
---|
177 | /* ### parse path */ |
---|
178 | comb->priv.repos_path = path; |
---|
179 | |
---|
180 | return FALSE; |
---|
181 | } |
---|
182 | |
---|
183 | |
---|
184 | static int |
---|
185 | parse_working_uri(dav_resource_combined *comb, |
---|
186 | const char *path, |
---|
187 | const char *label, |
---|
188 | int use_checked_in) |
---|
189 | { |
---|
190 | const char *slash; |
---|
191 | |
---|
192 | /* format: ACTIVITY_ID/REPOS_PATH */ |
---|
193 | |
---|
194 | /* ### what to do with LABEL and USE_CHECKED_IN ?? */ |
---|
195 | |
---|
196 | comb->res.type = DAV_RESOURCE_TYPE_WORKING; |
---|
197 | comb->res.working = TRUE; |
---|
198 | comb->res.versioned = TRUE; |
---|
199 | |
---|
200 | slash = ap_strchr_c(path, '/'); |
---|
201 | |
---|
202 | /* This sucker starts with a slash. That's bogus. */ |
---|
203 | if (slash == path) |
---|
204 | return TRUE; |
---|
205 | |
---|
206 | if (slash == NULL) |
---|
207 | { |
---|
208 | /* There's no slash character in our path. Assume it's just an |
---|
209 | ACTIVITY_ID pointing to the root path. That should be cool. |
---|
210 | We'll just drop through to the normal case handling below. */ |
---|
211 | comb->priv.root.activity_id = apr_pstrdup(comb->res.pool, path); |
---|
212 | comb->priv.repos_path = "/"; |
---|
213 | } |
---|
214 | else |
---|
215 | { |
---|
216 | comb->priv.root.activity_id = apr_pstrndup(comb->res.pool, path, |
---|
217 | slash - path); |
---|
218 | comb->priv.repos_path = slash; |
---|
219 | } |
---|
220 | |
---|
221 | return FALSE; |
---|
222 | } |
---|
223 | |
---|
224 | |
---|
225 | static int |
---|
226 | parse_activity_uri(dav_resource_combined *comb, |
---|
227 | const char *path, |
---|
228 | const char *label, |
---|
229 | int use_checked_in) |
---|
230 | { |
---|
231 | /* format: ACTIVITY_ID */ |
---|
232 | |
---|
233 | /* ### what to do with LABEL and USE_CHECKED_IN ?? */ |
---|
234 | |
---|
235 | comb->res.type = DAV_RESOURCE_TYPE_ACTIVITY; |
---|
236 | |
---|
237 | comb->priv.root.activity_id = path; |
---|
238 | |
---|
239 | return FALSE; |
---|
240 | } |
---|
241 | |
---|
242 | |
---|
243 | static int |
---|
244 | parse_vcc_uri(dav_resource_combined *comb, |
---|
245 | const char *path, |
---|
246 | const char *label, |
---|
247 | int use_checked_in) |
---|
248 | { |
---|
249 | /* format: "default" (a singleton) */ |
---|
250 | |
---|
251 | if (strcmp(path, DAV_SVN__DEFAULT_VCC_NAME) != 0) |
---|
252 | return TRUE; |
---|
253 | |
---|
254 | if (label == NULL && !use_checked_in) |
---|
255 | { |
---|
256 | /* Version Controlled Configuration (baseline selector) */ |
---|
257 | |
---|
258 | /* ### mod_dav has a proper model for these. technically, they are |
---|
259 | ### version-controlled resources (REGULAR), but that just monkeys |
---|
260 | ### up a lot of stuff for us. use a PRIVATE for now. */ |
---|
261 | |
---|
262 | comb->res.type = DAV_RESOURCE_TYPE_PRIVATE; /* _REGULAR */ |
---|
263 | comb->priv.restype = DAV_SVN_RESTYPE_VCC; |
---|
264 | |
---|
265 | comb->res.exists = TRUE; |
---|
266 | comb->res.versioned = TRUE; |
---|
267 | comb->res.baselined = TRUE; |
---|
268 | |
---|
269 | /* NOTE: comb->priv.repos_path == NULL */ |
---|
270 | } |
---|
271 | else |
---|
272 | { |
---|
273 | /* a specific Version Resource; in this case, a Baseline */ |
---|
274 | |
---|
275 | int revnum; |
---|
276 | |
---|
277 | if (label != NULL) |
---|
278 | { |
---|
279 | revnum = SVN_STR_TO_REV(label); /* assume slash terminates */ |
---|
280 | if (!SVN_IS_VALID_REVNUM(revnum)) |
---|
281 | return TRUE; /* ### be nice to get better feedback */ |
---|
282 | } |
---|
283 | else /* use_checked_in */ |
---|
284 | { |
---|
285 | /* use the DAV:checked-in value of the VCC. this is always the |
---|
286 | "latest" (or "youngest") revision. */ |
---|
287 | |
---|
288 | /* signal prep_version to look it up */ |
---|
289 | revnum = SVN_INVALID_REVNUM; |
---|
290 | } |
---|
291 | |
---|
292 | comb->res.type = DAV_RESOURCE_TYPE_VERSION; |
---|
293 | |
---|
294 | /* exists? need to wait for now */ |
---|
295 | comb->res.versioned = TRUE; |
---|
296 | comb->res.baselined = TRUE; |
---|
297 | |
---|
298 | /* which baseline (revision tree) to access */ |
---|
299 | comb->priv.root.rev = revnum; |
---|
300 | |
---|
301 | /* NOTE: comb->priv.repos_path == NULL */ |
---|
302 | /* NOTE: comb->priv.created_rev == SVN_INVALID_REVNUM */ |
---|
303 | } |
---|
304 | |
---|
305 | return FALSE; |
---|
306 | } |
---|
307 | |
---|
308 | |
---|
309 | static int |
---|
310 | parse_baseline_coll_uri(dav_resource_combined *comb, |
---|
311 | const char *path, |
---|
312 | const char *label, |
---|
313 | int use_checked_in) |
---|
314 | { |
---|
315 | const char *slash; |
---|
316 | int revnum; |
---|
317 | |
---|
318 | /* format: REVISION/REPOS_PATH */ |
---|
319 | |
---|
320 | /* ### what to do with LABEL and USE_CHECKED_IN ?? */ |
---|
321 | |
---|
322 | slash = ap_strchr_c(path, '/'); |
---|
323 | if (slash == NULL) |
---|
324 | slash = "/"; /* they are referring to the root of the BC */ |
---|
325 | else if (slash == path) |
---|
326 | return TRUE; /* the REVISION was missing(?) |
---|
327 | ### not sure this can happen, though, because |
---|
328 | ### it would imply two slashes, yet those are |
---|
329 | ### cleaned out within get_resource */ |
---|
330 | |
---|
331 | revnum = SVN_STR_TO_REV(path); /* assume slash terminates conversion */ |
---|
332 | if (!SVN_IS_VALID_REVNUM(revnum)) |
---|
333 | return TRUE; /* ### be nice to get better feedback */ |
---|
334 | |
---|
335 | /* ### mod_dav doesn't have a proper model for these. they are standard |
---|
336 | ### VCRs, but we need some additional semantics attached to them. |
---|
337 | ### need to figure out a way to label them as special. */ |
---|
338 | |
---|
339 | comb->res.type = DAV_RESOURCE_TYPE_REGULAR; |
---|
340 | comb->res.versioned = TRUE; |
---|
341 | comb->priv.root.rev = revnum; |
---|
342 | comb->priv.repos_path = slash; |
---|
343 | |
---|
344 | return FALSE; |
---|
345 | } |
---|
346 | |
---|
347 | |
---|
348 | static int |
---|
349 | parse_baseline_uri(dav_resource_combined *comb, |
---|
350 | const char *path, |
---|
351 | const char *label, |
---|
352 | int use_checked_in) |
---|
353 | { |
---|
354 | int revnum; |
---|
355 | |
---|
356 | /* format: REVISION */ |
---|
357 | |
---|
358 | /* ### what to do with LABEL and USE_CHECKED_IN ?? */ |
---|
359 | |
---|
360 | revnum = SVN_STR_TO_REV(path); |
---|
361 | if (!SVN_IS_VALID_REVNUM(revnum)) |
---|
362 | return TRUE; /* ### be nice to get better feedback */ |
---|
363 | |
---|
364 | /* create a Baseline resource (a special Version Resource) */ |
---|
365 | |
---|
366 | comb->res.type = DAV_RESOURCE_TYPE_VERSION; |
---|
367 | |
---|
368 | /* exists? need to wait for now */ |
---|
369 | comb->res.versioned = TRUE; |
---|
370 | comb->res.baselined = TRUE; |
---|
371 | |
---|
372 | /* which baseline (revision tree) to access */ |
---|
373 | comb->priv.root.rev = revnum; |
---|
374 | |
---|
375 | /* NOTE: comb->priv.repos_path == NULL */ |
---|
376 | /* NOTE: comb->priv.created_rev == SVN_INVALID_REVNUM */ |
---|
377 | |
---|
378 | return FALSE; |
---|
379 | } |
---|
380 | |
---|
381 | |
---|
382 | static int |
---|
383 | parse_wrk_baseline_uri(dav_resource_combined *comb, |
---|
384 | const char *path, |
---|
385 | const char *label, |
---|
386 | int use_checked_in) |
---|
387 | { |
---|
388 | const char *slash; |
---|
389 | |
---|
390 | /* format: ACTIVITY_ID/REVISION */ |
---|
391 | |
---|
392 | /* ### what to do with LABEL and USE_CHECKED_IN ?? */ |
---|
393 | |
---|
394 | comb->res.type = DAV_RESOURCE_TYPE_WORKING; |
---|
395 | comb->res.working = TRUE; |
---|
396 | comb->res.versioned = TRUE; |
---|
397 | comb->res.baselined = TRUE; |
---|
398 | |
---|
399 | if ((slash = ap_strchr_c(path, '/')) == NULL |
---|
400 | || slash == path |
---|
401 | || slash[1] == '\0') |
---|
402 | return TRUE; |
---|
403 | |
---|
404 | comb->priv.root.activity_id = apr_pstrndup(comb->res.pool, path, |
---|
405 | slash - path); |
---|
406 | comb->priv.root.rev = SVN_STR_TO_REV(slash + 1); |
---|
407 | |
---|
408 | /* NOTE: comb->priv.repos_path == NULL */ |
---|
409 | |
---|
410 | return FALSE; |
---|
411 | } |
---|
412 | |
---|
413 | |
---|
414 | static const struct special_defn |
---|
415 | { |
---|
416 | const char *name; |
---|
417 | |
---|
418 | /* |
---|
419 | * COMB is the resource that we are constructing. Any elements that |
---|
420 | * can be determined from the PATH may be set in COMB. However, further |
---|
421 | * operations are not allowed (we don't want anything besides a parse |
---|
422 | * error to occur). |
---|
423 | * |
---|
424 | * At a minimum, the parse function must set COMB->res.type and |
---|
425 | * COMB->priv.repos_path. |
---|
426 | * |
---|
427 | * PATH does not contain a leading slash. Given "/root/$svn/xxx/the/path" |
---|
428 | * as the request URI, the PATH variable will be "the/path" |
---|
429 | */ |
---|
430 | int (*parse)(dav_resource_combined *comb, const char *path, |
---|
431 | const char *label, int use_checked_in); |
---|
432 | |
---|
433 | /* The number of subcompenents after the !svn/xxx/... before we |
---|
434 | reach the actual path within the repository. */ |
---|
435 | int numcomponents; |
---|
436 | |
---|
437 | /* Boolean: are the subcomponents followed by a repos path? */ |
---|
438 | int has_repos_path; |
---|
439 | |
---|
440 | /* The private resource type for the /$svn/xxx/ collection. */ |
---|
441 | enum dav_svn_private_restype restype; |
---|
442 | |
---|
443 | } special_subdirs[] = |
---|
444 | { |
---|
445 | { "ver", parse_version_uri, 1, TRUE, DAV_SVN_RESTYPE_VER_COLLECTION }, |
---|
446 | { "his", parse_history_uri, 0, FALSE, DAV_SVN_RESTYPE_HIS_COLLECTION }, |
---|
447 | { "wrk", parse_working_uri, 1, TRUE, DAV_SVN_RESTYPE_WRK_COLLECTION }, |
---|
448 | { "act", parse_activity_uri, 1, FALSE, DAV_SVN_RESTYPE_ACT_COLLECTION }, |
---|
449 | { "vcc", parse_vcc_uri, 1, FALSE, DAV_SVN_RESTYPE_VCC_COLLECTION }, |
---|
450 | { "bc", parse_baseline_coll_uri, 1, TRUE, DAV_SVN_RESTYPE_BC_COLLECTION }, |
---|
451 | { "bln", parse_baseline_uri, 1, FALSE, DAV_SVN_RESTYPE_BLN_COLLECTION }, |
---|
452 | { "wbl", parse_wrk_baseline_uri, 2, FALSE, DAV_SVN_RESTYPE_WBL_COLLECTION }, |
---|
453 | { NULL } /* sentinel */ |
---|
454 | }; |
---|
455 | |
---|
456 | |
---|
457 | /* |
---|
458 | * parse_uri: parse the provided URI into its various bits |
---|
459 | * |
---|
460 | * URI will contain a path relative to our configured root URI. It should |
---|
461 | * not have a leading "/". The root is identified by "". |
---|
462 | * |
---|
463 | * On output: *COMB will contain all of the information parsed out of |
---|
464 | * the URI -- the resource type, activity ID, path, etc. |
---|
465 | * |
---|
466 | * Note: this function will only parse the URI. Validation of the pieces, |
---|
467 | * opening data stores, etc, are not part of this function. |
---|
468 | * |
---|
469 | * TRUE is returned if a parsing error occurred. FALSE for success. |
---|
470 | */ |
---|
471 | static int |
---|
472 | parse_uri(dav_resource_combined *comb, |
---|
473 | const char *uri, |
---|
474 | const char *label, |
---|
475 | int use_checked_in) |
---|
476 | { |
---|
477 | const char *special_uri = comb->priv.repos->special_uri; |
---|
478 | apr_size_t len1; |
---|
479 | apr_size_t len2; |
---|
480 | char ch; |
---|
481 | |
---|
482 | len1 = strlen(uri); |
---|
483 | len2 = strlen(special_uri); |
---|
484 | if (len1 > len2 |
---|
485 | && ((ch = uri[len2]) == '/' || ch == '\0') |
---|
486 | && memcmp(uri, special_uri, len2) == 0) |
---|
487 | { |
---|
488 | if (ch == '\0') |
---|
489 | { |
---|
490 | /* URI was "/root/!svn". It exists, but has restricted usage. */ |
---|
491 | comb->res.type = DAV_RESOURCE_TYPE_PRIVATE; |
---|
492 | comb->priv.restype = DAV_SVN_RESTYPE_ROOT_COLLECTION; |
---|
493 | } |
---|
494 | else |
---|
495 | { |
---|
496 | const struct special_defn *defn; |
---|
497 | |
---|
498 | /* skip past the "!svn/" prefix */ |
---|
499 | uri += len2 + 1; |
---|
500 | len1 -= len2 + 1; |
---|
501 | |
---|
502 | for (defn = special_subdirs ; defn->name != NULL; ++defn) |
---|
503 | { |
---|
504 | apr_size_t len3 = strlen(defn->name); |
---|
505 | |
---|
506 | if (len1 >= len3 && memcmp(uri, defn->name, len3) == 0) |
---|
507 | { |
---|
508 | if (uri[len3] == '\0') |
---|
509 | { |
---|
510 | /* URI was "/root/!svn/XXX". The location exists, but |
---|
511 | has restricted usage. */ |
---|
512 | comb->res.type = DAV_RESOURCE_TYPE_PRIVATE; |
---|
513 | |
---|
514 | /* store the resource type so that we can PROPFIND |
---|
515 | on this collection. */ |
---|
516 | comb->priv.restype = defn->restype; |
---|
517 | } |
---|
518 | else if (uri[len3] == '/') |
---|
519 | { |
---|
520 | if ((*defn->parse)(comb, uri + len3 + 1, label, |
---|
521 | use_checked_in)) |
---|
522 | return TRUE; |
---|
523 | } |
---|
524 | else |
---|
525 | { |
---|
526 | /* e.g. "/root/!svn/activity" (we just know "act") */ |
---|
527 | return TRUE; |
---|
528 | } |
---|
529 | |
---|
530 | break; |
---|
531 | } |
---|
532 | } |
---|
533 | |
---|
534 | /* if completed the loop, then it is an unrecognized subdir */ |
---|
535 | if (defn->name == NULL) |
---|
536 | return TRUE; |
---|
537 | } |
---|
538 | } |
---|
539 | else |
---|
540 | { |
---|
541 | /* Anything under the root, but not under "!svn". These are all |
---|
542 | version-controlled resources. */ |
---|
543 | comb->res.type = DAV_RESOURCE_TYPE_REGULAR; |
---|
544 | comb->res.versioned = TRUE; |
---|
545 | |
---|
546 | /* The location of these resources corresponds directly to the URI, |
---|
547 | and we keep the leading "/". */ |
---|
548 | comb->priv.repos_path = comb->priv.uri_path->data; |
---|
549 | } |
---|
550 | |
---|
551 | return FALSE; |
---|
552 | } |
---|
553 | |
---|
554 | |
---|
555 | static dav_error * |
---|
556 | prep_regular(dav_resource_combined *comb) |
---|
557 | { |
---|
558 | apr_pool_t *pool = comb->res.pool; |
---|
559 | dav_svn_repos *repos = comb->priv.repos; |
---|
560 | svn_error_t *serr; |
---|
561 | dav_error *derr; |
---|
562 | svn_node_kind_t kind; |
---|
563 | |
---|
564 | /* A REGULAR resource might have a specific revision already (e.g. if it |
---|
565 | is part of a baseline collection). However, if it doesn't, then we |
---|
566 | will assume that we need the youngest revision. |
---|
567 | ### other cases besides a BC? */ |
---|
568 | if (comb->priv.root.rev == SVN_INVALID_REVNUM) |
---|
569 | { |
---|
570 | serr = svn_fs_youngest_rev(&comb->priv.root.rev, repos->fs, pool); |
---|
571 | if (serr != NULL) |
---|
572 | { |
---|
573 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
574 | "Could not determine the proper " |
---|
575 | "revision to access", |
---|
576 | pool); |
---|
577 | } |
---|
578 | } |
---|
579 | |
---|
580 | /* get the root of the tree */ |
---|
581 | serr = svn_fs_revision_root(&comb->priv.root.root, repos->fs, |
---|
582 | comb->priv.root.rev, pool); |
---|
583 | if (serr != NULL) |
---|
584 | { |
---|
585 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
586 | "Could not open the root of the " |
---|
587 | "repository", |
---|
588 | pool); |
---|
589 | } |
---|
590 | |
---|
591 | derr = fs_check_path(&kind, comb->priv.root.root, |
---|
592 | comb->priv.repos_path, pool); |
---|
593 | if (derr != NULL) |
---|
594 | return derr; |
---|
595 | |
---|
596 | comb->res.exists = (kind != svn_node_none); |
---|
597 | comb->res.collection = (kind == svn_node_dir); |
---|
598 | |
---|
599 | /* HACK: dav_get_resource_state() is making shortcut assumptions |
---|
600 | about how to distinguish a null resource from a lock-null |
---|
601 | resource. This is the only way to get around that problem. |
---|
602 | Without it, it won't ever detect lock-nulls, and thus 'svn unlock |
---|
603 | nonexistentURL' will always return 404's. */ |
---|
604 | if (! comb->res.exists) |
---|
605 | comb->priv.r->path_info = (char *) ""; |
---|
606 | |
---|
607 | return NULL; |
---|
608 | } |
---|
609 | |
---|
610 | |
---|
611 | static dav_error * |
---|
612 | prep_version(dav_resource_combined *comb) |
---|
613 | { |
---|
614 | svn_error_t *serr; |
---|
615 | apr_pool_t *pool = comb->res.pool; |
---|
616 | |
---|
617 | /* we are accessing the Version Resource by REV/PATH */ |
---|
618 | |
---|
619 | /* ### assert: .baselined = TRUE */ |
---|
620 | |
---|
621 | /* if we don't have a revision, then assume the youngest */ |
---|
622 | if (!SVN_IS_VALID_REVNUM(comb->priv.root.rev)) |
---|
623 | { |
---|
624 | serr = svn_fs_youngest_rev(&comb->priv.root.rev, |
---|
625 | comb->priv.repos->fs, |
---|
626 | pool); |
---|
627 | if (serr != NULL) |
---|
628 | { |
---|
629 | /* ### might not be a baseline */ |
---|
630 | |
---|
631 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
632 | "Could not fetch 'youngest' revision " |
---|
633 | "to enable accessing the latest " |
---|
634 | "baseline resource.", |
---|
635 | pool); |
---|
636 | } |
---|
637 | } |
---|
638 | |
---|
639 | /* ### baselines have no repos_path, and we don't need to open |
---|
640 | ### a root (yet). we just needed to ensure that we have the proper |
---|
641 | ### revision number. */ |
---|
642 | |
---|
643 | if (!comb->priv.root.root) |
---|
644 | { |
---|
645 | serr = svn_fs_revision_root(&comb->priv.root.root, |
---|
646 | comb->priv.repos->fs, |
---|
647 | comb->priv.root.rev, |
---|
648 | pool); |
---|
649 | if (serr != NULL) |
---|
650 | { |
---|
651 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
652 | "Could not open a revision root.", |
---|
653 | pool); |
---|
654 | } |
---|
655 | } |
---|
656 | |
---|
657 | /* ### we should probably check that the revision is valid */ |
---|
658 | comb->res.exists = TRUE; |
---|
659 | |
---|
660 | /* Set up the proper URI. Most likely, we arrived here via a VCC, |
---|
661 | so the URI will be incorrect. Set the canonical form. */ |
---|
662 | /* ### assuming a baseline */ |
---|
663 | comb->res.uri = dav_svn__build_uri(comb->priv.repos, |
---|
664 | DAV_SVN__BUILD_URI_BASELINE, |
---|
665 | comb->priv.root.rev, NULL, |
---|
666 | 0 /* add_href */, |
---|
667 | pool); |
---|
668 | |
---|
669 | return NULL; |
---|
670 | } |
---|
671 | |
---|
672 | |
---|
673 | static dav_error * |
---|
674 | prep_history(dav_resource_combined *comb) |
---|
675 | { |
---|
676 | return NULL; |
---|
677 | } |
---|
678 | |
---|
679 | |
---|
680 | static dav_error * |
---|
681 | prep_working(dav_resource_combined *comb) |
---|
682 | { |
---|
683 | const char *txn_name = dav_svn__get_txn(comb->priv.repos, |
---|
684 | comb->priv.root.activity_id); |
---|
685 | apr_pool_t *pool = comb->res.pool; |
---|
686 | svn_error_t *serr; |
---|
687 | dav_error *derr; |
---|
688 | svn_node_kind_t kind; |
---|
689 | |
---|
690 | if (txn_name == NULL) |
---|
691 | { |
---|
692 | /* ### HTTP_BAD_REQUEST is probably wrong */ |
---|
693 | return dav_new_error(pool, HTTP_BAD_REQUEST, 0, |
---|
694 | "An unknown activity was specified in the URL. " |
---|
695 | "This is generally caused by a problem in the " |
---|
696 | "client software."); |
---|
697 | } |
---|
698 | comb->priv.root.txn_name = txn_name; |
---|
699 | |
---|
700 | /* get the FS transaction, given its name */ |
---|
701 | serr = svn_fs_open_txn(&comb->priv.root.txn, comb->priv.repos->fs, txn_name, |
---|
702 | pool); |
---|
703 | if (serr != NULL) |
---|
704 | { |
---|
705 | if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION) |
---|
706 | { |
---|
707 | svn_error_clear(serr); |
---|
708 | return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0, |
---|
709 | "An activity was specified and found, but the " |
---|
710 | "corresponding SVN FS transaction was not " |
---|
711 | "found."); |
---|
712 | } |
---|
713 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
714 | "Could not open the SVN FS transaction " |
---|
715 | "corresponding to the specified activity.", |
---|
716 | pool); |
---|
717 | } |
---|
718 | |
---|
719 | if (comb->res.baselined) |
---|
720 | { |
---|
721 | /* a Working Baseline */ |
---|
722 | |
---|
723 | /* if the transaction exists, then the working resource exists */ |
---|
724 | comb->res.exists = TRUE; |
---|
725 | |
---|
726 | return NULL; |
---|
727 | } |
---|
728 | |
---|
729 | /* Set the txn author if not previously set. Protect against multi-author |
---|
730 | * commits by verifying authenticated user associated with the current |
---|
731 | * request is the same as the txn author. |
---|
732 | * Note that anonymous requests are being excluded as being a change |
---|
733 | * in author, because the commit may touch areas of the repository |
---|
734 | * that are anonymous writeable as well as areas that are not. |
---|
735 | */ |
---|
736 | if (comb->priv.repos->username) |
---|
737 | { |
---|
738 | svn_string_t *current_author; |
---|
739 | svn_string_t request_author; |
---|
740 | |
---|
741 | serr = svn_fs_txn_prop(¤t_author, comb->priv.root.txn, |
---|
742 | SVN_PROP_REVISION_AUTHOR, pool); |
---|
743 | if (serr != NULL) |
---|
744 | { |
---|
745 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
746 | "Failed to retrieve author of the SVN FS transaction " |
---|
747 | "corresponding to the specified activity.", |
---|
748 | pool); |
---|
749 | } |
---|
750 | |
---|
751 | request_author.data = comb->priv.repos->username; |
---|
752 | request_author.len = strlen(request_author.data); |
---|
753 | if (!current_author) |
---|
754 | { |
---|
755 | serr = svn_fs_change_txn_prop(comb->priv.root.txn, |
---|
756 | SVN_PROP_REVISION_AUTHOR, &request_author, pool); |
---|
757 | if (serr != NULL) |
---|
758 | { |
---|
759 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
760 | "Failed to set the author of the SVN FS transaction " |
---|
761 | "corresponding to the specified activity.", |
---|
762 | pool); |
---|
763 | } |
---|
764 | } |
---|
765 | else if (!svn_string_compare(current_author, &request_author)) |
---|
766 | { |
---|
767 | return dav_new_error(pool, HTTP_NOT_IMPLEMENTED, 0, |
---|
768 | "Multi-author commits not supported."); |
---|
769 | } |
---|
770 | } |
---|
771 | |
---|
772 | /* get the root of the tree */ |
---|
773 | serr = svn_fs_txn_root(&comb->priv.root.root, comb->priv.root.txn, pool); |
---|
774 | if (serr != NULL) |
---|
775 | { |
---|
776 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
777 | "Could not open the (transaction) root of " |
---|
778 | "the repository", |
---|
779 | pool); |
---|
780 | } |
---|
781 | |
---|
782 | derr = fs_check_path(&kind, comb->priv.root.root, |
---|
783 | comb->priv.repos_path, pool); |
---|
784 | if (derr != NULL) |
---|
785 | return derr; |
---|
786 | |
---|
787 | comb->res.exists = (kind != svn_node_none); |
---|
788 | comb->res.collection = (kind == svn_node_dir); |
---|
789 | |
---|
790 | return NULL; |
---|
791 | } |
---|
792 | |
---|
793 | |
---|
794 | static dav_error * |
---|
795 | prep_activity(dav_resource_combined *comb) |
---|
796 | { |
---|
797 | const char *txn_name = dav_svn__get_txn(comb->priv.repos, |
---|
798 | comb->priv.root.activity_id); |
---|
799 | |
---|
800 | comb->priv.root.txn_name = txn_name; |
---|
801 | comb->res.exists = txn_name != NULL; |
---|
802 | |
---|
803 | return NULL; |
---|
804 | } |
---|
805 | |
---|
806 | |
---|
807 | static dav_error * |
---|
808 | prep_private(dav_resource_combined *comb) |
---|
809 | { |
---|
810 | if (comb->priv.restype == DAV_SVN_RESTYPE_VCC) |
---|
811 | { |
---|
812 | /* ### what to do */ |
---|
813 | } |
---|
814 | /* else nothing to do (### for now) */ |
---|
815 | |
---|
816 | return NULL; |
---|
817 | } |
---|
818 | |
---|
819 | |
---|
820 | static const struct res_type_handler |
---|
821 | { |
---|
822 | dav_resource_type type; |
---|
823 | dav_error * (*prep)(dav_resource_combined *comb); |
---|
824 | |
---|
825 | } res_type_handlers[] = |
---|
826 | { |
---|
827 | /* skip UNKNOWN */ |
---|
828 | { DAV_RESOURCE_TYPE_REGULAR, prep_regular }, |
---|
829 | { DAV_RESOURCE_TYPE_VERSION, prep_version }, |
---|
830 | { DAV_RESOURCE_TYPE_HISTORY, prep_history }, |
---|
831 | { DAV_RESOURCE_TYPE_WORKING, prep_working }, |
---|
832 | /* skip WORKSPACE */ |
---|
833 | { DAV_RESOURCE_TYPE_ACTIVITY, prep_activity }, |
---|
834 | { DAV_RESOURCE_TYPE_PRIVATE, prep_private }, |
---|
835 | |
---|
836 | { 0, NULL } /* sentinel */ |
---|
837 | }; |
---|
838 | |
---|
839 | |
---|
840 | /* |
---|
841 | ** ### docco... |
---|
842 | ** |
---|
843 | ** Set .exists and .collection |
---|
844 | ** open other, internal bits... |
---|
845 | */ |
---|
846 | static dav_error * |
---|
847 | prep_resource(dav_resource_combined *comb) |
---|
848 | { |
---|
849 | const struct res_type_handler *scan; |
---|
850 | |
---|
851 | for (scan = res_type_handlers; scan->prep != NULL; ++scan) |
---|
852 | { |
---|
853 | if (comb->res.type == scan->type) |
---|
854 | return (*scan->prep)(comb); |
---|
855 | } |
---|
856 | |
---|
857 | return dav_new_error(comb->res.pool, HTTP_INTERNAL_SERVER_ERROR, 0, |
---|
858 | "DESIGN FAILURE: unknown resource type"); |
---|
859 | } |
---|
860 | |
---|
861 | |
---|
862 | static dav_resource * |
---|
863 | create_private_resource(const dav_resource *base, |
---|
864 | enum dav_svn_private_restype restype) |
---|
865 | { |
---|
866 | dav_resource_combined *comb; |
---|
867 | svn_stringbuf_t *path; |
---|
868 | const struct special_defn *defn; |
---|
869 | |
---|
870 | for (defn = special_subdirs; defn->name != NULL; ++defn) |
---|
871 | if (defn->restype == restype) |
---|
872 | break; |
---|
873 | /* assert: defn->name != NULL */ |
---|
874 | |
---|
875 | path = svn_stringbuf_createf(base->pool, "/%s/%s", |
---|
876 | base->info->repos->special_uri, defn->name); |
---|
877 | |
---|
878 | comb = apr_pcalloc(base->pool, sizeof(*comb)); |
---|
879 | |
---|
880 | /* ### can/should we leverage prep_resource */ |
---|
881 | |
---|
882 | comb->res.type = DAV_RESOURCE_TYPE_PRIVATE; |
---|
883 | |
---|
884 | comb->res.exists = TRUE; |
---|
885 | comb->res.collection = TRUE; /* ### always true? */ |
---|
886 | /* versioned = baselined = working = FALSE */ |
---|
887 | |
---|
888 | comb->res.uri = apr_pstrcat(base->pool, base->info->repos->root_path, |
---|
889 | path->data, NULL); |
---|
890 | comb->res.info = &comb->priv; |
---|
891 | comb->res.hooks = &dav_svn__hooks_repository; |
---|
892 | comb->res.pool = base->pool; |
---|
893 | |
---|
894 | comb->priv.uri_path = path; |
---|
895 | comb->priv.repos = base->info->repos; |
---|
896 | comb->priv.root.rev = SVN_INVALID_REVNUM; |
---|
897 | return &comb->res; |
---|
898 | } |
---|
899 | |
---|
900 | |
---|
901 | static void log_warning(void *baton, svn_error_t *err) |
---|
902 | { |
---|
903 | request_rec *r = baton; |
---|
904 | |
---|
905 | /* ### hmm. the FS is cleaned up at request cleanup time. "r" might |
---|
906 | ### not really be valid. we should probably put the FS into a |
---|
907 | ### subpool to ensure it gets cleaned before the request. |
---|
908 | |
---|
909 | ### is there a good way to create and use a subpool for all |
---|
910 | ### of our functions ... ?? |
---|
911 | */ |
---|
912 | |
---|
913 | ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, "%s", err->message); |
---|
914 | } |
---|
915 | |
---|
916 | |
---|
917 | AP_MODULE_DECLARE(dav_error *) |
---|
918 | dav_svn_split_uri(request_rec *r, |
---|
919 | const char *uri_to_split, |
---|
920 | const char *root_path, |
---|
921 | const char **cleaned_uri, |
---|
922 | int *trailing_slash, |
---|
923 | const char **repos_name, |
---|
924 | const char **relative_path, |
---|
925 | const char **repos_path) |
---|
926 | { |
---|
927 | apr_size_t len1; |
---|
928 | int had_slash; |
---|
929 | const char *fs_path; |
---|
930 | const char *fs_parent_path; |
---|
931 | const char *relative; |
---|
932 | char *uri; |
---|
933 | |
---|
934 | /* one of these is NULL, the other non-NULL. */ |
---|
935 | fs_path = dav_svn__get_fs_path(r); |
---|
936 | fs_parent_path = dav_svn__get_fs_parent_path(r); |
---|
937 | |
---|
938 | if ((fs_path == NULL) && (fs_parent_path == NULL)) |
---|
939 | { |
---|
940 | /* ### are SVN_ERR_APMOD codes within the right numeric space? */ |
---|
941 | return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, |
---|
942 | SVN_ERR_APMOD_MISSING_PATH_TO_FS, |
---|
943 | "The server is misconfigured: " |
---|
944 | "either an SVNPath or SVNParentPath " |
---|
945 | "directive is required to specify the location " |
---|
946 | "of this resource's repository."); |
---|
947 | } |
---|
948 | |
---|
949 | /* make a copy so that we can do some work on it */ |
---|
950 | uri = apr_pstrdup(r->pool, uri_to_split); |
---|
951 | |
---|
952 | /* remove duplicate slashes, and make sure URI has no trailing '/' */ |
---|
953 | ap_no2slash(uri); |
---|
954 | len1 = strlen(uri); |
---|
955 | had_slash = (len1 > 0 && uri[len1 - 1] == '/'); |
---|
956 | if (len1 > 1 && had_slash) |
---|
957 | uri[len1 - 1] = '\0'; |
---|
958 | |
---|
959 | if (had_slash) |
---|
960 | *trailing_slash = TRUE; |
---|
961 | else |
---|
962 | *trailing_slash = FALSE; |
---|
963 | |
---|
964 | /* return the first item. */ |
---|
965 | *cleaned_uri = apr_pstrdup(r->pool, uri); |
---|
966 | |
---|
967 | /* The URL space defined by the SVN provider is always a virtual |
---|
968 | space. Construct the path relative to the configured Location |
---|
969 | (root_path). So... the relative location is simply the URL used, |
---|
970 | skipping the root_path. |
---|
971 | |
---|
972 | Note: mod_dav has canonialized root_path. It will not have a trailing |
---|
973 | slash (unless it is "/"). |
---|
974 | |
---|
975 | Note: given a URI of /something and a root of /some, then it is |
---|
976 | impossible to be here (and end up with "thing"). This is simply |
---|
977 | because we control /some and are dispatched to here for its |
---|
978 | URIs. We do not control /something, so we don't get here. Or, |
---|
979 | if we *do* control /something, then it is for THAT root. |
---|
980 | */ |
---|
981 | relative = ap_stripprefix(uri, root_path); |
---|
982 | |
---|
983 | /* We want a leading slash on the path specified by <relative>. This |
---|
984 | will almost always be the case since root_path does not have a trailing |
---|
985 | slash. However, if the root is "/", then the slash will be removed |
---|
986 | from <relative>. Backing up a character will put the leading slash |
---|
987 | back. |
---|
988 | |
---|
989 | Watch out for the empty string! This can happen when URI == ROOT_PATH. |
---|
990 | We simply turn the path into "/" for this case. */ |
---|
991 | if (*relative == '\0') |
---|
992 | relative = "/"; |
---|
993 | else if (*relative != '/') |
---|
994 | --relative; |
---|
995 | /* ### need a better name... it isn't "relative" because of the leading |
---|
996 | ### slash. something about SVN-private-path */ |
---|
997 | |
---|
998 | /* Depending on whether SVNPath or SVNParentPath was used, we need |
---|
999 | to compute 'relative' and 'repos_name' differently. */ |
---|
1000 | |
---|
1001 | /* Normal case: the SVNPath command was used to specify a |
---|
1002 | particular repository. */ |
---|
1003 | if (fs_path != NULL) |
---|
1004 | { |
---|
1005 | /* the repos_name is the last component of root_path. */ |
---|
1006 | *repos_name = svn_path_basename(root_path, r->pool); |
---|
1007 | |
---|
1008 | /* 'relative' is already correct for SVNPath; the root_path |
---|
1009 | already contains the name of the repository, so relative is |
---|
1010 | everything beyond that. */ |
---|
1011 | } |
---|
1012 | |
---|
1013 | else |
---|
1014 | { |
---|
1015 | /* SVNParentPath was used instead: assume the first component of |
---|
1016 | 'relative' is the name of a repository. */ |
---|
1017 | const char *magic_component, *magic_end; |
---|
1018 | |
---|
1019 | /* A repository name is required here. |
---|
1020 | Remember that 'relative' always starts with a "/". */ |
---|
1021 | if (relative[1] == '\0') |
---|
1022 | { |
---|
1023 | /* ### are SVN_ERR_APMOD codes within the right numeric space? */ |
---|
1024 | return dav_new_error(r->pool, HTTP_FORBIDDEN, |
---|
1025 | SVN_ERR_APMOD_MALFORMED_URI, |
---|
1026 | "The URI does not contain the name " |
---|
1027 | "of a repository."); |
---|
1028 | } |
---|
1029 | |
---|
1030 | magic_end = ap_strchr_c(relative + 1, '/'); |
---|
1031 | if (!magic_end) |
---|
1032 | { |
---|
1033 | /* ### Request was for parent directory with no trailing |
---|
1034 | slash; we probably ought to just redirect to same with |
---|
1035 | trailing slash appended. */ |
---|
1036 | magic_component = relative + 1; |
---|
1037 | relative = "/"; |
---|
1038 | } |
---|
1039 | else |
---|
1040 | { |
---|
1041 | magic_component = apr_pstrndup(r->pool, relative + 1, |
---|
1042 | magic_end - relative - 1); |
---|
1043 | relative = magic_end; |
---|
1044 | } |
---|
1045 | |
---|
1046 | /* return answer */ |
---|
1047 | *repos_name = magic_component; |
---|
1048 | } |
---|
1049 | |
---|
1050 | /* We can return 'relative' at this point too. */ |
---|
1051 | *relative_path = apr_pstrdup(r->pool, relative); |
---|
1052 | |
---|
1053 | /* Code to remove the !svn junk from the front of the relative path, |
---|
1054 | mainly stolen from parse_uri(). This code assumes that |
---|
1055 | the 'relative' string being parsed doesn't start with '/'. */ |
---|
1056 | relative++; |
---|
1057 | |
---|
1058 | { |
---|
1059 | const char *special_uri = dav_svn__get_special_uri(r); |
---|
1060 | apr_size_t len2; |
---|
1061 | char ch; |
---|
1062 | |
---|
1063 | len1 = strlen(relative); |
---|
1064 | len2 = strlen(special_uri); |
---|
1065 | if (len1 > len2 |
---|
1066 | && ((ch = relative[len2]) == '/' || ch == '\0') |
---|
1067 | && memcmp(relative, special_uri, len2) == 0) |
---|
1068 | { |
---|
1069 | if (ch == '\0') |
---|
1070 | { |
---|
1071 | /* relative is just "!svn", which is malformed. */ |
---|
1072 | return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, |
---|
1073 | SVN_ERR_APMOD_MALFORMED_URI, |
---|
1074 | "Nothing follows the svn special_uri."); |
---|
1075 | } |
---|
1076 | else |
---|
1077 | { |
---|
1078 | const struct special_defn *defn; |
---|
1079 | |
---|
1080 | /* skip past the "!svn/" prefix */ |
---|
1081 | relative += len2 + 1; |
---|
1082 | len1 -= len2 + 1; |
---|
1083 | |
---|
1084 | for (defn = special_subdirs ; defn->name != NULL; ++defn) |
---|
1085 | { |
---|
1086 | apr_size_t len3 = strlen(defn->name); |
---|
1087 | |
---|
1088 | if (len1 >= len3 && memcmp(relative, defn->name, len3) == 0) |
---|
1089 | { |
---|
1090 | /* Found a matching special dir. */ |
---|
1091 | |
---|
1092 | if (relative[len3] == '\0') |
---|
1093 | { |
---|
1094 | /* relative is "!svn/xxx" */ |
---|
1095 | if (defn->numcomponents == 0) |
---|
1096 | *repos_path = NULL; |
---|
1097 | else |
---|
1098 | return |
---|
1099 | dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, |
---|
1100 | SVN_ERR_APMOD_MALFORMED_URI, |
---|
1101 | "Missing info after special_uri."); |
---|
1102 | } |
---|
1103 | else if (relative[len3] == '/') |
---|
1104 | { |
---|
1105 | /* Skip past defn->numcomponents components, |
---|
1106 | return everything beyond that.*/ |
---|
1107 | int j; |
---|
1108 | const char *end = NULL, *start = relative + len3 + 1; |
---|
1109 | |
---|
1110 | for (j = 0; j < defn->numcomponents; j++) |
---|
1111 | { |
---|
1112 | end = ap_strchr_c(start, '/'); |
---|
1113 | if (! end) |
---|
1114 | break; |
---|
1115 | start = end + 1; |
---|
1116 | } |
---|
1117 | |
---|
1118 | if (! end) |
---|
1119 | { |
---|
1120 | /* Did we break from the loop prematurely? */ |
---|
1121 | if (j != (defn->numcomponents - 1)) |
---|
1122 | return |
---|
1123 | dav_new_error(r->pool, |
---|
1124 | HTTP_INTERNAL_SERVER_ERROR, |
---|
1125 | SVN_ERR_APMOD_MALFORMED_URI, |
---|
1126 | "Not enough components" |
---|
1127 | " after special_uri."); |
---|
1128 | |
---|
1129 | if (! defn->has_repos_path) |
---|
1130 | /* It's okay to not have found a slash. */ |
---|
1131 | *repos_path = NULL; |
---|
1132 | else |
---|
1133 | *repos_path = "/"; |
---|
1134 | } |
---|
1135 | else |
---|
1136 | { |
---|
1137 | /* Found a slash after the special components. */ |
---|
1138 | *repos_path = apr_pstrdup(r->pool, start); |
---|
1139 | } |
---|
1140 | } |
---|
1141 | else |
---|
1142 | { |
---|
1143 | return |
---|
1144 | dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, |
---|
1145 | SVN_ERR_APMOD_MALFORMED_URI, |
---|
1146 | "Unknown data after special_uri."); |
---|
1147 | } |
---|
1148 | |
---|
1149 | break; |
---|
1150 | } |
---|
1151 | } |
---|
1152 | |
---|
1153 | if (defn->name == NULL) |
---|
1154 | return |
---|
1155 | dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, |
---|
1156 | SVN_ERR_APMOD_MALFORMED_URI, |
---|
1157 | "Couldn't match subdir after special_uri."); |
---|
1158 | } |
---|
1159 | } |
---|
1160 | else |
---|
1161 | { |
---|
1162 | /* There's no "!svn/" at all, so the relative path is already |
---|
1163 | a valid path within the repository. */ |
---|
1164 | *repos_path = apr_pstrdup(r->pool, relative); |
---|
1165 | } |
---|
1166 | } |
---|
1167 | |
---|
1168 | return NULL; |
---|
1169 | } |
---|
1170 | |
---|
1171 | |
---|
1172 | /* Context for cleanup handler. */ |
---|
1173 | struct cleanup_fs_access_baton |
---|
1174 | { |
---|
1175 | svn_fs_t *fs; |
---|
1176 | apr_pool_t *pool; |
---|
1177 | }; |
---|
1178 | |
---|
1179 | |
---|
1180 | /* Pool cleanup handler. Make sure fs's access ctx points to NULL |
---|
1181 | when request pool is destroyed. */ |
---|
1182 | static apr_status_t |
---|
1183 | cleanup_fs_access(void *data) |
---|
1184 | { |
---|
1185 | svn_error_t *serr; |
---|
1186 | struct cleanup_fs_access_baton *baton = data; |
---|
1187 | |
---|
1188 | serr = svn_fs_set_access(baton->fs, NULL); |
---|
1189 | if (serr) |
---|
1190 | { |
---|
1191 | ap_log_perror(APLOG_MARK, APLOG_ERR, serr->apr_err, baton->pool, |
---|
1192 | "cleanup_fs_access: error clearing fs access context"); |
---|
1193 | svn_error_clear(serr); |
---|
1194 | } |
---|
1195 | |
---|
1196 | return APR_SUCCESS; |
---|
1197 | } |
---|
1198 | |
---|
1199 | |
---|
1200 | /* Helper func to construct a special 'parentpath' private resource. */ |
---|
1201 | static dav_error * |
---|
1202 | get_parentpath_resource(request_rec *r, |
---|
1203 | const char *root_path, |
---|
1204 | dav_resource **resource) |
---|
1205 | { |
---|
1206 | const char *new_uri; |
---|
1207 | dav_svn_root *droot = apr_pcalloc(r->pool, sizeof(*droot)); |
---|
1208 | dav_svn_repos *repos = apr_pcalloc(r->pool, sizeof(*repos)); |
---|
1209 | dav_resource_combined *comb = apr_pcalloc(r->pool, sizeof(*comb)); |
---|
1210 | apr_size_t len = strlen(r->uri); |
---|
1211 | |
---|
1212 | comb->res.exists = TRUE; |
---|
1213 | comb->res.collection = TRUE; |
---|
1214 | comb->res.uri = apr_pstrdup(r->pool, r->uri); |
---|
1215 | comb->res.info = &comb->priv; |
---|
1216 | comb->res.hooks = &dav_svn__hooks_repository; |
---|
1217 | comb->res.pool = r->pool; |
---|
1218 | comb->res.type = DAV_RESOURCE_TYPE_PRIVATE; |
---|
1219 | |
---|
1220 | comb->priv.restype = DAV_SVN_RESTYPE_PARENTPATH_COLLECTION; |
---|
1221 | comb->priv.r = r; |
---|
1222 | comb->priv.repos_path = "Collection of Repositories"; |
---|
1223 | comb->priv.root = *droot; |
---|
1224 | droot->rev = SVN_INVALID_REVNUM; |
---|
1225 | |
---|
1226 | comb->priv.repos = repos; |
---|
1227 | repos->pool = r->pool; |
---|
1228 | repos->xslt_uri = dav_svn__get_xslt_uri(r); |
---|
1229 | repos->autoversioning = dav_svn__get_autoversioning_flag(r); |
---|
1230 | repos->bulk_updates = dav_svn__get_bulk_updates_flag(r); |
---|
1231 | repos->base_url = ap_construct_url(r->pool, "", r); |
---|
1232 | repos->special_uri = dav_svn__get_special_uri(r); |
---|
1233 | repos->username = r->user; |
---|
1234 | repos->client_capabilities = apr_hash_make(repos->pool); |
---|
1235 | |
---|
1236 | /* Make sure this type of resource always has a trailing slash; if |
---|
1237 | not, redirect to a URI that does. */ |
---|
1238 | if (r->uri[len-1] != '/') |
---|
1239 | { |
---|
1240 | new_uri = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri), |
---|
1241 | "/", NULL); |
---|
1242 | apr_table_setn(r->headers_out, "Location", |
---|
1243 | ap_construct_url(r->pool, new_uri, r)); |
---|
1244 | return dav_new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0, |
---|
1245 | "Requests for a collection must have a " |
---|
1246 | "trailing slash on the URI."); |
---|
1247 | } |
---|
1248 | |
---|
1249 | /* No other "prepping" of resource needs to happen -- no opening |
---|
1250 | of a repository or anything like that, because, well, there's |
---|
1251 | no repository to open. */ |
---|
1252 | *resource = &comb->res; |
---|
1253 | return NULL; |
---|
1254 | } |
---|
1255 | |
---|
1256 | /* --------------- Borrowed from httpd's mod_negotiation.c -------------- */ |
---|
1257 | |
---|
1258 | typedef struct accept_rec { |
---|
1259 | char *name; /* MUST be lowercase */ |
---|
1260 | float quality; |
---|
1261 | } accept_rec; |
---|
1262 | |
---|
1263 | /* |
---|
1264 | * Get a single Accept-encoding line from ACCEPT_LINE, and place the |
---|
1265 | * information we have parsed out of it into RESULT. |
---|
1266 | */ |
---|
1267 | |
---|
1268 | static const char *get_entry(apr_pool_t *p, accept_rec *result, |
---|
1269 | const char *accept_line) |
---|
1270 | { |
---|
1271 | result->quality = 1.0f; |
---|
1272 | |
---|
1273 | /* |
---|
1274 | * Note that this handles what I gather is the "old format", |
---|
1275 | * |
---|
1276 | * Accept: text/html text/plain moo/zot |
---|
1277 | * |
---|
1278 | * without any compatibility kludges --- if the token after the |
---|
1279 | * MIME type begins with a semicolon, we know we're looking at parms, |
---|
1280 | * otherwise, we know we aren't. (So why all the pissing and moaning |
---|
1281 | * in the CERN server code? I must be missing something). |
---|
1282 | */ |
---|
1283 | |
---|
1284 | result->name = ap_get_token(p, &accept_line, 0); |
---|
1285 | ap_str_tolower(result->name); /* You want case insensitive, |
---|
1286 | * you'll *get* case insensitive. |
---|
1287 | */ |
---|
1288 | |
---|
1289 | while (*accept_line == ';') |
---|
1290 | { |
---|
1291 | /* Parameters ... */ |
---|
1292 | |
---|
1293 | char *parm; |
---|
1294 | char *cp; |
---|
1295 | char *end; |
---|
1296 | |
---|
1297 | ++accept_line; |
---|
1298 | parm = ap_get_token(p, &accept_line, 1); |
---|
1299 | |
---|
1300 | /* Look for 'var = value' --- and make sure the var is in lcase. */ |
---|
1301 | |
---|
1302 | for (cp = parm; (*cp && !apr_isspace(*cp) && *cp != '='); ++cp) |
---|
1303 | { |
---|
1304 | *cp = apr_tolower(*cp); |
---|
1305 | } |
---|
1306 | |
---|
1307 | if (!*cp) |
---|
1308 | { |
---|
1309 | continue; /* No '='; just ignore it. */ |
---|
1310 | } |
---|
1311 | |
---|
1312 | *cp++ = '\0'; /* Delimit var */ |
---|
1313 | while (*cp && (apr_isspace(*cp) || *cp == '=')) |
---|
1314 | { |
---|
1315 | ++cp; |
---|
1316 | } |
---|
1317 | |
---|
1318 | if (*cp == '"') |
---|
1319 | { |
---|
1320 | ++cp; |
---|
1321 | for (end = cp; |
---|
1322 | (*end && *end != '\n' && *end != '\r' && *end != '\"'); |
---|
1323 | end++); |
---|
1324 | } |
---|
1325 | else |
---|
1326 | { |
---|
1327 | for (end = cp; (*end && !apr_isspace(*end)); end++); |
---|
1328 | } |
---|
1329 | if (*end) |
---|
1330 | { |
---|
1331 | *end = '\0'; /* strip ending quote or return */ |
---|
1332 | } |
---|
1333 | ap_str_tolower(cp); |
---|
1334 | |
---|
1335 | if (parm[0] == 'q' |
---|
1336 | && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0'))) |
---|
1337 | { |
---|
1338 | result->quality = (float) atof(cp); |
---|
1339 | } |
---|
1340 | } |
---|
1341 | |
---|
1342 | if (*accept_line == ',') |
---|
1343 | { |
---|
1344 | ++accept_line; |
---|
1345 | } |
---|
1346 | |
---|
1347 | return accept_line; |
---|
1348 | } |
---|
1349 | |
---|
1350 | /* @a accept_line is the Accept-Encoding header, which is of the |
---|
1351 | format: |
---|
1352 | |
---|
1353 | Accept-Encoding: name; q=N; |
---|
1354 | |
---|
1355 | This function will return an array of accept_rec structures that |
---|
1356 | contain the accepted encodings and the quality each one has |
---|
1357 | associated with them. |
---|
1358 | */ |
---|
1359 | static apr_array_header_t *do_header_line(apr_pool_t *p, |
---|
1360 | const char *accept_line) |
---|
1361 | { |
---|
1362 | apr_array_header_t *accept_recs; |
---|
1363 | |
---|
1364 | if (!accept_line) |
---|
1365 | return NULL; |
---|
1366 | |
---|
1367 | accept_recs = apr_array_make(p, 10, sizeof(accept_rec)); |
---|
1368 | |
---|
1369 | while (*accept_line) |
---|
1370 | { |
---|
1371 | accept_rec *prefs = (accept_rec *) apr_array_push(accept_recs); |
---|
1372 | accept_line = get_entry(p, prefs, accept_line); |
---|
1373 | } |
---|
1374 | |
---|
1375 | return accept_recs; |
---|
1376 | } |
---|
1377 | |
---|
1378 | /* ---------------------------------------------------------------------- */ |
---|
1379 | |
---|
1380 | |
---|
1381 | /* qsort comparison function for the quality field of the accept_rec |
---|
1382 | structure */ |
---|
1383 | static int sort_encoding_pref(const void *accept_rec1, const void *accept_rec2) |
---|
1384 | { |
---|
1385 | float diff = ((const accept_rec *) accept_rec1)->quality - |
---|
1386 | ((const accept_rec *) accept_rec2)->quality; |
---|
1387 | return (diff == 0 ? 0 : (diff > 0 ? -1 : 1)); |
---|
1388 | } |
---|
1389 | |
---|
1390 | /* Parse and handle any possible Accept-Encoding header that has been |
---|
1391 | sent as part of the request. */ |
---|
1392 | static void |
---|
1393 | negotiate_encoding_prefs(request_rec *r, int *svndiff_version) |
---|
1394 | { |
---|
1395 | /* It would be nice if mod_negotiation |
---|
1396 | <http://httpd.apache.org/docs-2.1/mod/mod_negotiation.html> could |
---|
1397 | handle the Accept-Encoding header parsing for us. Sadly, it |
---|
1398 | looks like its data structures and routines are private (see |
---|
1399 | httpd/modules/mappers/mod_negotiation.c). Thus, we duplicate the |
---|
1400 | necessary ones in this file. */ |
---|
1401 | int i; |
---|
1402 | const apr_array_header_t *encoding_prefs; |
---|
1403 | encoding_prefs = do_header_line(r->pool, |
---|
1404 | apr_table_get(r->headers_in, |
---|
1405 | "Accept-Encoding")); |
---|
1406 | |
---|
1407 | if (!encoding_prefs || apr_is_empty_array(encoding_prefs)) |
---|
1408 | { |
---|
1409 | *svndiff_version = 0; |
---|
1410 | return; |
---|
1411 | } |
---|
1412 | |
---|
1413 | *svndiff_version = 0; |
---|
1414 | qsort(encoding_prefs->elts, (size_t) encoding_prefs->nelts, |
---|
1415 | sizeof(accept_rec), sort_encoding_pref); |
---|
1416 | for (i = 0; i < encoding_prefs->nelts; i++) |
---|
1417 | { |
---|
1418 | struct accept_rec rec = APR_ARRAY_IDX(encoding_prefs, i, |
---|
1419 | struct accept_rec); |
---|
1420 | if (strcmp(rec.name, "svndiff1") == 0) |
---|
1421 | { |
---|
1422 | *svndiff_version = 1; |
---|
1423 | break; |
---|
1424 | } |
---|
1425 | else if (strcmp(rec.name, "svndiff") == 0) |
---|
1426 | { |
---|
1427 | *svndiff_version = 0; |
---|
1428 | break; |
---|
1429 | } |
---|
1430 | } |
---|
1431 | } |
---|
1432 | |
---|
1433 | |
---|
1434 | /* The only two possible values for a capability. */ |
---|
1435 | static const char *capability_yes = "yes"; |
---|
1436 | static const char *capability_no = "no"; |
---|
1437 | |
---|
1438 | /* Convert CAPABILITIES, a hash table mapping 'const char *' keys to |
---|
1439 | * "yes" or "no" values, to a list of all keys whose value is "yes". |
---|
1440 | * Return the list, allocated in POOL, and use POOL for all temporary |
---|
1441 | * allocation. |
---|
1442 | */ |
---|
1443 | static apr_array_header_t * |
---|
1444 | capabilities_as_list(apr_hash_t *capabilities, apr_pool_t *pool) |
---|
1445 | { |
---|
1446 | apr_array_header_t *list = apr_array_make(pool, apr_hash_count(capabilities), |
---|
1447 | sizeof(char *)); |
---|
1448 | apr_hash_index_t *hi; |
---|
1449 | |
---|
1450 | for (hi = apr_hash_first(pool, capabilities); hi; hi = apr_hash_next(hi)) |
---|
1451 | { |
---|
1452 | const void *key; |
---|
1453 | void *val; |
---|
1454 | apr_hash_this(hi, &key, NULL, &val); |
---|
1455 | if (strcmp((const char *) val, "yes") == 0) |
---|
1456 | APR_ARRAY_PUSH(list, const char *) = key; |
---|
1457 | } |
---|
1458 | |
---|
1459 | return list; |
---|
1460 | } |
---|
1461 | |
---|
1462 | |
---|
1463 | /* Given a non-NULL QUERY string of the form "key1=val1&key2=val2&...", |
---|
1464 | * parse the keys and values into an apr table. Allocate the table in |
---|
1465 | * POOL; dup all keys and values into POOL as well. |
---|
1466 | * |
---|
1467 | * Note that repeating the same key will cause table overwrites |
---|
1468 | * (e.g. "r=3&r=5"), and that a lack of value ("p=") is legal, but |
---|
1469 | * equivalent to not specifying the key at all. |
---|
1470 | */ |
---|
1471 | static apr_table_t * |
---|
1472 | querystring_to_table(const char *query, apr_pool_t *pool) |
---|
1473 | { |
---|
1474 | apr_table_t *table = apr_table_make(pool, 2); |
---|
1475 | apr_array_header_t *array = svn_cstring_split(query, "&", TRUE, pool); |
---|
1476 | int i; |
---|
1477 | for (i = 0; i < array->nelts; i++) |
---|
1478 | { |
---|
1479 | char *keyval = APR_ARRAY_IDX(array, i, char *); |
---|
1480 | char *equals = strchr(keyval, '='); |
---|
1481 | if (equals != NULL) |
---|
1482 | { |
---|
1483 | *equals = '\0'; |
---|
1484 | apr_table_set(table, keyval, equals + 1); |
---|
1485 | } |
---|
1486 | } |
---|
1487 | return table; |
---|
1488 | } |
---|
1489 | |
---|
1490 | |
---|
1491 | /* Helper for get_resource(). |
---|
1492 | * |
---|
1493 | * Given a fully fleshed out COMB object which has already been parsed |
---|
1494 | * via parse_uri(), parse the querystring in QUERY. |
---|
1495 | * |
---|
1496 | * Specifically, look for optional 'p=PEGREV' and 'r=WORKINGREV' |
---|
1497 | * values in the querystring, and modify COMB so that prep_regular() |
---|
1498 | * opens the correct revision and path. |
---|
1499 | */ |
---|
1500 | static dav_error * |
---|
1501 | parse_querystring(const char *query, dav_resource_combined *comb, |
---|
1502 | apr_pool_t *pool) |
---|
1503 | { |
---|
1504 | svn_error_t *serr; |
---|
1505 | svn_revnum_t working_rev, peg_rev; |
---|
1506 | apr_table_t *pairs = querystring_to_table(query, pool); |
---|
1507 | const char *prevstr = apr_table_get(pairs, "p"); |
---|
1508 | const char *wrevstr; |
---|
1509 | |
---|
1510 | if (prevstr) |
---|
1511 | { |
---|
1512 | peg_rev = SVN_STR_TO_REV(prevstr); |
---|
1513 | if (!SVN_IS_VALID_REVNUM(peg_rev)) |
---|
1514 | return dav_new_error(pool, HTTP_BAD_REQUEST, 0, |
---|
1515 | "invalid peg rev in query string"); |
---|
1516 | } |
---|
1517 | else |
---|
1518 | { |
---|
1519 | /* No peg-rev? Default to HEAD, just like the cmdline client. */ |
---|
1520 | serr = svn_fs_youngest_rev(&peg_rev, comb->priv.repos->fs, pool); |
---|
1521 | if (serr != NULL) |
---|
1522 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
1523 | "Couldn't fetch youngest rev.", pool); |
---|
1524 | } |
---|
1525 | |
---|
1526 | wrevstr = apr_table_get(pairs, "r"); |
---|
1527 | if (wrevstr) |
---|
1528 | { |
---|
1529 | working_rev = SVN_STR_TO_REV(wrevstr); |
---|
1530 | if (!SVN_IS_VALID_REVNUM(working_rev)) |
---|
1531 | return dav_new_error(pool, HTTP_BAD_REQUEST, 0, |
---|
1532 | "invalid working rev in query string"); |
---|
1533 | } |
---|
1534 | else |
---|
1535 | /* No working-rev? Assume it's equal to the peg-rev, just |
---|
1536 | like the cmdline client does. */ |
---|
1537 | working_rev = peg_rev; |
---|
1538 | |
---|
1539 | if (working_rev == peg_rev) |
---|
1540 | comb->priv.root.rev = peg_rev; |
---|
1541 | else if (working_rev > peg_rev) |
---|
1542 | return dav_new_error(pool, HTTP_CONFLICT, 0, |
---|
1543 | "working rev greater than peg rev."); |
---|
1544 | else /* working_rev < peg_rev */ |
---|
1545 | { |
---|
1546 | const char *newpath; |
---|
1547 | apr_hash_t *locations; |
---|
1548 | apr_array_header_t *loc_revs = apr_array_make(pool, 1, |
---|
1549 | sizeof(svn_revnum_t)); |
---|
1550 | |
---|
1551 | dav_svn__authz_read_baton *arb = apr_pcalloc(pool, sizeof(*arb)); |
---|
1552 | arb->r = comb->priv.r; |
---|
1553 | arb->repos = comb->priv.repos; |
---|
1554 | |
---|
1555 | APR_ARRAY_PUSH(loc_revs, svn_revnum_t) = working_rev; |
---|
1556 | serr = svn_repos_trace_node_locations(comb->priv.repos->fs, &locations, |
---|
1557 | comb->priv.repos_path, peg_rev, |
---|
1558 | loc_revs, |
---|
1559 | dav_svn__authz_read_func(arb), |
---|
1560 | arb, pool); |
---|
1561 | if (serr != NULL) |
---|
1562 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
1563 | "Couldn't trace history.", pool); |
---|
1564 | newpath = apr_hash_get(locations, &working_rev, sizeof(svn_revnum_t)); |
---|
1565 | if (newpath != NULL) |
---|
1566 | { |
---|
1567 | comb->priv.root.rev = working_rev; |
---|
1568 | comb->priv.repos_path = newpath; |
---|
1569 | } |
---|
1570 | else |
---|
1571 | return dav_new_error(pool, HTTP_NOT_FOUND, 0, |
---|
1572 | "path doesn't exist in that revision."); |
---|
1573 | } |
---|
1574 | |
---|
1575 | return NULL; |
---|
1576 | } |
---|
1577 | |
---|
1578 | |
---|
1579 | |
---|
1580 | static dav_error * |
---|
1581 | get_resource(request_rec *r, |
---|
1582 | const char *root_path, |
---|
1583 | const char *label, |
---|
1584 | int use_checked_in, |
---|
1585 | dav_resource **resource) |
---|
1586 | { |
---|
1587 | const char *fs_path; |
---|
1588 | const char *repo_name; |
---|
1589 | const char *xslt_uri; |
---|
1590 | const char *fs_parent_path; |
---|
1591 | dav_resource_combined *comb; |
---|
1592 | dav_svn_repos *repos; |
---|
1593 | const char *cleaned_uri; |
---|
1594 | const char *repos_name; |
---|
1595 | const char *relative; |
---|
1596 | const char *repos_path; |
---|
1597 | const char *repos_key; |
---|
1598 | const char *version_name; |
---|
1599 | svn_error_t *serr; |
---|
1600 | dav_error *err; |
---|
1601 | int had_slash; |
---|
1602 | dav_locktoken_list *ltl; |
---|
1603 | struct cleanup_fs_access_baton *cleanup_baton; |
---|
1604 | void *userdata; |
---|
1605 | |
---|
1606 | repo_name = dav_svn__get_repo_name(r); |
---|
1607 | xslt_uri = dav_svn__get_xslt_uri(r); |
---|
1608 | fs_parent_path = dav_svn__get_fs_parent_path(r); |
---|
1609 | |
---|
1610 | /* Special case: detect and build the SVNParentPath as a unique type |
---|
1611 | of private resource, iff the SVNListParentPath directive is 'on'. */ |
---|
1612 | if (fs_parent_path && dav_svn__get_list_parentpath_flag(r)) |
---|
1613 | { |
---|
1614 | char *uri = apr_pstrdup(r->pool, r->uri); |
---|
1615 | char *parentpath = apr_pstrdup(r->pool, root_path); |
---|
1616 | apr_size_t uri_len = strlen(uri); |
---|
1617 | apr_size_t parentpath_len = strlen(parentpath); |
---|
1618 | |
---|
1619 | if (uri[uri_len-1] == '/') |
---|
1620 | uri[uri_len-1] = '\0'; |
---|
1621 | |
---|
1622 | if (parentpath[parentpath_len-1] == '/') |
---|
1623 | parentpath[parentpath_len-1] = '\0'; |
---|
1624 | |
---|
1625 | if (strcmp(parentpath, uri) == 0) |
---|
1626 | { |
---|
1627 | err = get_parentpath_resource(r, root_path, resource); |
---|
1628 | if (err) |
---|
1629 | return err; |
---|
1630 | return NULL; |
---|
1631 | } |
---|
1632 | } |
---|
1633 | |
---|
1634 | /* This does all the work of interpreting/splitting the request uri. */ |
---|
1635 | err = dav_svn_split_uri(r, r->uri, root_path, |
---|
1636 | &cleaned_uri, &had_slash, |
---|
1637 | &repos_name, &relative, &repos_path); |
---|
1638 | if (err) |
---|
1639 | return err; |
---|
1640 | |
---|
1641 | /* The path that we will eventually try to open as an svn |
---|
1642 | repository. Normally defined by the SVNPath directive. */ |
---|
1643 | fs_path = dav_svn__get_fs_path(r); |
---|
1644 | |
---|
1645 | /* If the SVNParentPath directive was used instead... */ |
---|
1646 | if (fs_parent_path != NULL) |
---|
1647 | { |
---|
1648 | /* ...then the URL to the repository is actually one implicit |
---|
1649 | component longer... */ |
---|
1650 | root_path = svn_path_join(root_path, repos_name, r->pool); |
---|
1651 | /* ...and we need to specify exactly what repository to open. */ |
---|
1652 | fs_path = svn_path_join(fs_parent_path, repos_name, r->pool); |
---|
1653 | } |
---|
1654 | |
---|
1655 | /* Start building and filling a 'combination' object. */ |
---|
1656 | comb = apr_pcalloc(r->pool, sizeof(*comb)); |
---|
1657 | comb->res.info = &comb->priv; |
---|
1658 | comb->res.hooks = &dav_svn__hooks_repository; |
---|
1659 | comb->res.pool = r->pool; |
---|
1660 | comb->res.uri = cleaned_uri; |
---|
1661 | |
---|
1662 | /* Original request, off which to generate subrequests later. */ |
---|
1663 | comb->priv.r = r; |
---|
1664 | |
---|
1665 | /* ### ugly hack to carry over Content-Type data to the open_stream, which |
---|
1666 | ### does not have access to the request headers. */ |
---|
1667 | { |
---|
1668 | const char *ct = apr_table_get(r->headers_in, "content-type"); |
---|
1669 | |
---|
1670 | comb->priv.is_svndiff = |
---|
1671 | ct != NULL |
---|
1672 | && strcmp(ct, SVN_SVNDIFF_MIME_TYPE) == 0; |
---|
1673 | } |
---|
1674 | |
---|
1675 | negotiate_encoding_prefs(r, &comb->priv.svndiff_version); |
---|
1676 | |
---|
1677 | /* ### and another hack for computing diffs to send to the client */ |
---|
1678 | comb->priv.delta_base = apr_table_get(r->headers_in, |
---|
1679 | SVN_DAV_DELTA_BASE_HEADER); |
---|
1680 | |
---|
1681 | /* Gather any options requested by an svn client. */ |
---|
1682 | comb->priv.svn_client_options = apr_table_get(r->headers_in, |
---|
1683 | SVN_DAV_OPTIONS_HEADER); |
---|
1684 | |
---|
1685 | /* See if the client sent a custom 'version name' request header. */ |
---|
1686 | version_name = apr_table_get(r->headers_in, SVN_DAV_VERSION_NAME_HEADER); |
---|
1687 | comb->priv.version_name |
---|
1688 | = version_name ? SVN_STR_TO_REV(version_name): SVN_INVALID_REVNUM; |
---|
1689 | |
---|
1690 | /* Remember checksums, if any. */ |
---|
1691 | comb->priv.base_checksum = |
---|
1692 | apr_table_get(r->headers_in, SVN_DAV_BASE_FULLTEXT_MD5_HEADER); |
---|
1693 | comb->priv.result_checksum = |
---|
1694 | apr_table_get(r->headers_in, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER); |
---|
1695 | |
---|
1696 | /* "relative" is part of the "uri" string, so it has the proper |
---|
1697 | lifetime to store here. */ |
---|
1698 | /* ### that comment no longer applies. we're creating a string with its |
---|
1699 | ### own lifetime now. so WHY are we using a string? hmm... */ |
---|
1700 | comb->priv.uri_path = svn_stringbuf_create(relative, r->pool); |
---|
1701 | |
---|
1702 | /* initialize this until we put something real here */ |
---|
1703 | comb->priv.root.rev = SVN_INVALID_REVNUM; |
---|
1704 | |
---|
1705 | /* create the repository structure and stash it away */ |
---|
1706 | repos = apr_pcalloc(r->pool, sizeof(*repos)); |
---|
1707 | repos->pool = r->pool; |
---|
1708 | |
---|
1709 | comb->priv.repos = repos; |
---|
1710 | |
---|
1711 | /* We are assuming the root_path will live at least as long as this |
---|
1712 | resource. Considering that it typically comes from the per-dir |
---|
1713 | config in mod_dav, this is valid for now. */ |
---|
1714 | repos->root_path = svn_path_uri_encode(root_path, r->pool); |
---|
1715 | |
---|
1716 | /* where is the SVN FS for this resource? */ |
---|
1717 | repos->fs_path = fs_path; |
---|
1718 | |
---|
1719 | /* A name for the repository */ |
---|
1720 | repos->repo_name = repo_name; |
---|
1721 | |
---|
1722 | /* The repository filesystem basename */ |
---|
1723 | repos->repo_basename = repos_name; |
---|
1724 | |
---|
1725 | /* An XSL transformation */ |
---|
1726 | repos->xslt_uri = xslt_uri; |
---|
1727 | |
---|
1728 | /* Is autoversioning active in this repos? */ |
---|
1729 | repos->autoversioning = dav_svn__get_autoversioning_flag(r); |
---|
1730 | |
---|
1731 | /* Are bulk updates allowed in this repos? */ |
---|
1732 | repos->bulk_updates = dav_svn__get_bulk_updates_flag(r); |
---|
1733 | |
---|
1734 | /* Path to activities database */ |
---|
1735 | repos->activities_db = dav_svn__get_activities_db(r); |
---|
1736 | if (repos->activities_db == NULL) |
---|
1737 | /* If not specified, use default ($repos/dav/activities.d). */ |
---|
1738 | repos->activities_db = svn_path_join(repos->fs_path, |
---|
1739 | DEFAULT_ACTIVITY_DB, |
---|
1740 | r->pool); |
---|
1741 | else if (fs_parent_path != NULL) |
---|
1742 | /* If this is a ParentPath-based repository, treat the specified |
---|
1743 | path as a similar parent directory. */ |
---|
1744 | repos->activities_db = svn_path_join(repos->activities_db, |
---|
1745 | svn_path_basename(repos->fs_path, |
---|
1746 | r->pool), |
---|
1747 | r->pool); |
---|
1748 | |
---|
1749 | /* Remember various bits for later URL construction */ |
---|
1750 | repos->base_url = ap_construct_url(r->pool, "", r); |
---|
1751 | repos->special_uri = dav_svn__get_special_uri(r); |
---|
1752 | |
---|
1753 | /* Remember who is making this request */ |
---|
1754 | repos->username = r->user; |
---|
1755 | |
---|
1756 | /* Allocate room for capabilities, but don't search for any until |
---|
1757 | we know that this is a Subversion client. */ |
---|
1758 | repos->client_capabilities = apr_hash_make(repos->pool); |
---|
1759 | |
---|
1760 | /* Remember if the requesting client is a Subversion client, and if |
---|
1761 | so, what its capabilities are. */ |
---|
1762 | { |
---|
1763 | const char *val = apr_table_get(r->headers_in, "User-Agent"); |
---|
1764 | |
---|
1765 | if (val && (ap_strstr_c(val, "SVN/") == val)) |
---|
1766 | { |
---|
1767 | repos->is_svn_client = TRUE; |
---|
1768 | |
---|
1769 | /* Client capabilities are self-reported. There is no |
---|
1770 | guarantee the client actually has the capabilities it says |
---|
1771 | it has, we just assume it is in the client's interests to |
---|
1772 | report accurately. Also, we only remember the capabilities |
---|
1773 | the server cares about (even though the client may send |
---|
1774 | more than that). */ |
---|
1775 | |
---|
1776 | /* Start out assuming no capabilities. */ |
---|
1777 | apr_hash_set(repos->client_capabilities, SVN_RA_CAPABILITY_MERGEINFO, |
---|
1778 | APR_HASH_KEY_STRING, capability_no); |
---|
1779 | |
---|
1780 | /* Then see what we can find. */ |
---|
1781 | val = apr_table_get(r->headers_in, "DAV"); |
---|
1782 | if (val) |
---|
1783 | { |
---|
1784 | apr_array_header_t *vals |
---|
1785 | = svn_cstring_split(val, ",", TRUE, r->pool); |
---|
1786 | |
---|
1787 | if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, |
---|
1788 | vals)) |
---|
1789 | { |
---|
1790 | apr_hash_set(repos->client_capabilities, |
---|
1791 | SVN_RA_CAPABILITY_MERGEINFO, |
---|
1792 | APR_HASH_KEY_STRING, capability_yes); |
---|
1793 | } |
---|
1794 | } |
---|
1795 | } |
---|
1796 | } |
---|
1797 | |
---|
1798 | /* Retrieve/cache open repository */ |
---|
1799 | repos_key = apr_pstrcat(r->pool, "mod_dav_svn:", fs_path, NULL); |
---|
1800 | apr_pool_userdata_get(&userdata, repos_key, r->connection->pool); |
---|
1801 | repos->repos = userdata; |
---|
1802 | if (repos->repos == NULL) |
---|
1803 | { |
---|
1804 | serr = svn_repos_open(&(repos->repos), fs_path, r->connection->pool); |
---|
1805 | if (serr != NULL) |
---|
1806 | { |
---|
1807 | /* The error returned by svn_repos_open might contain the |
---|
1808 | actual path to the failed repository. We don't want to |
---|
1809 | leak that path back to the client, because that would be |
---|
1810 | a security risk, but we do want to log the real error on |
---|
1811 | the server side. */ |
---|
1812 | return dav_svn__sanitize_error(serr, "Could not open the requested " |
---|
1813 | "SVN filesystem", |
---|
1814 | HTTP_INTERNAL_SERVER_ERROR, r); |
---|
1815 | } |
---|
1816 | |
---|
1817 | /* Cache the open repos for the next request on this connection */ |
---|
1818 | apr_pool_userdata_set(repos->repos, repos_key, |
---|
1819 | NULL, r->connection->pool); |
---|
1820 | |
---|
1821 | /* Store the capabilities of the current connection, making sure |
---|
1822 | to use the same pool repos->repos itself was created in. */ |
---|
1823 | serr = svn_repos_remember_client_capabilities |
---|
1824 | (repos->repos, capabilities_as_list(repos->client_capabilities, |
---|
1825 | r->connection->pool)); |
---|
1826 | if (serr != NULL) |
---|
1827 | { |
---|
1828 | return dav_svn__sanitize_error(serr, |
---|
1829 | "Error storing client capabilities " |
---|
1830 | "in repos object", |
---|
1831 | HTTP_INTERNAL_SERVER_ERROR, r); |
---|
1832 | } |
---|
1833 | } |
---|
1834 | |
---|
1835 | /* cache the filesystem object */ |
---|
1836 | repos->fs = svn_repos_fs(repos->repos); |
---|
1837 | |
---|
1838 | /* capture warnings during cleanup of the FS */ |
---|
1839 | svn_fs_set_warning_func(repos->fs, log_warning, r); |
---|
1840 | |
---|
1841 | /* if an authenticated username is present, attach it to the FS */ |
---|
1842 | if (r->user) |
---|
1843 | { |
---|
1844 | svn_fs_access_t *access_ctx; |
---|
1845 | |
---|
1846 | /* The fs is cached in connection->pool, but the fs access |
---|
1847 | context lives in r->pool. Because the username or token |
---|
1848 | could change on each request, we need to make sure that the |
---|
1849 | fs points to a NULL access context after the request is gone. */ |
---|
1850 | cleanup_baton = apr_pcalloc(r->pool, sizeof(*cleanup_baton)); |
---|
1851 | cleanup_baton->pool = r->pool; |
---|
1852 | cleanup_baton->fs = repos->fs; |
---|
1853 | apr_pool_cleanup_register(r->pool, cleanup_baton, cleanup_fs_access, |
---|
1854 | apr_pool_cleanup_null); |
---|
1855 | |
---|
1856 | /* Create an access context based on the authenticated username. */ |
---|
1857 | serr = svn_fs_create_access(&access_ctx, r->user, r->pool); |
---|
1858 | if (serr) |
---|
1859 | { |
---|
1860 | return dav_svn__sanitize_error(serr, |
---|
1861 | "Could not create fs access context", |
---|
1862 | HTTP_INTERNAL_SERVER_ERROR, r); |
---|
1863 | } |
---|
1864 | |
---|
1865 | /* Attach the access context to the fs. */ |
---|
1866 | serr = svn_fs_set_access(repos->fs, access_ctx); |
---|
1867 | if (serr) |
---|
1868 | { |
---|
1869 | return dav_svn__sanitize_error(serr, "Could not attach access " |
---|
1870 | "context to fs", |
---|
1871 | HTTP_INTERNAL_SERVER_ERROR, r); |
---|
1872 | } |
---|
1873 | } |
---|
1874 | |
---|
1875 | /* Look for locktokens in the "If:" request header. */ |
---|
1876 | err = dav_get_locktoken_list(r, <l); |
---|
1877 | |
---|
1878 | /* dav_get_locktoken_list claims to return a NULL list when no |
---|
1879 | locktokens are present. But it actually throws this error |
---|
1880 | instead! So we're deliberately trapping/ignoring it. |
---|
1881 | |
---|
1882 | This is a workaround for a bug in mod_dav. Remove this when the |
---|
1883 | bug is fixed in mod_dav. See Subversion Issue #2248 */ |
---|
1884 | if (err && (err->error_id != DAV_ERR_IF_ABSENT)) |
---|
1885 | return err; |
---|
1886 | |
---|
1887 | /* If one or more locktokens are present in the header, push them |
---|
1888 | into the filesystem access context. */ |
---|
1889 | if (ltl) |
---|
1890 | { |
---|
1891 | svn_fs_access_t *access_ctx; |
---|
1892 | dav_locktoken_list *list = ltl; |
---|
1893 | |
---|
1894 | serr = svn_fs_get_access(&access_ctx, repos->fs); |
---|
1895 | if (serr) |
---|
1896 | { |
---|
1897 | return dav_svn__sanitize_error(serr, "Lock token is in request, " |
---|
1898 | "but no user name", |
---|
1899 | HTTP_BAD_REQUEST, r); |
---|
1900 | } |
---|
1901 | |
---|
1902 | do { |
---|
1903 | /* Note the path/lock pairs are only for lock token checking |
---|
1904 | in access, and the relative path is not actually accurate |
---|
1905 | as it contains the !svn bits. However, we're using only |
---|
1906 | the tokens anyway (for access control). */ |
---|
1907 | |
---|
1908 | serr = svn_fs_access_add_lock_token2(access_ctx, relative, |
---|
1909 | list->locktoken->uuid_str); |
---|
1910 | |
---|
1911 | if (serr) |
---|
1912 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
1913 | "Error pushing token into filesystem.", |
---|
1914 | r->pool); |
---|
1915 | list = list->next; |
---|
1916 | |
---|
1917 | } while (list); |
---|
1918 | } |
---|
1919 | |
---|
1920 | |
---|
1921 | /* Figure out the type of the resource. Note that we have a PARSE step |
---|
1922 | which is separate from a PREP step. This is because the PARSE can |
---|
1923 | map multiple URLs to the same resource type. The PREP operates on |
---|
1924 | the type of the resource. */ |
---|
1925 | |
---|
1926 | /* skip over the leading "/" in the relative URI */ |
---|
1927 | if (parse_uri(comb, relative + 1, label, use_checked_in)) |
---|
1928 | goto malformed_URI; |
---|
1929 | |
---|
1930 | /* Check for a query string on a regular-type resource; this allows |
---|
1931 | us to discover and parse a "universal" rev-path URI of the form |
---|
1932 | "path?[r=REV][&p=PEGREV]" */ |
---|
1933 | if ((comb->res.type == DAV_RESOURCE_TYPE_REGULAR) |
---|
1934 | && (r->parsed_uri.query != NULL)) |
---|
1935 | if ((err = parse_querystring(r->parsed_uri.query, comb, r->pool)) != NULL) |
---|
1936 | return err; |
---|
1937 | |
---|
1938 | #ifdef SVN_DEBUG |
---|
1939 | if (comb->res.type == DAV_RESOURCE_TYPE_UNKNOWN) |
---|
1940 | { |
---|
1941 | /* Unknown URI. Return NULL to indicate "no resource" */ |
---|
1942 | DBG0("DESIGN FAILURE: should not be UNKNOWN at this point"); |
---|
1943 | *resource = NULL; |
---|
1944 | return NULL; |
---|
1945 | } |
---|
1946 | #endif |
---|
1947 | |
---|
1948 | /* prepare the resource for operation */ |
---|
1949 | if ((err = prep_resource(comb)) != NULL) |
---|
1950 | return err; |
---|
1951 | |
---|
1952 | /* a GET request for a REGULAR collection resource MUST have a trailing |
---|
1953 | slash. Redirect to include one if it does not. */ |
---|
1954 | if (comb->res.collection && comb->res.type == DAV_RESOURCE_TYPE_REGULAR |
---|
1955 | && !had_slash && r->method_number == M_GET) |
---|
1956 | { |
---|
1957 | /* note that we drop r->args. we don't deal with them anyways */ |
---|
1958 | const char *new_path = apr_pstrcat(r->pool, |
---|
1959 | ap_escape_uri(r->pool, r->uri), |
---|
1960 | "/", |
---|
1961 | NULL); |
---|
1962 | apr_table_setn(r->headers_out, "Location", |
---|
1963 | ap_construct_url(r->pool, new_path, r)); |
---|
1964 | return dav_new_error(r->pool, HTTP_MOVED_PERMANENTLY, 0, |
---|
1965 | "Requests for a collection must have a " |
---|
1966 | "trailing slash on the URI."); |
---|
1967 | } |
---|
1968 | |
---|
1969 | *resource = &comb->res; |
---|
1970 | return NULL; |
---|
1971 | |
---|
1972 | malformed_URI: |
---|
1973 | /* A malformed URI error occurs when a URI indicates the "special" area, |
---|
1974 | yet it has an improper construction. Generally, this is because some |
---|
1975 | doofus typed it in manually or has a buggy client. */ |
---|
1976 | /* ### pick something other than HTTP_INTERNAL_SERVER_ERROR */ |
---|
1977 | /* ### are SVN_ERR_APMOD codes within the right numeric space? */ |
---|
1978 | return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, |
---|
1979 | SVN_ERR_APMOD_MALFORMED_URI, |
---|
1980 | "The URI indicated a resource within Subversion's " |
---|
1981 | "special resource area, but does not exist. This is " |
---|
1982 | "generally caused by a problem in the client " |
---|
1983 | "software."); |
---|
1984 | } |
---|
1985 | |
---|
1986 | |
---|
1987 | /* Helper func: return the parent of PATH, allocated in POOL. */ |
---|
1988 | static const char * |
---|
1989 | get_parent_path(const char *path, apr_pool_t *pool) |
---|
1990 | { |
---|
1991 | apr_size_t len; |
---|
1992 | const char *parentpath, *base_name; |
---|
1993 | char *tmp = apr_pstrdup(pool, path); |
---|
1994 | |
---|
1995 | len = strlen(tmp); |
---|
1996 | |
---|
1997 | if (len > 0) |
---|
1998 | { |
---|
1999 | /* Remove any trailing slash; else svn_path_split() asserts. */ |
---|
2000 | if (tmp[len-1] == '/') |
---|
2001 | tmp[len-1] = '\0'; |
---|
2002 | svn_path_split(tmp, &parentpath, &base_name, pool); |
---|
2003 | |
---|
2004 | return parentpath; |
---|
2005 | } |
---|
2006 | |
---|
2007 | return path; |
---|
2008 | } |
---|
2009 | |
---|
2010 | |
---|
2011 | static dav_error * |
---|
2012 | get_parent_resource(const dav_resource *resource, |
---|
2013 | dav_resource **parent_resource) |
---|
2014 | { |
---|
2015 | dav_resource *parent; |
---|
2016 | dav_resource_private *parentinfo; |
---|
2017 | svn_stringbuf_t *path = resource->info->uri_path; |
---|
2018 | |
---|
2019 | /* the root of the repository has no parent */ |
---|
2020 | if (path->len == 1 && *path->data == '/') |
---|
2021 | { |
---|
2022 | *parent_resource = NULL; |
---|
2023 | return NULL; |
---|
2024 | } |
---|
2025 | |
---|
2026 | switch (resource->type) |
---|
2027 | { |
---|
2028 | case DAV_RESOURCE_TYPE_REGULAR: |
---|
2029 | |
---|
2030 | parent = apr_pcalloc(resource->pool, sizeof(*parent)); |
---|
2031 | parentinfo = apr_pcalloc(resource->pool, sizeof(*parentinfo)); |
---|
2032 | |
---|
2033 | parent->type = DAV_RESOURCE_TYPE_REGULAR; |
---|
2034 | parent->exists = 1; |
---|
2035 | parent->collection = 1; |
---|
2036 | parent->versioned = 1; |
---|
2037 | parent->hooks = resource->hooks; |
---|
2038 | parent->pool = resource->pool; |
---|
2039 | parent->uri = get_parent_path(resource->uri, resource->pool); |
---|
2040 | parent->info = parentinfo; |
---|
2041 | |
---|
2042 | parentinfo->pool = resource->info->pool; |
---|
2043 | parentinfo->uri_path = |
---|
2044 | svn_stringbuf_create(get_parent_path(resource->info->uri_path->data, |
---|
2045 | resource->pool), resource->pool); |
---|
2046 | parentinfo->repos = resource->info->repos; |
---|
2047 | parentinfo->root = resource->info->root; |
---|
2048 | parentinfo->r = resource->info->r; |
---|
2049 | parentinfo->svn_client_options = resource->info->svn_client_options; |
---|
2050 | parentinfo->repos_path = get_parent_path(resource->info->repos_path, |
---|
2051 | resource->pool); |
---|
2052 | |
---|
2053 | *parent_resource = parent; |
---|
2054 | break; |
---|
2055 | |
---|
2056 | case DAV_RESOURCE_TYPE_WORKING: |
---|
2057 | /* The "/" occurring within the URL of working resources is part of |
---|
2058 | its identifier; it does not establish parent resource relationships. |
---|
2059 | All working resources have the same parent, which is: |
---|
2060 | http://host.name/path2repos/$svn/wrk/ |
---|
2061 | */ |
---|
2062 | *parent_resource = |
---|
2063 | create_private_resource(resource, DAV_SVN_RESTYPE_WRK_COLLECTION); |
---|
2064 | break; |
---|
2065 | |
---|
2066 | case DAV_RESOURCE_TYPE_ACTIVITY: |
---|
2067 | *parent_resource = |
---|
2068 | create_private_resource(resource, DAV_SVN_RESTYPE_ACT_COLLECTION); |
---|
2069 | break; |
---|
2070 | |
---|
2071 | default: |
---|
2072 | /* ### needs more work. need parents for other resource types |
---|
2073 | ### |
---|
2074 | ### return an error so we can easily identify the cases where |
---|
2075 | ### we've called this function unexpectedly. */ |
---|
2076 | return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, |
---|
2077 | apr_psprintf(resource->pool, |
---|
2078 | "get_parent_resource was called for " |
---|
2079 | "%s (type %d)", |
---|
2080 | resource->uri, resource->type)); |
---|
2081 | break; |
---|
2082 | } |
---|
2083 | |
---|
2084 | return NULL; |
---|
2085 | } |
---|
2086 | |
---|
2087 | |
---|
2088 | /* does RES2 live in the same repository as RES1? */ |
---|
2089 | static int |
---|
2090 | is_our_resource(const dav_resource *res1, const dav_resource *res2) |
---|
2091 | { |
---|
2092 | if (res1->hooks != res2->hooks |
---|
2093 | || strcmp(res1->info->repos->fs_path, res2->info->repos->fs_path) != 0) |
---|
2094 | { |
---|
2095 | /* a different provider, or a different FS repository */ |
---|
2096 | return 0; |
---|
2097 | } |
---|
2098 | |
---|
2099 | /* coalesce the repository */ |
---|
2100 | if (res1->info->repos != res2->info->repos) |
---|
2101 | { |
---|
2102 | /* ### might be nice to have a pool which we can clear to toss |
---|
2103 | ### out the old, redundant repos/fs. */ |
---|
2104 | |
---|
2105 | /* have res2 point to res1's filesystem */ |
---|
2106 | res2->info->repos = res1->info->repos; |
---|
2107 | |
---|
2108 | /* res2's fs_root object is now invalid. regenerate it using |
---|
2109 | the now-shared filesystem. */ |
---|
2110 | if (res2->info->root.txn_name) |
---|
2111 | { |
---|
2112 | /* reopen the txn by name */ |
---|
2113 | svn_error_clear(svn_fs_open_txn(&(res2->info->root.txn), |
---|
2114 | res2->info->repos->fs, |
---|
2115 | res2->info->root.txn_name, |
---|
2116 | res2->info->repos->pool)); |
---|
2117 | |
---|
2118 | /* regenerate the txn "root" object */ |
---|
2119 | svn_error_clear(svn_fs_txn_root(&(res2->info->root.root), |
---|
2120 | res2->info->root.txn, |
---|
2121 | res2->info->repos->pool)); |
---|
2122 | } |
---|
2123 | else if (res2->info->root.rev) |
---|
2124 | { |
---|
2125 | /* default: regenerate the revision "root" object */ |
---|
2126 | svn_error_clear(svn_fs_revision_root(&(res2->info->root.root), |
---|
2127 | res2->info->repos->fs, |
---|
2128 | res2->info->root.rev, |
---|
2129 | res2->info->repos->pool)); |
---|
2130 | } |
---|
2131 | } |
---|
2132 | |
---|
2133 | return 1; |
---|
2134 | } |
---|
2135 | |
---|
2136 | |
---|
2137 | static int |
---|
2138 | is_same_resource(const dav_resource *res1, const dav_resource *res2) |
---|
2139 | { |
---|
2140 | if (!is_our_resource(res1, res2)) |
---|
2141 | return 0; |
---|
2142 | |
---|
2143 | /* ### what if the same resource were reached via two URIs? */ |
---|
2144 | |
---|
2145 | return svn_stringbuf_compare(res1->info->uri_path, res2->info->uri_path); |
---|
2146 | } |
---|
2147 | |
---|
2148 | |
---|
2149 | static int |
---|
2150 | is_parent_resource(const dav_resource *res1, const dav_resource *res2) |
---|
2151 | { |
---|
2152 | apr_size_t len1 = strlen(res1->info->uri_path->data); |
---|
2153 | apr_size_t len2; |
---|
2154 | |
---|
2155 | if (!is_our_resource(res1, res2)) |
---|
2156 | return 0; |
---|
2157 | |
---|
2158 | /* ### what if a resource were reached via two URIs? we ought to define |
---|
2159 | ### parent/child relations for resources independent of URIs. |
---|
2160 | ### i.e. define a "canonical" location for each resource, then return |
---|
2161 | ### the parent based on that location. */ |
---|
2162 | |
---|
2163 | /* res2 is one of our resources, we can use its ->info ptr */ |
---|
2164 | len2 = strlen(res2->info->uri_path->data); |
---|
2165 | |
---|
2166 | return (len2 > len1 |
---|
2167 | && memcmp(res1->info->uri_path->data, res2->info->uri_path->data, |
---|
2168 | len1) == 0 |
---|
2169 | && res2->info->uri_path->data[len1] == '/'); |
---|
2170 | } |
---|
2171 | |
---|
2172 | |
---|
2173 | #if 0 |
---|
2174 | /* Given an apache request R and a ROOT_PATH to the svn location |
---|
2175 | block, set *KIND to the node-kind of the URI's associated |
---|
2176 | (revision, path) pair, if possible. |
---|
2177 | |
---|
2178 | Public uris, baseline collections, version resources, and working |
---|
2179 | (non-baseline) resources all have associated (revision, path) |
---|
2180 | pairs, and thus one of {svn_node_file, svn_node_dir, svn_node_none} |
---|
2181 | will be returned. |
---|
2182 | |
---|
2183 | If URI is something more abstract, then set *KIND to |
---|
2184 | svn_node_unknown. This is true for baselines, working baselines, |
---|
2185 | version controled configurations, activities, histories, and other |
---|
2186 | private resources. |
---|
2187 | */ |
---|
2188 | static dav_error * |
---|
2189 | resource_kind(request_rec *r, |
---|
2190 | const char *uri, |
---|
2191 | const char *root_path, |
---|
2192 | svn_node_kind_t *kind) |
---|
2193 | { |
---|
2194 | dav_error *derr; |
---|
2195 | svn_error_t *serr; |
---|
2196 | dav_resource *resource; |
---|
2197 | svn_revnum_t base_rev; |
---|
2198 | svn_fs_root_t *base_rev_root; |
---|
2199 | char *saved_uri; |
---|
2200 | |
---|
2201 | /* Temporarily insert the uri that the user actually wants us to |
---|
2202 | convert into a resource. Typically, this is already r->uri, so |
---|
2203 | this is usually a no-op. But sometimes the caller may pass in |
---|
2204 | the Destination: header uri. |
---|
2205 | |
---|
2206 | ### WHAT WE REALLY WANT here is to refactor get_resource, |
---|
2207 | so that some alternate interface actually allows us to specify |
---|
2208 | the URI to process, i.e. not always process r->uri. |
---|
2209 | */ |
---|
2210 | saved_uri = r->uri; |
---|
2211 | r->uri = apr_pstrdup(r->pool, uri); |
---|
2212 | |
---|
2213 | /* parse the uri and prep the associated resource. */ |
---|
2214 | derr = get_resource(r, root_path, |
---|
2215 | /* ### I can't believe that every single |
---|
2216 | parser ignores the LABEL and USE_CHECKED_IN |
---|
2217 | args below!! */ |
---|
2218 | "ignored_label", 1, |
---|
2219 | &resource); |
---|
2220 | /* Restore r back to normal. */ |
---|
2221 | r->uri = saved_uri; |
---|
2222 | |
---|
2223 | if (derr) |
---|
2224 | return derr; |
---|
2225 | |
---|
2226 | if (resource->type == DAV_RESOURCE_TYPE_REGULAR) |
---|
2227 | { |
---|
2228 | /* Either a public URI or a bc. In both cases, prep_regular() |
---|
2229 | has already set the 'exists' and 'collection' flags by |
---|
2230 | querying the appropriate revision root and path. */ |
---|
2231 | if (! resource->exists) |
---|
2232 | *kind = svn_node_none; |
---|
2233 | else |
---|
2234 | *kind = resource->collection ? svn_node_dir : svn_node_file; |
---|
2235 | } |
---|
2236 | |
---|
2237 | else if (resource->type == DAV_RESOURCE_TYPE_VERSION) |
---|
2238 | { |
---|
2239 | if (resource->baselined) /* bln */ |
---|
2240 | *kind = svn_node_unknown; |
---|
2241 | |
---|
2242 | else /* ver */ |
---|
2243 | { |
---|
2244 | derr = fs_check_path(kind, resource->info->root.root, |
---|
2245 | resource->info->repos_path, r->pool); |
---|
2246 | if (derr != NULL) |
---|
2247 | return derr; |
---|
2248 | } |
---|
2249 | } |
---|
2250 | |
---|
2251 | else if (resource->type == DAV_RESOURCE_TYPE_WORKING) |
---|
2252 | { |
---|
2253 | if (resource->baselined) /* wbl */ |
---|
2254 | *kind = svn_node_unknown; |
---|
2255 | |
---|
2256 | else /* wrk */ |
---|
2257 | { |
---|
2258 | /* don't call fs_check_path on the txn, but on the original |
---|
2259 | revision that the txn is based on. */ |
---|
2260 | base_rev = svn_fs_txn_base_revision(resource->info->root.txn); |
---|
2261 | serr = svn_fs_revision_root(&base_rev_root, |
---|
2262 | resource->info->repos->fs, |
---|
2263 | base_rev, r->pool); |
---|
2264 | if (serr) |
---|
2265 | return dav_svn__convert_err |
---|
2266 | (serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2267 | apr_psprintf(r->pool, |
---|
2268 | "Could not open root of revision %ld", |
---|
2269 | base_rev), |
---|
2270 | r->pool); |
---|
2271 | |
---|
2272 | derr = fs_check_path(kind, base_rev_root, |
---|
2273 | resource->info->repos_path, r->pool); |
---|
2274 | if (derr != NULL) |
---|
2275 | return derr; |
---|
2276 | } |
---|
2277 | } |
---|
2278 | |
---|
2279 | else |
---|
2280 | /* act, his, vcc, or some other private resource */ |
---|
2281 | *kind = svn_node_unknown; |
---|
2282 | |
---|
2283 | return NULL; |
---|
2284 | } |
---|
2285 | #endif |
---|
2286 | |
---|
2287 | |
---|
2288 | static dav_error * |
---|
2289 | open_stream(const dav_resource *resource, |
---|
2290 | dav_stream_mode mode, |
---|
2291 | dav_stream **stream) |
---|
2292 | { |
---|
2293 | svn_node_kind_t kind; |
---|
2294 | dav_error *derr; |
---|
2295 | svn_error_t *serr; |
---|
2296 | |
---|
2297 | if (mode == DAV_MODE_WRITE_TRUNC || mode == DAV_MODE_WRITE_SEEKABLE) |
---|
2298 | { |
---|
2299 | if (resource->type != DAV_RESOURCE_TYPE_WORKING) |
---|
2300 | { |
---|
2301 | return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0, |
---|
2302 | "Resource body changes may only be made to " |
---|
2303 | "working resources [at this time]."); |
---|
2304 | } |
---|
2305 | } |
---|
2306 | |
---|
2307 | #if 1 |
---|
2308 | if (mode == DAV_MODE_WRITE_SEEKABLE) |
---|
2309 | { |
---|
2310 | return dav_new_error(resource->pool, HTTP_NOT_IMPLEMENTED, 0, |
---|
2311 | "Resource body writes cannot use ranges " |
---|
2312 | "[at this time]."); |
---|
2313 | } |
---|
2314 | #endif |
---|
2315 | |
---|
2316 | /* start building the stream structure */ |
---|
2317 | *stream = apr_pcalloc(resource->pool, sizeof(**stream)); |
---|
2318 | (*stream)->res = resource; |
---|
2319 | |
---|
2320 | derr = fs_check_path(&kind, resource->info->root.root, |
---|
2321 | resource->info->repos_path, resource->pool); |
---|
2322 | if (derr != NULL) |
---|
2323 | return derr; |
---|
2324 | |
---|
2325 | if (kind == svn_node_none) /* No existing file. */ |
---|
2326 | { |
---|
2327 | serr = svn_fs_make_file(resource->info->root.root, |
---|
2328 | resource->info->repos_path, |
---|
2329 | resource->pool); |
---|
2330 | |
---|
2331 | if (serr != NULL) |
---|
2332 | { |
---|
2333 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2334 | "Could not create file within the " |
---|
2335 | "repository.", |
---|
2336 | resource->pool); |
---|
2337 | } |
---|
2338 | } |
---|
2339 | |
---|
2340 | /* if the working-resource was auto-checked-out (i.e. came into |
---|
2341 | existence through the autoversioning feature), then possibly set |
---|
2342 | the svn:mime-type property based on whatever value mod_mime has |
---|
2343 | chosen. If the path already has an svn:mime-type property |
---|
2344 | set, do nothing. */ |
---|
2345 | if (resource->info->auto_checked_out |
---|
2346 | && resource->info->r->content_type) |
---|
2347 | { |
---|
2348 | svn_string_t *mime_type; |
---|
2349 | |
---|
2350 | serr = svn_fs_node_prop(&mime_type, |
---|
2351 | resource->info->root.root, |
---|
2352 | resource->info->repos_path, |
---|
2353 | SVN_PROP_MIME_TYPE, |
---|
2354 | resource->pool); |
---|
2355 | |
---|
2356 | if (serr != NULL) |
---|
2357 | { |
---|
2358 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2359 | "Error fetching mime-type property.", |
---|
2360 | resource->pool); |
---|
2361 | } |
---|
2362 | |
---|
2363 | if (!mime_type) |
---|
2364 | { |
---|
2365 | serr = svn_fs_change_node_prop(resource->info->root.root, |
---|
2366 | resource->info->repos_path, |
---|
2367 | SVN_PROP_MIME_TYPE, |
---|
2368 | svn_string_create |
---|
2369 | (resource->info->r->content_type, |
---|
2370 | resource->pool), |
---|
2371 | resource->pool); |
---|
2372 | if (serr != NULL) |
---|
2373 | { |
---|
2374 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2375 | "Could not set mime-type property.", |
---|
2376 | resource->pool); |
---|
2377 | } |
---|
2378 | } |
---|
2379 | } |
---|
2380 | |
---|
2381 | serr = svn_fs_apply_textdelta(&(*stream)->delta_handler, |
---|
2382 | &(*stream)->delta_baton, |
---|
2383 | resource->info->root.root, |
---|
2384 | resource->info->repos_path, |
---|
2385 | resource->info->base_checksum, |
---|
2386 | resource->info->result_checksum, |
---|
2387 | resource->pool); |
---|
2388 | |
---|
2389 | if (serr != NULL) |
---|
2390 | { |
---|
2391 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2392 | "Could not prepare to write the file", |
---|
2393 | resource->pool); |
---|
2394 | } |
---|
2395 | |
---|
2396 | /* if the incoming data is an SVNDIFF, then create a stream that |
---|
2397 | will process the data into windows and invoke the FS window handler |
---|
2398 | when a window is ready. */ |
---|
2399 | /* ### we need a better way to check the content-type! this is bogus |
---|
2400 | ### because we're effectively looking at the request_rec. doubly |
---|
2401 | ### bogus because this means you cannot open arbitrary streams and |
---|
2402 | ### feed them content (the type is always tied to a request_rec). |
---|
2403 | ### probably ought to pass the type to open_stream */ |
---|
2404 | if (resource->info->is_svndiff) |
---|
2405 | { |
---|
2406 | (*stream)->wstream = |
---|
2407 | svn_txdelta_parse_svndiff((*stream)->delta_handler, |
---|
2408 | (*stream)->delta_baton, |
---|
2409 | TRUE, |
---|
2410 | resource->pool); |
---|
2411 | } |
---|
2412 | |
---|
2413 | return NULL; |
---|
2414 | } |
---|
2415 | |
---|
2416 | |
---|
2417 | static dav_error * |
---|
2418 | close_stream(dav_stream *stream, int commit) |
---|
2419 | { |
---|
2420 | svn_error_t *serr; |
---|
2421 | apr_pool_t *pool = stream->res->pool; |
---|
2422 | |
---|
2423 | if (stream->rstream != NULL) |
---|
2424 | { |
---|
2425 | serr = svn_stream_close(stream->rstream); |
---|
2426 | if (serr) |
---|
2427 | return dav_svn__convert_err |
---|
2428 | (serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2429 | "mod_dav_svn close_stream: error closing read stream", |
---|
2430 | pool); |
---|
2431 | } |
---|
2432 | |
---|
2433 | /* if we have a write-stream, then closing it also takes care of the |
---|
2434 | handler (so make sure not to send a NULL to it, too) */ |
---|
2435 | if (stream->wstream != NULL) |
---|
2436 | { |
---|
2437 | serr = svn_stream_close(stream->wstream); |
---|
2438 | if (serr) |
---|
2439 | return dav_svn__convert_err |
---|
2440 | (serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2441 | "mod_dav_svn close_stream: error closing write stream", |
---|
2442 | pool); |
---|
2443 | } |
---|
2444 | else if (stream->delta_handler != NULL) |
---|
2445 | { |
---|
2446 | serr = (*stream->delta_handler)(NULL, stream->delta_baton); |
---|
2447 | if (serr) |
---|
2448 | return dav_svn__convert_err |
---|
2449 | (serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2450 | "mod_dav_svn close_stream: error sending final (null) delta window", |
---|
2451 | pool); |
---|
2452 | } |
---|
2453 | |
---|
2454 | return NULL; |
---|
2455 | } |
---|
2456 | |
---|
2457 | |
---|
2458 | static dav_error * |
---|
2459 | write_stream(dav_stream *stream, const void *buf, apr_size_t bufsize) |
---|
2460 | { |
---|
2461 | svn_error_t *serr; |
---|
2462 | apr_pool_t *pool = stream->res->pool; |
---|
2463 | |
---|
2464 | if (stream->wstream != NULL) |
---|
2465 | { |
---|
2466 | serr = svn_stream_write(stream->wstream, buf, &bufsize); |
---|
2467 | /* ### would the returned bufsize ever not match the requested amt? */ |
---|
2468 | } |
---|
2469 | else |
---|
2470 | { |
---|
2471 | svn_txdelta_window_t window = { 0 }; |
---|
2472 | svn_txdelta_op_t op; |
---|
2473 | svn_string_t data; |
---|
2474 | |
---|
2475 | data.data = buf; |
---|
2476 | data.len = bufsize; |
---|
2477 | |
---|
2478 | op.action_code = svn_txdelta_new; |
---|
2479 | op.offset = 0; |
---|
2480 | op.length = bufsize; |
---|
2481 | |
---|
2482 | window.tview_len = bufsize; /* result will be this long */ |
---|
2483 | window.num_ops = 1; |
---|
2484 | window.ops = &op; |
---|
2485 | window.new_data = &data; |
---|
2486 | |
---|
2487 | serr = (*stream->delta_handler)(&window, stream->delta_baton); |
---|
2488 | } |
---|
2489 | |
---|
2490 | if (serr) |
---|
2491 | { |
---|
2492 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2493 | "could not write the file contents", |
---|
2494 | pool); |
---|
2495 | } |
---|
2496 | return NULL; |
---|
2497 | } |
---|
2498 | |
---|
2499 | |
---|
2500 | static dav_error * |
---|
2501 | seek_stream(dav_stream *stream, apr_off_t abs_position) |
---|
2502 | { |
---|
2503 | /* ### fill this in */ |
---|
2504 | |
---|
2505 | return dav_new_error(stream->res->pool, HTTP_NOT_IMPLEMENTED, 0, |
---|
2506 | "Resource body read/write cannot use ranges " |
---|
2507 | "(at this time)"); |
---|
2508 | } |
---|
2509 | |
---|
2510 | /* Returns whether the DAV resource lacks potential for generation of |
---|
2511 | an ETag (defined as any of the following): |
---|
2512 | - it doesn't exist |
---|
2513 | - the resource type isn't REGULAR or VERSION |
---|
2514 | - the resource is a Baseline */ |
---|
2515 | #define RESOURCE_LACKS_ETAG_POTENTIAL(resource) \ |
---|
2516 | (!resource->exists \ |
---|
2517 | || (resource->type != DAV_RESOURCE_TYPE_REGULAR \ |
---|
2518 | && resource->type != DAV_RESOURCE_TYPE_VERSION) \ |
---|
2519 | || (resource->type == DAV_RESOURCE_TYPE_VERSION \ |
---|
2520 | && resource->baselined)) |
---|
2521 | |
---|
2522 | |
---|
2523 | /* Return the last modification time of RESOURCE, or -1 if the DAV |
---|
2524 | resource type is not handled, or if an error occurs. Temporary |
---|
2525 | allocations are made from RESOURCE->POOL. */ |
---|
2526 | static apr_time_t |
---|
2527 | get_last_modified(const dav_resource *resource) |
---|
2528 | { |
---|
2529 | apr_time_t last_modified; |
---|
2530 | svn_error_t *serr; |
---|
2531 | svn_revnum_t created_rev; |
---|
2532 | svn_string_t *date_time; |
---|
2533 | |
---|
2534 | if (RESOURCE_LACKS_ETAG_POTENTIAL(resource)) |
---|
2535 | return -1; |
---|
2536 | |
---|
2537 | if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root, |
---|
2538 | resource->info->repos_path, |
---|
2539 | resource->pool))) |
---|
2540 | { |
---|
2541 | svn_error_clear(serr); |
---|
2542 | return -1; |
---|
2543 | } |
---|
2544 | |
---|
2545 | if ((serr = svn_fs_revision_prop(&date_time, resource->info->repos->fs, |
---|
2546 | created_rev, "svn:date", resource->pool))) |
---|
2547 | { |
---|
2548 | svn_error_clear(serr); |
---|
2549 | return -1; |
---|
2550 | } |
---|
2551 | |
---|
2552 | if (date_time == NULL || date_time->data == NULL) |
---|
2553 | return -1; |
---|
2554 | |
---|
2555 | if ((serr = svn_time_from_cstring(&last_modified, date_time->data, |
---|
2556 | resource->pool))) |
---|
2557 | { |
---|
2558 | svn_error_clear(serr); |
---|
2559 | return -1; |
---|
2560 | } |
---|
2561 | |
---|
2562 | return last_modified; |
---|
2563 | } |
---|
2564 | |
---|
2565 | |
---|
2566 | const char * |
---|
2567 | dav_svn__getetag(const dav_resource *resource, apr_pool_t *pool) |
---|
2568 | { |
---|
2569 | svn_error_t *serr; |
---|
2570 | svn_revnum_t created_rev; |
---|
2571 | |
---|
2572 | if (RESOURCE_LACKS_ETAG_POTENTIAL(resource)) |
---|
2573 | return ""; |
---|
2574 | |
---|
2575 | /* ### what kind of etag to return for activities, etc.? */ |
---|
2576 | |
---|
2577 | if ((serr = svn_fs_node_created_rev(&created_rev, resource->info->root.root, |
---|
2578 | resource->info->repos_path, |
---|
2579 | pool))) |
---|
2580 | { |
---|
2581 | /* ### what to do? */ |
---|
2582 | svn_error_clear(serr); |
---|
2583 | return ""; |
---|
2584 | } |
---|
2585 | |
---|
2586 | /* Use the "weak" format of the etag for collections because our GET |
---|
2587 | requests on collections include dynamic data (the HEAD revision, |
---|
2588 | the build version of Subversion, etc.). */ |
---|
2589 | return apr_psprintf(pool, "%s\"%ld/%s\"", |
---|
2590 | resource->collection ? "W/" : "", |
---|
2591 | created_rev, |
---|
2592 | apr_xml_quote_string(pool, |
---|
2593 | resource->info->repos_path, 1)); |
---|
2594 | } |
---|
2595 | |
---|
2596 | |
---|
2597 | /* Since dav_svn__getetag() takes a pool argument, this wrapper is for |
---|
2598 | the mod_dav hooks vtable entry, which does not. */ |
---|
2599 | static const char * |
---|
2600 | getetag_pathetic(const dav_resource *resource) |
---|
2601 | { |
---|
2602 | return dav_svn__getetag(resource, resource->pool); |
---|
2603 | } |
---|
2604 | |
---|
2605 | |
---|
2606 | static dav_error * |
---|
2607 | set_headers(request_rec *r, const dav_resource *resource) |
---|
2608 | { |
---|
2609 | svn_error_t *serr; |
---|
2610 | svn_filesize_t length; |
---|
2611 | const char *mimetype = NULL; |
---|
2612 | apr_time_t last_modified; |
---|
2613 | |
---|
2614 | if (!resource->exists) |
---|
2615 | return NULL; |
---|
2616 | |
---|
2617 | last_modified = get_last_modified(resource); |
---|
2618 | if (last_modified != -1) |
---|
2619 | { |
---|
2620 | /* Note the modification time for the requested resource, and |
---|
2621 | include the Last-Modified header in the response. */ |
---|
2622 | ap_update_mtime(r, last_modified); |
---|
2623 | ap_set_last_modified(r); |
---|
2624 | } |
---|
2625 | |
---|
2626 | /* generate our etag and place it into the output */ |
---|
2627 | apr_table_setn(r->headers_out, "ETag", |
---|
2628 | dav_svn__getetag(resource, resource->pool)); |
---|
2629 | |
---|
2630 | #if 0 |
---|
2631 | /* As version resources don't change, encourage caching. */ |
---|
2632 | /* ### FIXME: This conditional is wrong -- type is often REGULAR, |
---|
2633 | ### and the resource doesn't seem to be baselined. */ |
---|
2634 | if (resource->type == DAV_RESOURCE_TYPE_VERSION) |
---|
2635 | /* Cache resource for one week (specified in seconds). */ |
---|
2636 | apr_table_setn(r->headers_out, "Cache-Control", "max-age=604800"); |
---|
2637 | #endif |
---|
2638 | |
---|
2639 | /* we accept byte-ranges */ |
---|
2640 | apr_table_setn(r->headers_out, "Accept-Ranges", "bytes"); |
---|
2641 | |
---|
2642 | /* For a directory, we will send text/html or text/xml. If we have a delta |
---|
2643 | base, then we will always be generating an svndiff. Otherwise, |
---|
2644 | we need to fetch the appropriate MIME type from the resource's |
---|
2645 | properties (and use text/plain if it isn't there). */ |
---|
2646 | if (resource->collection) |
---|
2647 | { |
---|
2648 | if (resource->info->repos->xslt_uri) |
---|
2649 | mimetype = "text/xml"; |
---|
2650 | else |
---|
2651 | mimetype = "text/html; charset=UTF-8"; |
---|
2652 | } |
---|
2653 | else if (resource->info->delta_base != NULL) |
---|
2654 | { |
---|
2655 | dav_svn__uri_info info; |
---|
2656 | |
---|
2657 | /* First order of business is to parse it. */ |
---|
2658 | serr = dav_svn__simple_parse_uri(&info, resource, |
---|
2659 | resource->info->delta_base, |
---|
2660 | resource->pool); |
---|
2661 | |
---|
2662 | /* If we successfully parse the base URL, then send an svndiff. */ |
---|
2663 | if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM)) |
---|
2664 | { |
---|
2665 | mimetype = SVN_SVNDIFF_MIME_TYPE; |
---|
2666 | } |
---|
2667 | svn_error_clear(serr); |
---|
2668 | } |
---|
2669 | |
---|
2670 | if ((mimetype == NULL) |
---|
2671 | && ((resource->type == DAV_RESOURCE_TYPE_VERSION) |
---|
2672 | || (resource->type == DAV_RESOURCE_TYPE_REGULAR)) |
---|
2673 | && (resource->info->repos_path != NULL)) |
---|
2674 | { |
---|
2675 | svn_string_t *value; |
---|
2676 | |
---|
2677 | serr = svn_fs_node_prop(&value, |
---|
2678 | resource->info->root.root, |
---|
2679 | resource->info->repos_path, |
---|
2680 | SVN_PROP_MIME_TYPE, |
---|
2681 | resource->pool); |
---|
2682 | if (serr != NULL) |
---|
2683 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2684 | "could not fetch the resource's MIME type", |
---|
2685 | resource->pool); |
---|
2686 | |
---|
2687 | if (value) |
---|
2688 | mimetype = value->data; |
---|
2689 | else if ((! resource->info->repos->is_svn_client) |
---|
2690 | && r->content_type) |
---|
2691 | mimetype = r->content_type; |
---|
2692 | else |
---|
2693 | mimetype = ap_default_type(r); |
---|
2694 | |
---|
2695 | serr = svn_mime_type_validate(mimetype, resource->pool); |
---|
2696 | if (serr) |
---|
2697 | { |
---|
2698 | /* Probably serr->apr == SVN_ERR_BAD_MIME_TYPE, but |
---|
2699 | there's no point even checking. No matter what the |
---|
2700 | error is, we can't derive the mime type from the |
---|
2701 | svn:mime-type property. So we resort to the infamous |
---|
2702 | "mime type of last resort." */ |
---|
2703 | svn_error_clear(serr); |
---|
2704 | mimetype = "application/octet-stream"; |
---|
2705 | } |
---|
2706 | |
---|
2707 | /* if we aren't sending a diff, then we know the length of the file, |
---|
2708 | so set up the Content-Length header */ |
---|
2709 | serr = svn_fs_file_length(&length, |
---|
2710 | resource->info->root.root, |
---|
2711 | resource->info->repos_path, |
---|
2712 | resource->pool); |
---|
2713 | if (serr != NULL) |
---|
2714 | { |
---|
2715 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2716 | "could not fetch the resource length", |
---|
2717 | resource->pool); |
---|
2718 | } |
---|
2719 | ap_set_content_length(r, (apr_off_t) length); |
---|
2720 | } |
---|
2721 | |
---|
2722 | /* set the discovered MIME type */ |
---|
2723 | /* ### it would be best to do this during the findct phase... */ |
---|
2724 | ap_set_content_type(r, mimetype); |
---|
2725 | |
---|
2726 | return NULL; |
---|
2727 | } |
---|
2728 | |
---|
2729 | |
---|
2730 | typedef struct { |
---|
2731 | ap_filter_t *output; |
---|
2732 | apr_pool_t *pool; |
---|
2733 | } diff_ctx_t; |
---|
2734 | |
---|
2735 | |
---|
2736 | static svn_error_t * |
---|
2737 | write_to_filter(void *baton, const char *buffer, apr_size_t *len) |
---|
2738 | { |
---|
2739 | diff_ctx_t *dc = baton; |
---|
2740 | apr_bucket_brigade *bb; |
---|
2741 | apr_bucket *bkt; |
---|
2742 | apr_status_t status; |
---|
2743 | |
---|
2744 | /* take the current data and shove it into the filter */ |
---|
2745 | bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc); |
---|
2746 | bkt = apr_bucket_transient_create(buffer, *len, dc->output->c->bucket_alloc); |
---|
2747 | APR_BRIGADE_INSERT_TAIL(bb, bkt); |
---|
2748 | if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS) { |
---|
2749 | return svn_error_create(status, NULL, |
---|
2750 | "Could not write data to filter"); |
---|
2751 | } |
---|
2752 | |
---|
2753 | return SVN_NO_ERROR; |
---|
2754 | } |
---|
2755 | |
---|
2756 | |
---|
2757 | static svn_error_t * |
---|
2758 | close_filter(void *baton) |
---|
2759 | { |
---|
2760 | diff_ctx_t *dc = baton; |
---|
2761 | apr_bucket_brigade *bb; |
---|
2762 | apr_bucket *bkt; |
---|
2763 | apr_status_t status; |
---|
2764 | |
---|
2765 | /* done with the file. write an EOS bucket now. */ |
---|
2766 | bb = apr_brigade_create(dc->pool, dc->output->c->bucket_alloc); |
---|
2767 | bkt = apr_bucket_eos_create(dc->output->c->bucket_alloc); |
---|
2768 | APR_BRIGADE_INSERT_TAIL(bb, bkt); |
---|
2769 | if ((status = ap_pass_brigade(dc->output, bb)) != APR_SUCCESS) |
---|
2770 | return svn_error_create(status, NULL, "Could not write EOS to filter"); |
---|
2771 | |
---|
2772 | return SVN_NO_ERROR; |
---|
2773 | } |
---|
2774 | |
---|
2775 | |
---|
2776 | static dav_error * |
---|
2777 | deliver(const dav_resource *resource, ap_filter_t *output) |
---|
2778 | { |
---|
2779 | svn_error_t *serr; |
---|
2780 | apr_bucket_brigade *bb; |
---|
2781 | apr_bucket *bkt; |
---|
2782 | apr_status_t status; |
---|
2783 | |
---|
2784 | /* Check resource type */ |
---|
2785 | if (resource->type != DAV_RESOURCE_TYPE_REGULAR |
---|
2786 | && resource->type != DAV_RESOURCE_TYPE_VERSION |
---|
2787 | && resource->type != DAV_RESOURCE_TYPE_WORKING |
---|
2788 | && resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION) |
---|
2789 | { |
---|
2790 | return dav_new_error(resource->pool, HTTP_CONFLICT, 0, |
---|
2791 | "Cannot GET this type of resource."); |
---|
2792 | } |
---|
2793 | |
---|
2794 | if (resource->collection) |
---|
2795 | { |
---|
2796 | const int gen_html = !resource->info->repos->xslt_uri; |
---|
2797 | apr_hash_t *entries; |
---|
2798 | apr_pool_t *entry_pool; |
---|
2799 | apr_array_header_t *sorted; |
---|
2800 | int i; |
---|
2801 | |
---|
2802 | /* XML schema for the directory index if xslt_uri is set: |
---|
2803 | |
---|
2804 | <?xml version="1.0"?> |
---|
2805 | <?xml-stylesheet type="text/xsl" href="[info->repos->xslt_uri]"?> */ |
---|
2806 | static const char xml_index_dtd[] = |
---|
2807 | "<!DOCTYPE svn [\n" |
---|
2808 | " <!ELEMENT svn (index)>\n" |
---|
2809 | " <!ATTLIST svn version CDATA #REQUIRED\n" |
---|
2810 | " href CDATA #REQUIRED>\n" |
---|
2811 | " <!ELEMENT index (updir?, (file | dir)*)>\n" |
---|
2812 | " <!ATTLIST index name CDATA #IMPLIED\n" |
---|
2813 | " path CDATA #IMPLIED\n" |
---|
2814 | " rev CDATA #IMPLIED\n" |
---|
2815 | " base CDATA #IMPLIED>\n" |
---|
2816 | " <!ELEMENT updir EMPTY>\n" |
---|
2817 | " <!ELEMENT file EMPTY>\n" |
---|
2818 | " <!ATTLIST file name CDATA #REQUIRED\n" |
---|
2819 | " href CDATA #REQUIRED>\n" |
---|
2820 | " <!ELEMENT dir EMPTY>\n" |
---|
2821 | " <!ATTLIST dir name CDATA #REQUIRED\n" |
---|
2822 | " href CDATA #REQUIRED>\n" |
---|
2823 | "]>\n"; |
---|
2824 | |
---|
2825 | /* <svn version="1.3.0 (dev-build)" |
---|
2826 | href="http://subversion.tigris.org"> |
---|
2827 | <index name="[info->repos->repo_name]" |
---|
2828 | path="[info->repos_path]" |
---|
2829 | rev="[info->root.rev]"> |
---|
2830 | <file name="foo" href="foo" /> |
---|
2831 | <dir name="bar" href="bar/" /> |
---|
2832 | </index> |
---|
2833 | </svn> */ |
---|
2834 | |
---|
2835 | |
---|
2836 | /* ### TO-DO: check for a new mod_dav_svn directive here also. */ |
---|
2837 | if (resource->info->restype == DAV_SVN_RESTYPE_PARENTPATH_COLLECTION) |
---|
2838 | { |
---|
2839 | apr_hash_index_t *hi; |
---|
2840 | apr_hash_t *dirents; |
---|
2841 | const char *fs_parent_path = |
---|
2842 | dav_svn__get_fs_parent_path(resource->info->r); |
---|
2843 | |
---|
2844 | serr = svn_io_get_dirents2(&dirents, fs_parent_path, resource->pool); |
---|
2845 | if (serr != NULL) |
---|
2846 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2847 | "couldn't fetch dirents of SVNParentPath", |
---|
2848 | resource->pool); |
---|
2849 | |
---|
2850 | /* convert an io dirent hash to an fs dirent hash. */ |
---|
2851 | entries = apr_hash_make(resource->pool); |
---|
2852 | for (hi = apr_hash_first(resource->pool, dirents); |
---|
2853 | hi; hi = apr_hash_next(hi)) |
---|
2854 | { |
---|
2855 | const void *key; |
---|
2856 | void *val; |
---|
2857 | svn_io_dirent_t *dirent; |
---|
2858 | svn_fs_dirent_t *ent = apr_pcalloc(resource->pool, sizeof(*ent)); |
---|
2859 | |
---|
2860 | apr_hash_this(hi, &key, NULL, &val); |
---|
2861 | dirent = val; |
---|
2862 | |
---|
2863 | if (dirent->kind != svn_node_dir) |
---|
2864 | continue; |
---|
2865 | |
---|
2866 | ent->name = key; |
---|
2867 | ent->id = NULL; /* ### does it matter? */ |
---|
2868 | ent->kind = dirent->kind; |
---|
2869 | |
---|
2870 | apr_hash_set(entries, key, APR_HASH_KEY_STRING, ent); |
---|
2871 | } |
---|
2872 | |
---|
2873 | } |
---|
2874 | else |
---|
2875 | { |
---|
2876 | serr = svn_fs_dir_entries(&entries, resource->info->root.root, |
---|
2877 | resource->info->repos_path, resource->pool); |
---|
2878 | if (serr != NULL) |
---|
2879 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
2880 | "could not fetch directory entries", |
---|
2881 | resource->pool); |
---|
2882 | } |
---|
2883 | |
---|
2884 | bb = apr_brigade_create(resource->pool, output->c->bucket_alloc); |
---|
2885 | |
---|
2886 | if (gen_html) |
---|
2887 | { |
---|
2888 | const char *title; |
---|
2889 | if (resource->info->repos_path == NULL) |
---|
2890 | title = "unknown location"; |
---|
2891 | else |
---|
2892 | title = resource->info->repos_path; |
---|
2893 | |
---|
2894 | if (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION) |
---|
2895 | { |
---|
2896 | if (SVN_IS_VALID_REVNUM(resource->info->root.rev)) |
---|
2897 | title = apr_psprintf(resource->pool, |
---|
2898 | "Revision %ld: %s", |
---|
2899 | resource->info->root.rev, title); |
---|
2900 | if (resource->info->repos->repo_basename) |
---|
2901 | title = apr_psprintf(resource->pool, "%s - %s", |
---|
2902 | resource->info->repos->repo_basename, |
---|
2903 | title); |
---|
2904 | if (resource->info->repos->repo_name) |
---|
2905 | title = apr_psprintf(resource->pool, "%s: %s", |
---|
2906 | resource->info->repos->repo_name, |
---|
2907 | title); |
---|
2908 | } |
---|
2909 | |
---|
2910 | ap_fprintf(output, bb, "<html><head><title>%s</title></head>\n" |
---|
2911 | "<body>\n <h2>%s</h2>\n <ul>\n", title, title); |
---|
2912 | } |
---|
2913 | else |
---|
2914 | { |
---|
2915 | const char *name = resource->info->repos->repo_name; |
---|
2916 | const char *href = resource->info->repos_path; |
---|
2917 | const char *base = resource->info->repos->repo_basename; |
---|
2918 | |
---|
2919 | ap_fputs(output, bb, "<?xml version=\"1.0\"?>\n"); |
---|
2920 | ap_fprintf(output, bb, |
---|
2921 | "<?xml-stylesheet type=\"text/xsl\" href=\"%s\"?>\n", |
---|
2922 | resource->info->repos->xslt_uri); |
---|
2923 | ap_fputs(output, bb, xml_index_dtd); |
---|
2924 | ap_fputs(output, bb, |
---|
2925 | "<svn version=\"" SVN_VERSION "\"\n" |
---|
2926 | " href=\"http://subversion.tigris.org/\">\n"); |
---|
2927 | ap_fputs(output, bb, " <index"); |
---|
2928 | if (name) |
---|
2929 | ap_fprintf(output, bb, " name=\"%s\"", |
---|
2930 | apr_xml_quote_string(resource->pool, name, 1)); |
---|
2931 | if (SVN_IS_VALID_REVNUM(resource->info->root.rev)) |
---|
2932 | ap_fprintf(output, bb, " rev=\"%ld\"", |
---|
2933 | resource->info->root.rev); |
---|
2934 | if (href) |
---|
2935 | ap_fprintf(output, bb, " path=\"%s\"", |
---|
2936 | apr_xml_quote_string(resource->pool, |
---|
2937 | href, |
---|
2938 | 1)); |
---|
2939 | if (base) |
---|
2940 | ap_fprintf(output, bb, " base=\"%s\"", base); |
---|
2941 | |
---|
2942 | ap_fputs(output, bb, ">\n"); |
---|
2943 | } |
---|
2944 | |
---|
2945 | if ((resource->info->repos_path && resource->info->repos_path[1] != '\0') |
---|
2946 | && (resource->info->restype != DAV_SVN_RESTYPE_PARENTPATH_COLLECTION)) |
---|
2947 | { |
---|
2948 | if (gen_html) |
---|
2949 | ap_fprintf(output, bb, " <li><a href=\"../\">..</a></li>\n"); |
---|
2950 | else |
---|
2951 | ap_fprintf(output, bb, " <updir />\n"); |
---|
2952 | } |
---|
2953 | |
---|
2954 | /* get a sorted list of the entries */ |
---|
2955 | sorted = svn_sort__hash(entries, svn_sort_compare_items_as_paths, |
---|
2956 | resource->pool); |
---|
2957 | |
---|
2958 | entry_pool = svn_pool_create(resource->pool); |
---|
2959 | |
---|
2960 | for (i = 0; i < sorted->nelts; ++i) |
---|
2961 | { |
---|
2962 | const svn_sort__item_t *item = &APR_ARRAY_IDX(sorted, i, |
---|
2963 | const svn_sort__item_t); |
---|
2964 | const svn_fs_dirent_t *entry = item->value; |
---|
2965 | const char *name = item->key; |
---|
2966 | const char *href = name; |
---|
2967 | svn_boolean_t is_dir = (entry->kind == svn_node_dir); |
---|
2968 | |
---|
2969 | svn_pool_clear(entry_pool); |
---|
2970 | |
---|
2971 | /* append a trailing slash onto the name for directories. we NEED |
---|
2972 | this for the href portion so that the relative reference will |
---|
2973 | descend properly. for the visible portion, it is just nice. */ |
---|
2974 | /* ### The xml output doesn't like to see a trailing slash on |
---|
2975 | ### the visible portion, so avoid that. */ |
---|
2976 | if (is_dir) |
---|
2977 | href = apr_pstrcat(entry_pool, href, "/", NULL); |
---|
2978 | |
---|
2979 | if (gen_html) |
---|
2980 | name = href; |
---|
2981 | |
---|
2982 | /* We quote special characters in both XML and HTML. */ |
---|
2983 | name = apr_xml_quote_string(entry_pool, name, !gen_html); |
---|
2984 | |
---|
2985 | /* According to httpd-2.0.54/include/httpd.h, ap_os_escape_path() |
---|
2986 | behaves differently on different platforms. It claims to |
---|
2987 | "convert an OS path to a URL in an OS dependant way". |
---|
2988 | Nevertheless, there appears to be only one implementation |
---|
2989 | of the function in httpd, and the code seems completely |
---|
2990 | platform independent, so we'll assume it's appropriate for |
---|
2991 | mod_dav_svn to use it to quote outbound paths. */ |
---|
2992 | href = ap_os_escape_path(entry_pool, href, 0); |
---|
2993 | href = apr_xml_quote_string(entry_pool, href, 1); |
---|
2994 | |
---|
2995 | if (gen_html) |
---|
2996 | { |
---|
2997 | ap_fprintf(output, bb, |
---|
2998 | " <li><a href=\"%s\">%s</a></li>\n", |
---|
2999 | href, name); |
---|
3000 | } |
---|
3001 | else |
---|
3002 | { |
---|
3003 | const char *const tag = (is_dir ? "dir" : "file"); |
---|
3004 | |
---|
3005 | /* This is where we could search for props */ |
---|
3006 | |
---|
3007 | ap_fprintf(output, bb, |
---|
3008 | " <%s name=\"%s\" href=\"%s\" />\n", |
---|
3009 | tag, name, href); |
---|
3010 | } |
---|
3011 | } |
---|
3012 | |
---|
3013 | svn_pool_destroy(entry_pool); |
---|
3014 | |
---|
3015 | if (gen_html) |
---|
3016 | ap_fputs(output, bb, |
---|
3017 | " </ul>\n <hr noshade><em>Powered by " |
---|
3018 | "<a href=\"http://subversion.tigris.org/\">Subversion</a> " |
---|
3019 | "version " SVN_VERSION "." |
---|
3020 | "</em>\n</body></html>"); |
---|
3021 | else |
---|
3022 | ap_fputs(output, bb, " </index>\n</svn>\n"); |
---|
3023 | |
---|
3024 | bkt = apr_bucket_eos_create(output->c->bucket_alloc); |
---|
3025 | APR_BRIGADE_INSERT_TAIL(bb, bkt); |
---|
3026 | if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) |
---|
3027 | return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, |
---|
3028 | "Could not write EOS to filter."); |
---|
3029 | |
---|
3030 | return NULL; |
---|
3031 | } |
---|
3032 | |
---|
3033 | |
---|
3034 | /* If we have a base for a delta, then we want to compute an svndiff |
---|
3035 | between the provided base and the requested resource. For a simple |
---|
3036 | request, then we just grab the file contents. */ |
---|
3037 | if (resource->info->delta_base != NULL) |
---|
3038 | { |
---|
3039 | dav_svn__uri_info info; |
---|
3040 | svn_fs_root_t *root; |
---|
3041 | svn_boolean_t is_file; |
---|
3042 | svn_txdelta_stream_t *txd_stream; |
---|
3043 | svn_stream_t *o_stream; |
---|
3044 | svn_txdelta_window_handler_t handler; |
---|
3045 | void * h_baton; |
---|
3046 | diff_ctx_t dc = { 0 }; |
---|
3047 | |
---|
3048 | /* First order of business is to parse it. */ |
---|
3049 | serr = dav_svn__simple_parse_uri(&info, resource, |
---|
3050 | resource->info->delta_base, |
---|
3051 | resource->pool); |
---|
3052 | |
---|
3053 | /* If we successfully parse the base URL, then send an svndiff. */ |
---|
3054 | if ((serr == NULL) && (info.rev != SVN_INVALID_REVNUM)) |
---|
3055 | { |
---|
3056 | /* We are always accessing the base resource by ID, so open |
---|
3057 | an ID root. */ |
---|
3058 | serr = svn_fs_revision_root(&root, resource->info->repos->fs, |
---|
3059 | info.rev, resource->pool); |
---|
3060 | if (serr != NULL) |
---|
3061 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3062 | "could not open a root for the base", |
---|
3063 | resource->pool); |
---|
3064 | |
---|
3065 | /* verify that it is a file */ |
---|
3066 | serr = svn_fs_is_file(&is_file, root, info.repos_path, |
---|
3067 | resource->pool); |
---|
3068 | if (serr != NULL) |
---|
3069 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3070 | "could not determine if the base " |
---|
3071 | "is really a file", |
---|
3072 | resource->pool); |
---|
3073 | if (!is_file) |
---|
3074 | return dav_new_error(resource->pool, HTTP_BAD_REQUEST, 0, |
---|
3075 | "the delta base does not refer to a file"); |
---|
3076 | |
---|
3077 | /* Okay. Let's open up a delta stream for the client to read. */ |
---|
3078 | serr = svn_fs_get_file_delta_stream(&txd_stream, |
---|
3079 | root, info.repos_path, |
---|
3080 | resource->info->root.root, |
---|
3081 | resource->info->repos_path, |
---|
3082 | resource->pool); |
---|
3083 | if (serr != NULL) |
---|
3084 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3085 | "could not prepare to read a delta", |
---|
3086 | resource->pool); |
---|
3087 | |
---|
3088 | /* create a stream that svndiff data will be written to, |
---|
3089 | which will copy it to the network */ |
---|
3090 | dc.output = output; |
---|
3091 | dc.pool = resource->pool; |
---|
3092 | o_stream = svn_stream_create(&dc, resource->pool); |
---|
3093 | svn_stream_set_write(o_stream, write_to_filter); |
---|
3094 | svn_stream_set_close(o_stream, close_filter); |
---|
3095 | |
---|
3096 | /* get a handler/baton for writing into the output stream */ |
---|
3097 | svn_txdelta_to_svndiff2(&handler, &h_baton, |
---|
3098 | o_stream, resource->info->svndiff_version, |
---|
3099 | resource->pool); |
---|
3100 | |
---|
3101 | /* got everything set up. read in delta windows and shove them into |
---|
3102 | the handler, which pushes data into the output stream, which goes |
---|
3103 | to the network. */ |
---|
3104 | serr = svn_txdelta_send_txstream(txd_stream, handler, h_baton, |
---|
3105 | resource->pool); |
---|
3106 | if (serr != NULL) |
---|
3107 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3108 | "could not deliver the txdelta stream", |
---|
3109 | resource->pool); |
---|
3110 | |
---|
3111 | |
---|
3112 | return NULL; |
---|
3113 | } |
---|
3114 | else |
---|
3115 | { |
---|
3116 | svn_error_clear(serr); |
---|
3117 | } |
---|
3118 | } |
---|
3119 | |
---|
3120 | /* resource->info->delta_base is NULL, or we had an invalid base URL */ |
---|
3121 | { |
---|
3122 | svn_stream_t *stream; |
---|
3123 | char *block; |
---|
3124 | |
---|
3125 | serr = svn_fs_file_contents(&stream, |
---|
3126 | resource->info->root.root, |
---|
3127 | resource->info->repos_path, |
---|
3128 | resource->pool); |
---|
3129 | if (serr != NULL) |
---|
3130 | { |
---|
3131 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3132 | "could not prepare to read the file", |
---|
3133 | resource->pool); |
---|
3134 | } |
---|
3135 | |
---|
3136 | /* ### one day in the future, we can create a custom bucket type |
---|
3137 | ### which will read from the FS stream on demand */ |
---|
3138 | |
---|
3139 | block = apr_palloc(resource->pool, SVN__STREAM_CHUNK_SIZE); |
---|
3140 | while (1) { |
---|
3141 | apr_size_t bufsize = SVN__STREAM_CHUNK_SIZE; |
---|
3142 | |
---|
3143 | /* read from the FS ... */ |
---|
3144 | serr = svn_stream_read(stream, block, &bufsize); |
---|
3145 | if (serr != NULL) |
---|
3146 | { |
---|
3147 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3148 | "could not read the file contents", |
---|
3149 | resource->pool); |
---|
3150 | } |
---|
3151 | if (bufsize == 0) |
---|
3152 | break; |
---|
3153 | |
---|
3154 | /* build a brigade and write to the filter ... */ |
---|
3155 | bb = apr_brigade_create(resource->pool, output->c->bucket_alloc); |
---|
3156 | bkt = apr_bucket_transient_create(block, bufsize, |
---|
3157 | output->c->bucket_alloc); |
---|
3158 | APR_BRIGADE_INSERT_TAIL(bb, bkt); |
---|
3159 | if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) { |
---|
3160 | /* ### what to do with status; and that HTTP code... */ |
---|
3161 | return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, |
---|
3162 | "Could not write data to filter."); |
---|
3163 | } |
---|
3164 | } |
---|
3165 | |
---|
3166 | /* done with the file. write an EOS bucket now. */ |
---|
3167 | bb = apr_brigade_create(resource->pool, output->c->bucket_alloc); |
---|
3168 | bkt = apr_bucket_eos_create(output->c->bucket_alloc); |
---|
3169 | APR_BRIGADE_INSERT_TAIL(bb, bkt); |
---|
3170 | if ((status = ap_pass_brigade(output, bb)) != APR_SUCCESS) { |
---|
3171 | /* ### what to do with status; and that HTTP code... */ |
---|
3172 | return dav_new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, |
---|
3173 | "Could not write EOS to filter."); |
---|
3174 | } |
---|
3175 | |
---|
3176 | return NULL; |
---|
3177 | } |
---|
3178 | } |
---|
3179 | |
---|
3180 | |
---|
3181 | static dav_error * |
---|
3182 | create_collection(dav_resource *resource) |
---|
3183 | { |
---|
3184 | svn_error_t *serr; |
---|
3185 | dav_error *err; |
---|
3186 | |
---|
3187 | if (resource->type != DAV_RESOURCE_TYPE_WORKING |
---|
3188 | && resource->type != DAV_RESOURCE_TYPE_REGULAR) |
---|
3189 | { |
---|
3190 | return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0, |
---|
3191 | "Collections can only be created within a working " |
---|
3192 | "or regular collection [at this time]."); |
---|
3193 | } |
---|
3194 | |
---|
3195 | /* ...regular resources allowed only if autoversioning is turned on. */ |
---|
3196 | if (resource->type == DAV_RESOURCE_TYPE_REGULAR |
---|
3197 | && ! (resource->info->repos->autoversioning)) |
---|
3198 | return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0, |
---|
3199 | "MKCOL called on regular resource, but " |
---|
3200 | "autoversioning is not active."); |
---|
3201 | |
---|
3202 | /* ### note that the parent was checked out at some point, and this |
---|
3203 | ### is being preformed relative to the working rsrc for that parent */ |
---|
3204 | |
---|
3205 | /* Auto-versioning mkcol of regular resource: */ |
---|
3206 | if (resource->type == DAV_RESOURCE_TYPE_REGULAR) |
---|
3207 | { |
---|
3208 | /* Change the VCR into a WR, in place. This creates a txn and |
---|
3209 | changes resource->info->root from a rev-root into a txn-root. */ |
---|
3210 | err = dav_svn__checkout(resource, |
---|
3211 | 1 /* auto-checkout */, |
---|
3212 | 0, 0, 0, NULL, NULL); |
---|
3213 | if (err) |
---|
3214 | return err; |
---|
3215 | } |
---|
3216 | |
---|
3217 | if ((serr = svn_fs_make_dir(resource->info->root.root, |
---|
3218 | resource->info->repos_path, |
---|
3219 | resource->pool)) != NULL) |
---|
3220 | { |
---|
3221 | /* ### need a better error */ |
---|
3222 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3223 | "Could not create the collection.", |
---|
3224 | resource->pool); |
---|
3225 | } |
---|
3226 | |
---|
3227 | /* Auto-versioning commit of the txn. */ |
---|
3228 | if (resource->info->auto_checked_out) |
---|
3229 | { |
---|
3230 | /* This also changes the WR back into a VCR, in place. */ |
---|
3231 | err = dav_svn__checkin(resource, 0, NULL); |
---|
3232 | if (err) |
---|
3233 | return err; |
---|
3234 | } |
---|
3235 | |
---|
3236 | return NULL; |
---|
3237 | } |
---|
3238 | |
---|
3239 | |
---|
3240 | static dav_error * |
---|
3241 | copy_resource(const dav_resource *src, |
---|
3242 | dav_resource *dst, |
---|
3243 | int depth, |
---|
3244 | dav_response **response) |
---|
3245 | { |
---|
3246 | svn_error_t *serr; |
---|
3247 | dav_error *err; |
---|
3248 | const char *src_repos_path, *dst_repos_path; |
---|
3249 | |
---|
3250 | /* ### source must be from a collection under baseline control. the |
---|
3251 | ### baseline will (implicitly) indicate the source revision, and the |
---|
3252 | ### path will be derived simply from the URL path */ |
---|
3253 | |
---|
3254 | /* ### the destination's parent must be a working collection */ |
---|
3255 | |
---|
3256 | /* ### ben goofing around: */ |
---|
3257 | /* char *msg; |
---|
3258 | apr_psprintf |
---|
3259 | (src->pool, "Got a COPY request with src arg '%s' and dst arg '%s'", |
---|
3260 | src->uri, dst->uri); |
---|
3261 | |
---|
3262 | return dav_new_error(src->pool, HTTP_NOT_IMPLEMENTED, 0, msg); |
---|
3263 | */ |
---|
3264 | |
---|
3265 | /* ### Safeguard: see issue #916, whereby we're allowing an |
---|
3266 | auto-checkout of a baseline for PROPPATCHing, *without* creating |
---|
3267 | a new baseline afterwards. We need to safeguard here that nobody |
---|
3268 | is calling COPY with the baseline as a Destination! */ |
---|
3269 | if (dst->baselined && dst->type == DAV_RESOURCE_TYPE_VERSION) |
---|
3270 | return dav_new_error(src->pool, HTTP_PRECONDITION_FAILED, 0, |
---|
3271 | "Illegal: COPY Destination is a baseline."); |
---|
3272 | |
---|
3273 | if (dst->type == DAV_RESOURCE_TYPE_REGULAR |
---|
3274 | && !(dst->info->repos->autoversioning)) |
---|
3275 | return dav_new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0, |
---|
3276 | "COPY called on regular resource, but " |
---|
3277 | "autoversioning is not active."); |
---|
3278 | |
---|
3279 | /* Auto-versioning copy of regular resource: */ |
---|
3280 | if (dst->type == DAV_RESOURCE_TYPE_REGULAR) |
---|
3281 | { |
---|
3282 | /* Change the VCR into a WR, in place. This creates a txn and |
---|
3283 | changes dst->info->root from a rev-root into a txn-root. */ |
---|
3284 | err = dav_svn__checkout(dst, |
---|
3285 | 1 /* auto-checkout */, |
---|
3286 | 0, 0, 0, NULL, NULL); |
---|
3287 | if (err) |
---|
3288 | return err; |
---|
3289 | } |
---|
3290 | |
---|
3291 | serr = svn_path_get_absolute(&src_repos_path, |
---|
3292 | svn_repos_path(src->info->repos->repos, |
---|
3293 | src->pool), |
---|
3294 | src->pool); |
---|
3295 | if (!serr) |
---|
3296 | serr = svn_path_get_absolute(&dst_repos_path, |
---|
3297 | svn_repos_path(dst->info->repos->repos, |
---|
3298 | dst->pool), |
---|
3299 | dst->pool); |
---|
3300 | |
---|
3301 | if (!serr) |
---|
3302 | { |
---|
3303 | if (strcmp(src_repos_path, dst_repos_path) != 0) |
---|
3304 | return dav_svn__new_error_tag |
---|
3305 | (dst->pool, HTTP_INTERNAL_SERVER_ERROR, 0, |
---|
3306 | "Copy source and destination are in different repositories.", |
---|
3307 | SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG); |
---|
3308 | serr = svn_fs_copy(src->info->root.root, /* root object of src rev*/ |
---|
3309 | src->info->repos_path, /* relative path of src */ |
---|
3310 | dst->info->root.root, /* root object of dst txn*/ |
---|
3311 | dst->info->repos_path, /* relative path of dst */ |
---|
3312 | src->pool); |
---|
3313 | } |
---|
3314 | if (serr) |
---|
3315 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3316 | "Unable to make a filesystem copy.", |
---|
3317 | dst->pool); |
---|
3318 | |
---|
3319 | /* Auto-versioning commit of the txn. */ |
---|
3320 | if (dst->info->auto_checked_out) |
---|
3321 | { |
---|
3322 | /* This also changes the WR back into a VCR, in place. */ |
---|
3323 | err = dav_svn__checkin(dst, 0, NULL); |
---|
3324 | if (err) |
---|
3325 | return err; |
---|
3326 | } |
---|
3327 | |
---|
3328 | return NULL; |
---|
3329 | } |
---|
3330 | |
---|
3331 | |
---|
3332 | static dav_error * |
---|
3333 | remove_resource(dav_resource *resource, dav_response **response) |
---|
3334 | { |
---|
3335 | svn_error_t *serr; |
---|
3336 | dav_error *err; |
---|
3337 | apr_hash_t *locks; |
---|
3338 | |
---|
3339 | /* Only activities, and working or regular resources can be deleted... */ |
---|
3340 | if (resource->type != DAV_RESOURCE_TYPE_WORKING |
---|
3341 | && resource->type != DAV_RESOURCE_TYPE_REGULAR |
---|
3342 | && resource->type != DAV_RESOURCE_TYPE_ACTIVITY) |
---|
3343 | return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0, |
---|
3344 | "DELETE called on invalid resource type."); |
---|
3345 | |
---|
3346 | /* ...and regular resources only if autoversioning is turned on. */ |
---|
3347 | if (resource->type == DAV_RESOURCE_TYPE_REGULAR |
---|
3348 | && ! (resource->info->repos->autoversioning)) |
---|
3349 | return dav_new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, 0, |
---|
3350 | "DELETE called on regular resource, but " |
---|
3351 | "autoversioning is not active."); |
---|
3352 | |
---|
3353 | /* Handle activity deletions (early exit). */ |
---|
3354 | if (resource->type == DAV_RESOURCE_TYPE_ACTIVITY) |
---|
3355 | { |
---|
3356 | return dav_svn__delete_activity(resource->info->repos, |
---|
3357 | resource->info->root.activity_id); |
---|
3358 | } |
---|
3359 | |
---|
3360 | /* ### note that the parent was checked out at some point, and this |
---|
3361 | ### is being preformed relative to the working rsrc for that parent */ |
---|
3362 | |
---|
3363 | /* NOTE: strictly speaking, we cannot determine whether the parent was |
---|
3364 | ever checked out, and that this working resource is relative to that |
---|
3365 | checked out parent. It is entirely possible the client checked out |
---|
3366 | the target resource and just deleted it. Subversion doesn't mind, but |
---|
3367 | this does imply we are not enforcing the "checkout the parent, then |
---|
3368 | delete from within" semantic. */ |
---|
3369 | |
---|
3370 | /* Auto-versioning delete of regular resource: */ |
---|
3371 | if (resource->type == DAV_RESOURCE_TYPE_REGULAR) |
---|
3372 | { |
---|
3373 | /* Change the VCR into a WR, in place. This creates a txn and |
---|
3374 | changes resource->info->root from a rev-root into a txn-root. */ |
---|
3375 | err = dav_svn__checkout(resource, |
---|
3376 | 1 /* auto-checkout */, |
---|
3377 | 0, 0, 0, NULL, NULL); |
---|
3378 | if (err) |
---|
3379 | return err; |
---|
3380 | } |
---|
3381 | |
---|
3382 | /* Sanity check: an svn client may have sent a custom request header |
---|
3383 | containing the revision of the item it thinks it's deleting. In |
---|
3384 | this case, we enforce the svn-specific semantic that the item |
---|
3385 | must be up-to-date. */ |
---|
3386 | if (SVN_IS_VALID_REVNUM(resource->info->version_name)) |
---|
3387 | { |
---|
3388 | svn_revnum_t created_rev; |
---|
3389 | serr = svn_fs_node_created_rev(&created_rev, |
---|
3390 | resource->info->root.root, |
---|
3391 | resource->info->repos_path, |
---|
3392 | resource->pool); |
---|
3393 | if (serr) |
---|
3394 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3395 | "Could not get created rev of resource", |
---|
3396 | resource->pool); |
---|
3397 | |
---|
3398 | if (resource->info->version_name < created_rev) |
---|
3399 | { |
---|
3400 | serr = svn_error_createf(SVN_ERR_RA_OUT_OF_DATE, NULL, |
---|
3401 | "Item '%s' is out of date", |
---|
3402 | resource->info->repos_path); |
---|
3403 | return dav_svn__convert_err(serr, HTTP_CONFLICT, |
---|
3404 | "Can't DELETE out-of-date resource", |
---|
3405 | resource->pool); |
---|
3406 | } |
---|
3407 | } |
---|
3408 | |
---|
3409 | /* Before attempting the filesystem delete, we need to push any |
---|
3410 | incoming lock-tokens into the filesystem's access_t. Normally |
---|
3411 | they come in via 'If:' header, and get_resource() |
---|
3412 | automatically notices them and does this work for us. In the |
---|
3413 | case of a directory deletion, however, svn clients are sending |
---|
3414 | 'child' lock-tokens in the DELETE request body. */ |
---|
3415 | |
---|
3416 | err = dav_svn__build_lock_hash(&locks, resource->info->r, |
---|
3417 | resource->info->repos_path, resource->pool); |
---|
3418 | if (err != NULL) |
---|
3419 | return err; |
---|
3420 | |
---|
3421 | if (apr_hash_count(locks)) |
---|
3422 | { |
---|
3423 | err = dav_svn__push_locks(resource, locks, resource->pool); |
---|
3424 | if (err != NULL) |
---|
3425 | return err; |
---|
3426 | } |
---|
3427 | |
---|
3428 | if ((serr = svn_fs_delete(resource->info->root.root, |
---|
3429 | resource->info->repos_path, |
---|
3430 | resource->pool)) != NULL) |
---|
3431 | { |
---|
3432 | /* ### need a better error */ |
---|
3433 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3434 | "Could not delete the resource", |
---|
3435 | resource->pool); |
---|
3436 | } |
---|
3437 | |
---|
3438 | /* Auto-versioning commit of the txn. */ |
---|
3439 | if (resource->info->auto_checked_out) |
---|
3440 | { |
---|
3441 | /* This also changes the WR back into a VCR, in place. */ |
---|
3442 | err = dav_svn__checkin(resource, 0, NULL); |
---|
3443 | if (err) |
---|
3444 | return err; |
---|
3445 | } |
---|
3446 | |
---|
3447 | return NULL; |
---|
3448 | } |
---|
3449 | |
---|
3450 | |
---|
3451 | static dav_error * |
---|
3452 | move_resource(dav_resource *src, |
---|
3453 | dav_resource *dst, |
---|
3454 | dav_response **response) |
---|
3455 | { |
---|
3456 | svn_error_t *serr; |
---|
3457 | dav_error *err; |
---|
3458 | |
---|
3459 | /* NOTE: The svn client does not call the MOVE method yet. Strictly |
---|
3460 | speaking, we do not need to implement this repository function. |
---|
3461 | But we do so anyway, so non-deltaV clients can work against the |
---|
3462 | repository when autoversioning is turned on. Like the svn client, |
---|
3463 | itself, we define a move to be a copy + delete within a single txn. */ |
---|
3464 | |
---|
3465 | /* Because we have no 'atomic' move, we only allow this method on |
---|
3466 | two regular resources with autoversioning active. That way we |
---|
3467 | can auto-checkout a single resource and do the copy + delete |
---|
3468 | within a single txn. (If we had two working resources, which txn |
---|
3469 | would we use?) */ |
---|
3470 | if (src->type != DAV_RESOURCE_TYPE_REGULAR |
---|
3471 | || dst->type != DAV_RESOURCE_TYPE_REGULAR |
---|
3472 | || !(src->info->repos->autoversioning)) |
---|
3473 | return dav_new_error(dst->pool, HTTP_METHOD_NOT_ALLOWED, 0, |
---|
3474 | "MOVE only allowed on two public URIs, and " |
---|
3475 | "autoversioning must be active."); |
---|
3476 | |
---|
3477 | /* Change the dst VCR into a WR, in place. This creates a txn and |
---|
3478 | changes dst->info->root from a rev-root into a txn-root. */ |
---|
3479 | err = dav_svn__checkout(dst, |
---|
3480 | 1 /* auto-checkout */, |
---|
3481 | 0, 0, 0, NULL, NULL); |
---|
3482 | if (err) |
---|
3483 | return err; |
---|
3484 | |
---|
3485 | /* Copy the src to the dst. */ |
---|
3486 | serr = svn_fs_copy(src->info->root.root, /* the root object of src rev*/ |
---|
3487 | src->info->repos_path, /* the relative path of src */ |
---|
3488 | dst->info->root.root, /* the root object of dst txn*/ |
---|
3489 | dst->info->repos_path, /* the relative path of dst */ |
---|
3490 | src->pool); |
---|
3491 | if (serr) |
---|
3492 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3493 | "Unable to make a filesystem copy.", |
---|
3494 | dst->pool); |
---|
3495 | |
---|
3496 | /* Notice: we're deleting the src repos path from the dst's txn_root. */ |
---|
3497 | if ((serr = svn_fs_delete(dst->info->root.root, |
---|
3498 | src->info->repos_path, |
---|
3499 | dst->pool)) != NULL) |
---|
3500 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3501 | "Could not delete the src resource.", |
---|
3502 | dst->pool); |
---|
3503 | |
---|
3504 | /* Commit: this also changes the WR back into a VCR, in place. */ |
---|
3505 | err = dav_svn__checkin(dst, 0, NULL); |
---|
3506 | if (err) |
---|
3507 | return err; |
---|
3508 | |
---|
3509 | return NULL; |
---|
3510 | } |
---|
3511 | |
---|
3512 | |
---|
3513 | typedef struct { |
---|
3514 | /* the input walk parameters */ |
---|
3515 | const dav_walk_params *params; |
---|
3516 | |
---|
3517 | /* reused as we walk */ |
---|
3518 | dav_walk_resource wres; |
---|
3519 | |
---|
3520 | /* the current resource */ |
---|
3521 | dav_resource res; /* wres.resource refers here */ |
---|
3522 | dav_resource_private info; /* the info in res */ |
---|
3523 | svn_stringbuf_t *uri; /* the uri within res */ |
---|
3524 | svn_stringbuf_t *repos_path; /* the repos_path within res */ |
---|
3525 | |
---|
3526 | } walker_ctx_t; |
---|
3527 | |
---|
3528 | |
---|
3529 | static dav_error * |
---|
3530 | do_walk(walker_ctx_t *ctx, int depth) |
---|
3531 | { |
---|
3532 | const dav_walk_params *params = ctx->params; |
---|
3533 | int isdir = ctx->res.collection; |
---|
3534 | dav_error *err; |
---|
3535 | svn_error_t *serr; |
---|
3536 | apr_hash_index_t *hi; |
---|
3537 | apr_size_t path_len; |
---|
3538 | apr_size_t uri_len; |
---|
3539 | apr_size_t repos_len; |
---|
3540 | apr_hash_t *children; |
---|
3541 | |
---|
3542 | /* Clear the temporary pool. */ |
---|
3543 | svn_pool_clear(ctx->info.pool); |
---|
3544 | |
---|
3545 | /* The current resource is a collection (possibly here thru recursion) |
---|
3546 | and this is the invocation for the collection. Alternatively, this is |
---|
3547 | the first [and only] entry to do_walk() for a member resource, so |
---|
3548 | this will be the invocation for the member. */ |
---|
3549 | err = (*params->func)(&ctx->wres, |
---|
3550 | isdir ? DAV_CALLTYPE_COLLECTION : DAV_CALLTYPE_MEMBER); |
---|
3551 | if (err != NULL) |
---|
3552 | return err; |
---|
3553 | |
---|
3554 | /* if we are not to recurse, or this is a member, then we're done */ |
---|
3555 | if (depth == 0 || !isdir) |
---|
3556 | return NULL; |
---|
3557 | |
---|
3558 | /* ### for now, let's say that working resources have no children. of |
---|
3559 | ### course, this isn't true (or "right") for working collections, but |
---|
3560 | ### we don't actually need to do a walk right now. */ |
---|
3561 | if (params->root->type == DAV_RESOURCE_TYPE_WORKING) |
---|
3562 | return NULL; |
---|
3563 | |
---|
3564 | /* ### need to allow more walking in the future */ |
---|
3565 | if (params->root->type != DAV_RESOURCE_TYPE_REGULAR) |
---|
3566 | { |
---|
3567 | return dav_new_error(params->pool, HTTP_METHOD_NOT_ALLOWED, 0, |
---|
3568 | "Walking the resource hierarchy can only be done " |
---|
3569 | "on 'regular' resources [at this time]."); |
---|
3570 | } |
---|
3571 | |
---|
3572 | /* assert: collection resource. isdir == TRUE. repos_path != NULL. */ |
---|
3573 | |
---|
3574 | /* append "/" to the paths, in preparation for appending child names. |
---|
3575 | don't add "/" if the paths are simply "/" */ |
---|
3576 | if (ctx->info.uri_path->data[ctx->info.uri_path->len - 1] != '/') |
---|
3577 | svn_stringbuf_appendcstr(ctx->info.uri_path, "/"); |
---|
3578 | if (ctx->repos_path->data[ctx->repos_path->len - 1] != '/') |
---|
3579 | svn_stringbuf_appendcstr(ctx->repos_path, "/"); |
---|
3580 | |
---|
3581 | /* NOTE: the URI should already have a trailing "/" */ |
---|
3582 | |
---|
3583 | /* fix up the dependent pointers */ |
---|
3584 | ctx->info.repos_path = ctx->repos_path->data; |
---|
3585 | |
---|
3586 | /* all of the children exist. also initialize the collection flag. */ |
---|
3587 | ctx->res.exists = TRUE; |
---|
3588 | ctx->res.collection = FALSE; |
---|
3589 | |
---|
3590 | /* remember these values so we can chop back to them after each time |
---|
3591 | we append a child name to the path/uri/repos */ |
---|
3592 | path_len = ctx->info.uri_path->len; |
---|
3593 | uri_len = ctx->uri->len; |
---|
3594 | repos_len = ctx->repos_path->len; |
---|
3595 | |
---|
3596 | /* Tell our logging subsystem that we're listing a directory. |
---|
3597 | |
---|
3598 | Note: if we cared, we could look at the 'User-Agent:' request |
---|
3599 | header and distinguish an svn client ('svn ls') from a generic |
---|
3600 | DAV client. */ |
---|
3601 | dav_svn__operational_log(&ctx->info, |
---|
3602 | svn_log__get_dir(ctx->info.repos_path, |
---|
3603 | ctx->info.root.rev, |
---|
3604 | TRUE, FALSE, SVN_DIRENT_ALL, |
---|
3605 | params->pool)); |
---|
3606 | |
---|
3607 | /* fetch this collection's children */ |
---|
3608 | serr = svn_fs_dir_entries(&children, ctx->info.root.root, |
---|
3609 | ctx->info.repos_path, params->pool); |
---|
3610 | if (serr != NULL) |
---|
3611 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3612 | "could not fetch collection members", |
---|
3613 | params->pool); |
---|
3614 | |
---|
3615 | /* iterate over the children in this collection */ |
---|
3616 | for (hi = apr_hash_first(params->pool, children); hi; hi = apr_hash_next(hi)) |
---|
3617 | { |
---|
3618 | const void *key; |
---|
3619 | apr_ssize_t klen; |
---|
3620 | void *val; |
---|
3621 | svn_fs_dirent_t *dirent; |
---|
3622 | |
---|
3623 | /* fetch one of the children */ |
---|
3624 | apr_hash_this(hi, &key, &klen, &val); |
---|
3625 | dirent = val; |
---|
3626 | |
---|
3627 | /* authorize access to this resource, if applicable */ |
---|
3628 | if (params->walk_type & DAV_WALKTYPE_AUTH) |
---|
3629 | { |
---|
3630 | /* ### how/what to do? */ |
---|
3631 | } |
---|
3632 | |
---|
3633 | /* append this child to our buffers */ |
---|
3634 | svn_stringbuf_appendbytes(ctx->info.uri_path, key, klen); |
---|
3635 | svn_stringbuf_appendbytes(ctx->uri, key, klen); |
---|
3636 | svn_stringbuf_appendbytes(ctx->repos_path, key, klen); |
---|
3637 | |
---|
3638 | /* reset the pointers since the above may have changed them */ |
---|
3639 | ctx->res.uri = ctx->uri->data; |
---|
3640 | ctx->info.repos_path = ctx->repos_path->data; |
---|
3641 | |
---|
3642 | if (dirent->kind == svn_node_file) |
---|
3643 | { |
---|
3644 | err = (*params->func)(&ctx->wres, DAV_CALLTYPE_MEMBER); |
---|
3645 | if (err != NULL) |
---|
3646 | return err; |
---|
3647 | } |
---|
3648 | else |
---|
3649 | { |
---|
3650 | /* this resource is a collection */ |
---|
3651 | ctx->res.collection = TRUE; |
---|
3652 | |
---|
3653 | /* append a slash to the URI (the path doesn't need it yet) */ |
---|
3654 | svn_stringbuf_appendcstr(ctx->uri, "/"); |
---|
3655 | ctx->res.uri = ctx->uri->data; |
---|
3656 | |
---|
3657 | /* recurse on this collection */ |
---|
3658 | err = do_walk(ctx, depth - 1); |
---|
3659 | if (err != NULL) |
---|
3660 | return err; |
---|
3661 | |
---|
3662 | /* restore the data */ |
---|
3663 | ctx->res.collection = FALSE; |
---|
3664 | } |
---|
3665 | |
---|
3666 | /* chop the child off the paths and uri. NOTE: no null-term. */ |
---|
3667 | ctx->info.uri_path->len = path_len; |
---|
3668 | ctx->uri->len = uri_len; |
---|
3669 | ctx->repos_path->len = repos_len; |
---|
3670 | } |
---|
3671 | return NULL; |
---|
3672 | } |
---|
3673 | |
---|
3674 | static dav_error * |
---|
3675 | walk(const dav_walk_params *params, int depth, dav_response **response) |
---|
3676 | { |
---|
3677 | /* Thinking about adding support for LOCKNULL resources in this |
---|
3678 | walker? Check out the (working) code that was removed here: |
---|
3679 | Author: cmpilato |
---|
3680 | Date: Fri Mar 18 14:54:02 2005 |
---|
3681 | New Revision: 13475 |
---|
3682 | */ |
---|
3683 | |
---|
3684 | walker_ctx_t ctx = { 0 }; |
---|
3685 | dav_error *err; |
---|
3686 | |
---|
3687 | ctx.params = params; |
---|
3688 | |
---|
3689 | ctx.wres.walk_ctx = params->walk_ctx; |
---|
3690 | ctx.wres.pool = params->pool; |
---|
3691 | ctx.wres.resource = &ctx.res; |
---|
3692 | |
---|
3693 | /* copy the resource over and adjust the "info" reference */ |
---|
3694 | ctx.res = *params->root; |
---|
3695 | ctx.info = *ctx.res.info; |
---|
3696 | |
---|
3697 | ctx.res.info = &ctx.info; |
---|
3698 | |
---|
3699 | /* operate within the proper pool */ |
---|
3700 | ctx.res.pool = params->pool; |
---|
3701 | |
---|
3702 | /* Don't monkey with the path from params->root. Create a new one. |
---|
3703 | This path will then be extended/shortened as necessary. */ |
---|
3704 | ctx.info.uri_path = svn_stringbuf_dup(ctx.info.uri_path, params->pool); |
---|
3705 | |
---|
3706 | /* prep the URI buffer */ |
---|
3707 | ctx.uri = svn_stringbuf_create(params->root->uri, params->pool); |
---|
3708 | |
---|
3709 | /* same for repos_path */ |
---|
3710 | if (ctx.info.repos_path == NULL) |
---|
3711 | ctx.repos_path = NULL; |
---|
3712 | else |
---|
3713 | ctx.repos_path = svn_stringbuf_create(ctx.info.repos_path, params->pool); |
---|
3714 | |
---|
3715 | /* if we have a collection, then ensure the URI has a trailing "/" */ |
---|
3716 | /* ### get_resource always kills the trailing slash... */ |
---|
3717 | if (ctx.res.collection && ctx.uri->data[ctx.uri->len - 1] != '/') { |
---|
3718 | svn_stringbuf_appendcstr(ctx.uri, "/"); |
---|
3719 | } |
---|
3720 | |
---|
3721 | /* the current resource's URI is stored in the (telescoping) ctx.uri */ |
---|
3722 | ctx.res.uri = ctx.uri->data; |
---|
3723 | |
---|
3724 | /* the current resource's repos_path is stored in ctx.repos_path */ |
---|
3725 | if (ctx.repos_path != NULL) |
---|
3726 | ctx.info.repos_path = ctx.repos_path->data; |
---|
3727 | |
---|
3728 | /* Create a pool usable by the response. */ |
---|
3729 | ctx.info.pool = svn_pool_create(params->pool); |
---|
3730 | |
---|
3731 | /* ### is the root already/always open? need to verify */ |
---|
3732 | |
---|
3733 | /* always return the error, and any/all multistatus responses */ |
---|
3734 | err = do_walk(&ctx, depth); |
---|
3735 | *response = ctx.wres.response; |
---|
3736 | |
---|
3737 | return err; |
---|
3738 | } |
---|
3739 | |
---|
3740 | |
---|
3741 | |
---|
3742 | /*** Utility functions for resource management ***/ |
---|
3743 | |
---|
3744 | dav_resource * |
---|
3745 | dav_svn__create_working_resource(dav_resource *base, |
---|
3746 | const char *activity_id, |
---|
3747 | const char *txn_name, |
---|
3748 | int tweak_in_place) |
---|
3749 | { |
---|
3750 | const char *path; |
---|
3751 | dav_resource *res; |
---|
3752 | |
---|
3753 | if (base->baselined) |
---|
3754 | path = apr_psprintf(base->pool, |
---|
3755 | "/%s/wbl/%s/%ld", |
---|
3756 | base->info->repos->special_uri, |
---|
3757 | activity_id, base->info->root.rev); |
---|
3758 | else |
---|
3759 | path = apr_psprintf(base->pool, "/%s/wrk/%s%s", |
---|
3760 | base->info->repos->special_uri, |
---|
3761 | activity_id, base->info->repos_path); |
---|
3762 | path = svn_path_uri_encode(path, base->pool); |
---|
3763 | |
---|
3764 | if (tweak_in_place) |
---|
3765 | res = base; |
---|
3766 | else |
---|
3767 | { |
---|
3768 | res = apr_pcalloc(base->pool, sizeof(*res)); |
---|
3769 | res->info = apr_pcalloc(base->pool, sizeof(*res->info)); |
---|
3770 | } |
---|
3771 | |
---|
3772 | res->type = DAV_RESOURCE_TYPE_WORKING; |
---|
3773 | res->exists = TRUE; /* ### not necessarily correct */ |
---|
3774 | res->versioned = TRUE; |
---|
3775 | res->working = TRUE; |
---|
3776 | res->baselined = base->baselined; |
---|
3777 | /* collection = FALSE. ### not necessarily correct */ |
---|
3778 | |
---|
3779 | res->uri = apr_pstrcat(base->pool, base->info->repos->root_path, |
---|
3780 | path, NULL); |
---|
3781 | res->hooks = &dav_svn__hooks_repository; |
---|
3782 | res->pool = base->pool; |
---|
3783 | |
---|
3784 | res->info->uri_path = svn_stringbuf_create(path, base->pool); |
---|
3785 | res->info->repos = base->info->repos; |
---|
3786 | res->info->repos_path = base->info->repos_path; |
---|
3787 | res->info->root.rev = base->info->root.rev; |
---|
3788 | res->info->root.activity_id = activity_id; |
---|
3789 | res->info->root.txn_name = txn_name; |
---|
3790 | |
---|
3791 | if (tweak_in_place) |
---|
3792 | return NULL; |
---|
3793 | else |
---|
3794 | return res; |
---|
3795 | } |
---|
3796 | |
---|
3797 | |
---|
3798 | dav_error * |
---|
3799 | dav_svn__working_to_regular_resource(dav_resource *resource) |
---|
3800 | { |
---|
3801 | dav_resource_private *priv = resource->info; |
---|
3802 | dav_svn_repos *repos = priv->repos; |
---|
3803 | const char *path; |
---|
3804 | svn_error_t *serr; |
---|
3805 | |
---|
3806 | /* no need to change the repos object or repos_path */ |
---|
3807 | |
---|
3808 | /* set type back to REGULAR */ |
---|
3809 | resource->type = DAV_RESOURCE_TYPE_REGULAR; |
---|
3810 | |
---|
3811 | /* remove the working flag */ |
---|
3812 | resource->working = FALSE; |
---|
3813 | |
---|
3814 | /* Change the URL into either a baseline-collection or a public one. */ |
---|
3815 | if (priv->root.rev == SVN_INVALID_REVNUM) |
---|
3816 | { |
---|
3817 | serr = svn_fs_youngest_rev(&priv->root.rev, repos->fs, resource->pool); |
---|
3818 | if (serr != NULL) |
---|
3819 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3820 | "Could not determine youngest rev.", |
---|
3821 | resource->pool); |
---|
3822 | |
---|
3823 | /* create public URL */ |
---|
3824 | path = apr_psprintf(resource->pool, "%s", priv->repos_path); |
---|
3825 | } |
---|
3826 | else |
---|
3827 | { |
---|
3828 | /* if rev was specific, create baseline-collection URL */ |
---|
3829 | path = dav_svn__build_uri(repos, DAV_SVN__BUILD_URI_BC, |
---|
3830 | priv->root.rev, priv->repos_path, |
---|
3831 | 0, resource->pool); |
---|
3832 | } |
---|
3833 | path = svn_path_uri_encode(path, resource->pool); |
---|
3834 | priv->uri_path = svn_stringbuf_create(path, resource->pool); |
---|
3835 | |
---|
3836 | /* change root.root back into a revision root. */ |
---|
3837 | serr = svn_fs_revision_root(&priv->root.root, repos->fs, |
---|
3838 | priv->root.rev, resource->pool); |
---|
3839 | if (serr != NULL) |
---|
3840 | return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, |
---|
3841 | "Could not open revision root.", |
---|
3842 | resource->pool); |
---|
3843 | |
---|
3844 | return NULL; |
---|
3845 | } |
---|
3846 | |
---|
3847 | |
---|
3848 | dav_error * |
---|
3849 | dav_svn__create_version_resource(dav_resource **version_res, |
---|
3850 | const char *uri, |
---|
3851 | apr_pool_t *pool) |
---|
3852 | { |
---|
3853 | int result; |
---|
3854 | dav_error *err; |
---|
3855 | |
---|
3856 | dav_resource_combined *comb = apr_pcalloc(pool, sizeof(*comb)); |
---|
3857 | |
---|
3858 | result = parse_version_uri(comb, uri, NULL, 0); |
---|
3859 | if (result != 0) |
---|
3860 | return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0, |
---|
3861 | "Could not parse version resource uri."); |
---|
3862 | |
---|
3863 | err = prep_version(comb); |
---|
3864 | if (err) |
---|
3865 | return err; |
---|
3866 | |
---|
3867 | *version_res = &comb->res; |
---|
3868 | return NULL; |
---|
3869 | } |
---|
3870 | |
---|
3871 | |
---|
3872 | const dav_hooks_repository dav_svn__hooks_repository = |
---|
3873 | { |
---|
3874 | 1, /* special GET handling */ |
---|
3875 | get_resource, |
---|
3876 | get_parent_resource, |
---|
3877 | is_same_resource, |
---|
3878 | is_parent_resource, |
---|
3879 | open_stream, |
---|
3880 | close_stream, |
---|
3881 | write_stream, |
---|
3882 | seek_stream, |
---|
3883 | set_headers, |
---|
3884 | deliver, |
---|
3885 | create_collection, |
---|
3886 | copy_resource, |
---|
3887 | move_resource, |
---|
3888 | remove_resource, |
---|
3889 | walk, |
---|
3890 | getetag_pathetic |
---|
3891 | }; |
---|