1 /*
2 * version.c: mod_dav_svn versioning provider functions for Subversion
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 #include <apr_tables.h>
25 #include <apr_uuid.h>
26
27 #include <httpd.h>
28 #include <http_log.h>
29 #include <mod_dav.h>
30
31 #include "svn_hash.h"
32 #include "svn_fs.h"
33 #include "svn_xml.h"
34 #include "svn_repos.h"
35 #include "svn_dav.h"
36 #include "svn_time.h"
37 #include "svn_pools.h"
38 #include "svn_props.h"
39 #include "svn_dav.h"
40 #include "svn_base64.h"
41 #include "svn_version.h"
42 #include "private/svn_repos_private.h"
43 #include "private/svn_subr_private.h"
44 #include "private/svn_dav_protocol.h"
45 #include "private/svn_log.h"
46 #include "private/svn_fspath.h"
47
48 #include "dav_svn.h"
49
50
51 svn_error_t *
dav_svn__attach_auto_revprops(svn_fs_txn_t * txn,const char * fs_path,apr_pool_t * pool)52 dav_svn__attach_auto_revprops(svn_fs_txn_t *txn,
53 const char *fs_path,
54 apr_pool_t *pool)
55 {
56 const char *logmsg;
57 svn_string_t *logval;
58 svn_error_t *serr;
59
60 logmsg = apr_psprintf(pool,
61 "Autoversioning commit: a non-deltaV client made "
62 "a change to\n%s", fs_path);
63
64 logval = svn_string_create(logmsg, pool);
65 if ((serr = svn_repos_fs_change_txn_prop(txn, SVN_PROP_REVISION_LOG, logval,
66 pool)))
67 return serr;
68
69 /* Notate that this revision was created by autoversioning. (Tools
70 like post-commit email scripts might not care to send an email
71 for every autoversioning change.) */
72 if ((serr = svn_repos_fs_change_txn_prop(txn,
73 SVN_PROP_REVISION_AUTOVERSIONED,
74 svn_string_create("*", pool),
75 pool)))
76 return serr;
77
78 return SVN_NO_ERROR;
79 }
80
81
82 /* Helper: attach an auto-generated svn:log property to a txn within
83 an auto-checked-out working resource. */
84 static dav_error *
set_auto_revprops(dav_resource * resource)85 set_auto_revprops(dav_resource *resource)
86 {
87 svn_error_t *serr;
88
89 if (! (resource->type == DAV_RESOURCE_TYPE_WORKING
90 && resource->info->auto_checked_out))
91 return dav_svn__new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
92 "Set_auto_revprops called on invalid resource.");
93
94 if ((serr = dav_svn__attach_auto_revprops(resource->info->root.txn,
95 resource->info->repos_path,
96 resource->pool)))
97 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
98 "Error setting a revision property "
99 " on auto-checked-out resource's txn. ",
100 resource->pool);
101 return NULL;
102 }
103
104
105 static dav_error *
open_txn(svn_fs_txn_t ** ptxn,svn_fs_t * fs,const char * txn_name,apr_pool_t * pool)106 open_txn(svn_fs_txn_t **ptxn,
107 svn_fs_t *fs,
108 const char *txn_name,
109 apr_pool_t *pool)
110 {
111 svn_error_t *serr;
112
113 serr = svn_fs_open_txn(ptxn, fs, txn_name, pool);
114 if (serr != NULL)
115 {
116 if (serr->apr_err == SVN_ERR_FS_NO_SUCH_TRANSACTION)
117 {
118 /* ### correct HTTP error? */
119 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
120 "The transaction specified by the "
121 "activity does not exist",
122 pool);
123 }
124
125 /* ### correct HTTP error? */
126 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
127 "There was a problem opening the "
128 "transaction specified by this "
129 "activity.",
130 pool);
131 }
132
133 return NULL;
134 }
135
136
137 static void
get_vsn_options(apr_pool_t * p,apr_text_header * phdr)138 get_vsn_options(apr_pool_t *p, apr_text_header *phdr)
139 {
140 /* Note: we append pieces with care for Web Folders's 63-char limit
141 on the DAV: header */
142
143 apr_text_append(p, phdr,
144 "version-control,checkout,working-resource");
145 apr_text_append(p, phdr,
146 "merge,baseline,activity,version-controlled-collection");
147 /* Send SVN_RA_CAPABILITY_* capabilities. */
148 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_DEPTH);
149 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
150 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS);
151 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY);
152 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_INHERITED_PROPS);
153 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_INLINE_PROPS);
154 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS);
155 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_LIST);
156 /* Mergeinfo is a special case: here we merely say that the server
157 * knows how to handle mergeinfo -- whether the repository does too
158 * is a separate matter.
159 *
160 * Think of it as offering the client an early out: if the server
161 * can't do merge-tracking, there's no point finding out of the
162 * repository can. But if the server can, it may be worth expending
163 * an extra round trip to find out if the repository can too (the
164 * extra round trip being necessary because, sadly, we don't have
165 * access to the repository yet here, so we can only announce the
166 * server capability and remain agnostic about the repository).
167 */
168 apr_text_append(p, phdr, SVN_DAV_NS_DAV_SVN_MERGEINFO);
169
170 /* ### fork-control? */
171 }
172
173
174 static dav_error *
get_option(const dav_resource * resource,const apr_xml_elem * elem,apr_text_header * option)175 get_option(const dav_resource *resource,
176 const apr_xml_elem *elem,
177 apr_text_header *option)
178 {
179 int i;
180 request_rec *r = resource->info->r;
181 const char *repos_root_uri =
182 dav_svn__build_uri(resource->info->repos, DAV_SVN__BUILD_URI_PUBLIC,
183 SVN_IGNORED_REVNUM, "", FALSE /* add_href */,
184 resource->pool);
185 svn_version_t *master_version = dav_svn__get_master_version(r);
186
187 /* These capabilities are used during commit and when configured as
188 a WebDAV slave (SVNMasterURI is set) their availablity should
189 depend on the master version (SVNMasterVersion is set) if it is
190 older than our own version. Also, although SVNDIFF1 is available
191 before 1.10 none of those earlier servers advertised it so for
192 consistency we don't advertise it for masters older than 1.10. */
193 struct capability_versions_t {
194 const char *capability_name;
195 svn_version_t min_version;
196 } capabilities[] = {
197 { SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, { 1, 8, 0, ""} },
198 { SVN_DAV_NS_DAV_SVN_SVNDIFF1, { 1, 10, 0, ""} },
199 { SVN_DAV_NS_DAV_SVN_SVNDIFF2, { 1, 10, 0, ""} },
200 { SVN_DAV_NS_DAV_SVN_PUT_RESULT_CHECKSUM, { 1, 10, 0, ""} },
201 };
202
203 /* ### DAV:version-history-collection-set */
204 if (elem->ns != APR_XML_NS_DAV_ID
205 || strcmp(elem->name, "activity-collection-set") != 0)
206 {
207 /* We don't know about other options (yet).
208
209 If we ever add multiple option request keys we should
210 just write the requested option value and make sure
211 we set the headers *once*. */
212 return NULL;
213 }
214
215 apr_text_append(resource->pool, option,
216 "<D:activity-collection-set>");
217
218 apr_text_append(resource->pool, option,
219 dav_svn__build_uri(resource->info->repos,
220 DAV_SVN__BUILD_URI_ACT_COLLECTION,
221 SVN_INVALID_REVNUM, NULL,
222 TRUE /* add_href */,
223 resource->pool));
224 apr_text_append(resource->pool, option,
225 "</D:activity-collection-set>");
226
227 if (resource->info->repos->fs)
228 {
229 svn_error_t *serr;
230 svn_revnum_t youngest;
231 const char *uuid;
232
233 /* Got youngest revision? */
234 if ((serr = dav_svn__get_youngest_rev(&youngest, resource->info->repos,
235 resource->pool)))
236 {
237 return dav_svn__convert_err
238 (serr, HTTP_INTERNAL_SERVER_ERROR,
239 "Error fetching youngest revision from repository",
240 resource->pool);
241 }
242 if (SVN_IS_VALID_REVNUM(youngest))
243 {
244 apr_table_set(r->headers_out,
245 SVN_DAV_YOUNGEST_REV_HEADER,
246 apr_psprintf(resource->pool, "%ld", youngest));
247 }
248
249 /* Got repository UUID? */
250 if ((serr = svn_fs_get_uuid(resource->info->repos->fs,
251 &uuid, resource->pool)))
252 {
253 return dav_svn__convert_err
254 (serr, HTTP_INTERNAL_SERVER_ERROR,
255 "Error fetching repository UUID",
256 resource->pool);
257 }
258 if (uuid)
259 {
260 apr_table_set(r->headers_out,
261 SVN_DAV_REPOS_UUID_HEADER, uuid);
262 }
263 }
264
265 if (resource->info->repos->repos)
266 {
267 svn_error_t *serr;
268 svn_boolean_t has;
269
270 serr = svn_repos_has_capability(resource->info->repos->repos, &has,
271 SVN_REPOS_CAPABILITY_MERGEINFO,
272 r->pool);
273 if (serr)
274 return dav_svn__convert_err
275 (serr, HTTP_INTERNAL_SERVER_ERROR,
276 "Error fetching repository capabilities",
277 resource->pool);
278
279 apr_table_set(r->headers_out, SVN_DAV_REPOSITORY_MERGEINFO,
280 has ? "yes" : "no");
281 }
282
283 /* Welcome to the 2nd generation of the svn HTTP protocol, now
284 DeltaV-free! If we're configured to advise this support, do so. */
285 if (resource->info->repos->v2_protocol)
286 {
287 dav_svn__bulk_upd_conf bulk_upd_conf = dav_svn__get_bulk_updates_flag(r);
288
289 /* The list of Subversion's custom POSTs and which versions of
290 Subversion support them. We need this latter information
291 when acting as a WebDAV slave -- we don't want to claim
292 support for a POST type if the master server which will
293 actually have to handle it won't recognize it.
294
295 Keep this in sync with what's handled in handle_post_request().
296 */
297 struct posts_versions_t {
298 const char *post_name;
299 svn_version_t min_version;
300 } posts_versions[] = {
301 { "create-txn", { 1, 7, 0, "" } },
302 { "create-txn-with-props", { 1, 8, 0, "" } },
303 };
304
305 /* Add the header which indicates that this server can handle
306 replay REPORTs submitted against an HTTP v2 revision resource. */
307 apr_table_addn(r->headers_out, "DAV",
308 SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE);
309
310 /* Add a bunch of HTTP v2 headers which carry resource and
311 resource stub URLs that the client can use to naively build
312 addressable resources. */
313 apr_table_set(r->headers_out, SVN_DAV_ROOT_URI_HEADER, repos_root_uri);
314 apr_table_set(r->headers_out, SVN_DAV_ME_RESOURCE_HEADER,
315 apr_pstrcat(r->pool, repos_root_uri, "/",
316 dav_svn__get_me_resource_uri(r), SVN_VA_NULL));
317 apr_table_set(r->headers_out, SVN_DAV_REV_ROOT_STUB_HEADER,
318 apr_pstrcat(r->pool, repos_root_uri, "/",
319 dav_svn__get_rev_root_stub(r), SVN_VA_NULL));
320 apr_table_set(r->headers_out, SVN_DAV_REV_STUB_HEADER,
321 apr_pstrcat(r->pool, repos_root_uri, "/",
322 dav_svn__get_rev_stub(r), SVN_VA_NULL));
323 apr_table_set(r->headers_out, SVN_DAV_TXN_ROOT_STUB_HEADER,
324 apr_pstrcat(r->pool, repos_root_uri, "/",
325 dav_svn__get_txn_root_stub(r), SVN_VA_NULL));
326 apr_table_set(r->headers_out, SVN_DAV_TXN_STUB_HEADER,
327 apr_pstrcat(r->pool, repos_root_uri, "/",
328 dav_svn__get_txn_stub(r), SVN_VA_NULL));
329 apr_table_set(r->headers_out, SVN_DAV_VTXN_ROOT_STUB_HEADER,
330 apr_pstrcat(r->pool, repos_root_uri, "/",
331 dav_svn__get_vtxn_root_stub(r), SVN_VA_NULL));
332 apr_table_set(r->headers_out, SVN_DAV_VTXN_STUB_HEADER,
333 apr_pstrcat(r->pool, repos_root_uri, "/",
334 dav_svn__get_vtxn_stub(r), SVN_VA_NULL));
335 apr_table_set(r->headers_out, SVN_DAV_ALLOW_BULK_UPDATES,
336 bulk_upd_conf == CONF_BULKUPD_ON ? "On" :
337 bulk_upd_conf == CONF_BULKUPD_OFF ? "Off" : "Prefer");
338
339 /* Report the supported POST types. */
340 for (i = 0; i < sizeof(posts_versions)/sizeof(posts_versions[0]); ++i)
341 {
342 /* If we're proxying to a master server and its version
343 number is declared, we can selectively filter out POST
344 types that it doesn't support. */
345 if (master_version
346 && (! svn_version__at_least(master_version,
347 posts_versions[i].min_version.major,
348 posts_versions[i].min_version.minor,
349 posts_versions[i].min_version.patch)))
350 continue;
351
352 apr_table_addn(r->headers_out, SVN_DAV_SUPPORTED_POSTS_HEADER,
353 apr_pstrdup(r->pool, posts_versions[i].post_name));
354 }
355 }
356
357 /* Report commit capabilites. */
358 for (i = 0; i < sizeof(capabilities)/sizeof(capabilities[0]); ++i)
359 {
360 /* If a master version is declared filter out unsupported
361 capabilities. */
362 if (master_version
363 && (!svn_version__at_least(master_version,
364 capabilities[i].min_version.major,
365 capabilities[i].min_version.minor,
366 capabilities[i].min_version.patch)))
367 continue;
368
369 apr_table_addn(r->headers_out, "DAV",
370 apr_pstrdup(r->pool, capabilities[i].capability_name));
371 }
372
373 return NULL;
374 }
375
376
377 static int
versionable(const dav_resource * resource)378 versionable(const dav_resource *resource)
379 {
380 return 0;
381 }
382
383
384 static dav_auto_version
auto_versionable(const dav_resource * resource)385 auto_versionable(const dav_resource *resource)
386 {
387 /* The svn client attempts to proppatch a baseline when changing
388 unversioned revision props. Thus we allow baselines to be
389 "auto-checked-out" by mod_dav. See issue #916. */
390 if (resource->type == DAV_RESOURCE_TYPE_VERSION
391 && resource->baselined)
392 return DAV_AUTO_VERSION_ALWAYS;
393
394 /* No other autoversioning is allowed unless the SVNAutoversioning
395 directive is used. */
396 if (resource->info->repos->autoversioning)
397 {
398 /* This allows a straight-out PUT on a public file or collection
399 VCR. mod_dav's auto-versioning subsystem will check to see if
400 it's possible to auto-checkout a regular resource. */
401 if (resource->type == DAV_RESOURCE_TYPE_REGULAR)
402 return DAV_AUTO_VERSION_ALWAYS;
403
404 /* mod_dav's auto-versioning subsystem will also check to see if
405 it's possible to auto-checkin a working resource that was
406 auto-checked-out. We *only* allow auto-versioning on a working
407 resource if it was auto-checked-out. */
408 if (resource->type == DAV_RESOURCE_TYPE_WORKING
409 && resource->info->auto_checked_out)
410 return DAV_AUTO_VERSION_ALWAYS;
411 }
412
413 /* Default: whatever it is, assume it's not auto-versionable */
414 return DAV_AUTO_VERSION_NEVER;
415 }
416
417
418 static dav_error *
vsn_control(dav_resource * resource,const char * target)419 vsn_control(dav_resource *resource, const char *target)
420 {
421 /* All mod_dav_svn resources are versioned objects; so it doesn't
422 make sense to call vsn_control on a resource that exists . */
423 if (resource->exists)
424 return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0, 0,
425 "vsn_control called on already-versioned "
426 "resource.");
427
428 /* Only allow a NULL target, which means an create an 'empty' VCR. */
429 if (target != NULL)
430 return dav_svn__new_error_svn(resource->pool, HTTP_NOT_IMPLEMENTED,
431 SVN_ERR_UNSUPPORTED_FEATURE, 0,
432 "vsn_control called with non-null target");
433
434 /* This is kind of silly. The docstring for this callback says it's
435 supposed to "put a resource under version control". But in
436 Subversion, all REGULAR resources (bc's or public URIs) are
437 already under version control. So we don't need to do a thing to
438 the resource, just return. */
439 return NULL;
440 }
441
442
443 dav_error *
dav_svn__checkout(dav_resource * resource,int auto_checkout,int is_unreserved,int is_fork_ok,int create_activity,apr_array_header_t * activities,dav_resource ** working_resource)444 dav_svn__checkout(dav_resource *resource,
445 int auto_checkout,
446 int is_unreserved,
447 int is_fork_ok,
448 int create_activity,
449 apr_array_header_t *activities,
450 dav_resource **working_resource)
451 {
452 const char *txn_name;
453 svn_error_t *serr;
454 apr_status_t apr_err;
455 dav_error *derr;
456 dav_svn__uri_info parse;
457
458 /* Auto-Versioning Stuff */
459 if (auto_checkout)
460 {
461 const char *uuid_buf;
462 void *data;
463 const char *shared_activity, *shared_txn_name = NULL;
464
465 /* Baselines can be auto-checked-out -- grudgingly -- so we can
466 allow clients to proppatch unversioned rev props. See issue
467 #916. */
468 if ((resource->type == DAV_RESOURCE_TYPE_VERSION)
469 && resource->baselined)
470 /* ### We're violating deltaV big time here, by allowing a
471 dav_auto_checkout() on something that mod_dav assumes is a
472 VCR, not a VR. Anyway, mod_dav thinks we're checking out the
473 resource 'in place', so that no working resource is returned.
474 (It passes NULL as **working_resource.) */
475 return NULL;
476
477 if (resource->type != DAV_RESOURCE_TYPE_REGULAR)
478 return dav_svn__new_error_svn(resource->pool, HTTP_METHOD_NOT_ALLOWED,
479 SVN_ERR_UNSUPPORTED_FEATURE, 0,
480 "auto-checkout attempted on non-regular "
481 "version-controlled resource");
482
483 if (resource->baselined)
484 return dav_svn__new_error_svn(resource->pool, HTTP_METHOD_NOT_ALLOWED,
485 SVN_ERR_UNSUPPORTED_FEATURE, 0,
486 "auto-checkout attempted on baseline "
487 "collection, which is not supported");
488
489 /* See if the shared activity already exists. */
490 apr_err = apr_pool_userdata_get(&data,
491 DAV_SVN__AUTOVERSIONING_ACTIVITY,
492 resource->info->r->pool);
493 if (apr_err)
494 return dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
495 HTTP_INTERNAL_SERVER_ERROR,
496 "Error fetching pool userdata.",
497 resource->pool);
498 shared_activity = data;
499
500 if (! shared_activity)
501 {
502 /* Build a shared activity for all auto-checked-out resources. */
503 uuid_buf = svn_uuid_generate(resource->info->r->pool);
504 shared_activity = apr_pstrdup(resource->info->r->pool, uuid_buf);
505
506 derr = dav_svn__create_txn(resource->info->repos, &shared_txn_name,
507 NULL, resource->info->r->pool);
508 if (derr) return derr;
509
510 derr = dav_svn__store_activity(resource->info->repos,
511 shared_activity, shared_txn_name);
512 if (derr) return derr;
513
514 /* Save the shared activity in r->pool for others to use. */
515 apr_err = apr_pool_userdata_set(shared_activity,
516 DAV_SVN__AUTOVERSIONING_ACTIVITY,
517 NULL, resource->info->r->pool);
518 if (apr_err)
519 return dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
520 HTTP_INTERNAL_SERVER_ERROR,
521 "Error setting pool userdata.",
522 resource->pool);
523 }
524
525 if (! shared_txn_name)
526 {
527 shared_txn_name = dav_svn__get_txn(resource->info->repos,
528 shared_activity);
529 if (! shared_txn_name)
530 return dav_svn__new_error(resource->pool,
531 HTTP_INTERNAL_SERVER_ERROR, 0, 0,
532 "Cannot look up a txn_name by activity");
533 }
534
535 /* Tweak the VCR in-place, making it into a WR. (Ignore the
536 NULL return value.) */
537 dav_svn__create_working_resource(resource,
538 shared_activity, shared_txn_name,
539 TRUE /* tweak in place */);
540
541 /* Remember that this resource was auto-checked-out, so that
542 auto_versionable allows us to do an auto-checkin and
543 can_be_activity will allow this resource to be an
544 activity. */
545 resource->info->auto_checked_out = TRUE;
546
547 /* The txn and txn_root must be open and ready to go in the
548 resource's root object. Normally prep_resource() will do
549 this automatically on a WR's root object. We're
550 converting a VCR to WR forcibly, so it's now our job to
551 make sure it happens. */
552 derr = open_txn(&resource->info->root.txn, resource->info->repos->fs,
553 resource->info->root.txn_name, resource->pool);
554 if (derr) return derr;
555
556 serr = svn_fs_txn_root(&resource->info->root.root,
557 resource->info->root.txn, resource->pool);
558 if (serr != NULL)
559 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
560 "Could not open a (transaction) root "
561 "in the repository",
562 resource->pool);
563 return NULL;
564 }
565 /* end of Auto-Versioning Stuff */
566
567 if (resource->type != DAV_RESOURCE_TYPE_VERSION)
568 {
569 return dav_svn__new_error_svn(resource->pool, HTTP_METHOD_NOT_ALLOWED,
570 SVN_ERR_UNSUPPORTED_FEATURE, 0,
571 "CHECKOUT can only be performed on a "
572 "version resource");
573 }
574 if (create_activity)
575 {
576 return dav_svn__new_error_svn(resource->pool, HTTP_NOT_IMPLEMENTED,
577 SVN_ERR_UNSUPPORTED_FEATURE, 0,
578 "CHECKOUT cannot create an activity at "
579 "this time. Use MKACTIVITY first");
580 }
581 if (is_unreserved)
582 {
583 return dav_svn__new_error_svn(resource->pool, HTTP_NOT_IMPLEMENTED,
584 SVN_ERR_UNSUPPORTED_FEATURE, 0,
585 "Unreserved checkouts are not yet "
586 "available. A version history may not be "
587 "checked out more than once, into a "
588 "specific activity");
589 }
590 if (activities == NULL)
591 {
592 return dav_svn__new_error_svn(resource->pool, HTTP_CONFLICT,
593 SVN_ERR_INCOMPLETE_DATA, 0,
594 "An activity must be provided for "
595 "checkout");
596 }
597 /* assert: nelts > 0. the below check effectively means > 1. */
598 if (activities->nelts != 1)
599 {
600 return dav_svn__new_error_svn(resource->pool, HTTP_CONFLICT,
601 SVN_ERR_INCORRECT_PARAMS, 0,
602 "Only one activity may be specified within "
603 "the CHECKOUT");
604 }
605
606 serr = dav_svn__simple_parse_uri(&parse, resource,
607 APR_ARRAY_IDX(activities, 0, const char *),
608 resource->pool);
609 if (serr != NULL)
610 {
611 /* ### is BAD_REQUEST proper? */
612 return dav_svn__convert_err(serr, HTTP_CONFLICT,
613 "The activity href could not be parsed "
614 "properly.",
615 resource->pool);
616 }
617 if (parse.activity_id == NULL)
618 {
619 return dav_svn__new_error_svn(resource->pool, HTTP_CONFLICT,
620 SVN_ERR_INCORRECT_PARAMS, 0,
621 "The provided href is not an activity URI");
622 }
623
624 if ((txn_name = dav_svn__get_txn(resource->info->repos,
625 parse.activity_id)) == NULL)
626 {
627 return dav_svn__new_error_svn(resource->pool, HTTP_CONFLICT,
628 SVN_ERR_APMOD_ACTIVITY_NOT_FOUND, 0,
629 "The specified activity does not exist");
630 }
631
632 /* verify the specified version resource is the "latest", thus allowing
633 changes to be made. */
634 if (resource->baselined || resource->info->root.rev == SVN_INVALID_REVNUM)
635 {
636 /* a Baseline, or a standard Version Resource which was accessed
637 via a Label against a VCR within a Baseline Collection. */
638 /* ### at the moment, this branch is only reached for baselines */
639
640 svn_revnum_t youngest;
641
642 /* make sure the baseline being checked out is the latest */
643 serr = dav_svn__get_youngest_rev(&youngest, resource->info->repos,
644 resource->pool);
645 if (serr != NULL)
646 {
647 /* ### correct HTTP error? */
648 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
649 "Could not determine the youngest "
650 "revision for verification against "
651 "the baseline being checked out.",
652 resource->pool);
653 }
654
655 if (resource->info->root.rev != youngest)
656 {
657 return dav_svn__new_error_svn(resource->pool, HTTP_CONFLICT,
658 SVN_ERR_APMOD_BAD_BASELINE, 0,
659 "The specified baseline is not the "
660 "latest baseline, so it may not be "
661 "checked out");
662 }
663
664 /* ### hmm. what if the transaction root's revision is different
665 ### from this baseline? i.e. somebody created a new revision while
666 ### we are processing this commit.
667 ###
668 ### first question: what does the client *do* with a working
669 ### baseline? knowing that, and how it maps to our backend, then
670 ### we can figure out what to do here. */
671 }
672 else
673 {
674 /* standard Version Resource */
675
676 svn_fs_txn_t *txn;
677 svn_fs_root_t *txn_root;
678 svn_revnum_t txn_created_rev;
679 dav_error *err;
680
681 /* open the specified transaction so that we can verify this version
682 resource corresponds to the current/latest in the transaction. */
683 if ((err = open_txn(&txn, resource->info->repos->fs, txn_name,
684 resource->pool)) != NULL)
685 return err;
686
687 serr = svn_fs_txn_root(&txn_root, txn, resource->pool);
688 if (serr != NULL)
689 {
690 /* ### correct HTTP error? */
691 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
692 "Could not open the transaction tree.",
693 resource->pool);
694 }
695
696 /* assert: repos_path != NULL (for this type of resource) */
697
698
699 /* Out-of-dateness check: compare the created-rev of the item
700 in the txn against the created-rev of the version resource
701 being changed. */
702 serr = svn_fs_node_created_rev(&txn_created_rev,
703 txn_root, resource->info->repos_path,
704 resource->pool);
705 if (serr != NULL)
706 {
707 /* ### correct HTTP error? */
708 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
709 "Could not get created-rev of "
710 "transaction node.",
711 resource->pool);
712 }
713
714 /* If txn_created_rev is invalid, that means it's already
715 mutable in the txn... which means it has already passed this
716 out-of-dateness check. (Usually, this happens when looking
717 at a parent directory of an already checked-out
718 resource.)
719
720 Now, we come down to it. If the created revision of the node
721 in the transaction is different from the revision parsed from
722 the version resource URL, we're in a bit of a quandry, and
723 one of a few things could be true.
724
725 - The client is trying to modify an old (out-of-date)
726 revision of the resource. This is, of course,
727 unacceptable!
728
729 - The client is trying to modify a *newer* revision. If the
730 version resource is *newer* than the transaction root, then
731 the client started a commit, a new revision was created
732 within the repository, the client fetched the new resource
733 from that new revision, changed it (or merged in a prior
734 change), and then attempted to incorporate that into the
735 commit that was initially started. We could copy that new
736 node into our transaction and then modify it, but why
737 bother? We can stop the commit, and everything will be
738 fine again if the user simply restarts it (because we'll
739 use that new revision as the transaction root, thus
740 incorporating the new resource, which they will then
741 modify).
742
743 - The path/revision that client is wishing to edit and the
744 path/revision in the current transaction are actually the
745 same node, and thus this created-rev comparison didn't
746 really solidify anything after all. :-)
747 */
748
749 if (SVN_IS_VALID_REVNUM( txn_created_rev ))
750 {
751 if (resource->info->root.rev < txn_created_rev)
752 {
753 /* The item being modified is older than the one in the
754 transaction. The client is out of date. */
755 return dav_svn__new_error_svn
756 (resource->pool, HTTP_CONFLICT, SVN_ERR_FS_CONFLICT, 0,
757 "resource out of date; try updating");
758 }
759 else if (resource->info->root.rev > txn_created_rev)
760 {
761 /* The item being modified is being accessed via a newer
762 revision than the one in the transaction. We'll
763 check to see if they are still the same node, and if
764 not, return an error. */
765 svn_fs_node_relation_t node_relation;
766 if ((serr = svn_fs_node_relation(&node_relation, txn_root,
767 resource->info->repos_path,
768 resource->info->root.root,
769 resource->info->repos_path,
770 resource->pool)))
771 {
772 err = dav_svn__new_error_svn
773 (resource->pool, HTTP_CONFLICT, serr->apr_err, 0,
774 "Unable to fetch the node revision id of the version "
775 "resource within the revision");
776 svn_error_clear(serr);
777 return err;
778 }
779 if (node_relation != svn_fs_node_unchanged)
780 {
781 return dav_svn__new_error_svn
782 (resource->pool, HTTP_CONFLICT, SVN_ERR_FS_CONFLICT, 0,
783 "version resource newer than txn (restart the commit)");
784 }
785 }
786 }
787 }
788 *working_resource = dav_svn__create_working_resource(resource,
789 parse.activity_id,
790 txn_name,
791 FALSE);
792 return NULL;
793 }
794
795
796 static dav_error *
uncheckout(dav_resource * resource)797 uncheckout(dav_resource *resource)
798 {
799 if (resource->type != DAV_RESOURCE_TYPE_WORKING)
800 return dav_svn__new_error_svn(resource->pool, HTTP_INTERNAL_SERVER_ERROR,
801 SVN_ERR_UNSUPPORTED_FEATURE, 0,
802 "UNCHECKOUT called on non-working resource");
803
804 /* Try to abort the txn if it exists; but don't try too hard. :-) */
805 if (resource->info->root.txn)
806 svn_error_clear(svn_fs_abort_txn(resource->info->root.txn,
807 resource->pool));
808
809 /* Attempt to destroy the shared activity. */
810 if (resource->info->root.activity_id)
811 {
812 dav_svn__delete_activity(resource->info->repos,
813 resource->info->root.activity_id);
814 apr_pool_userdata_set(NULL, DAV_SVN__AUTOVERSIONING_ACTIVITY,
815 NULL, resource->info->r->pool);
816 }
817
818 resource->info->root.txn_name = NULL;
819 resource->info->root.txn = NULL;
820
821 /* We're no longer checked out. */
822 resource->info->auto_checked_out = FALSE;
823
824 /* Convert the working resource back into a regular one, in-place. */
825 return dav_svn__working_to_regular_resource(resource);
826 }
827
828
829 /* Closure object for cleanup_deltify. */
830 struct cleanup_deltify_baton
831 {
832 /* The repository in which to deltify. We use a path instead of an
833 object, because it's difficult to obtain a repos or fs object
834 with the right lifetime guarantees. */
835 const char *repos_path;
836
837 /* The revision number against which to deltify. */
838 svn_revnum_t revision;
839
840 /* The pool to use for all temporary allocation while working. This
841 may or may not be the same as the pool on which the cleanup is
842 registered, but obviously it must have a lifetime at least as
843 long as that pool. */
844 apr_pool_t *pool;
845 };
846
847
848 /* APR pool cleanup function to deltify against a just-committed
849 revision. DATA is a 'struct cleanup_deltify_baton *'.
850
851 If any errors occur, log them in the httpd server error log, but
852 return APR_SUCCESS no matter what, as this is a pool cleanup
853 function and deltification is not a matter of correctness
854 anyway. */
855 static apr_status_t
cleanup_deltify(void * data)856 cleanup_deltify(void *data)
857 {
858 struct cleanup_deltify_baton *cdb = data;
859 svn_repos_t *repos;
860 svn_error_t *err;
861
862 /* It's okay to allocate in the pool that's being cleaned up, and
863 it's also okay to register new cleanups against that pool. But
864 if you create subpools of it, you must make sure to destroy them
865 at the end of the cleanup. So we do all our work in this
866 subpool, then destroy it before exiting. */
867 apr_pool_t *subpool = svn_pool_create(cdb->pool);
868
869 err = svn_repos_open3(&repos, cdb->repos_path, NULL, subpool, subpool);
870 if (err)
871 {
872 ap_log_perror(APLOG_MARK, APLOG_ERR, err->apr_err, cdb->pool,
873 "cleanup_deltify: error opening repository '%s'",
874 cdb->repos_path);
875 svn_error_clear(err);
876 goto cleanup;
877 }
878
879 err = svn_fs_deltify_revision(svn_repos_fs(repos),
880 cdb->revision, subpool);
881 if (err)
882 {
883 ap_log_perror(APLOG_MARK, APLOG_ERR, err->apr_err, cdb->pool,
884 "cleanup_deltify: error deltifying against revision %ld"
885 " in repository '%s'",
886 cdb->revision, cdb->repos_path);
887 svn_error_clear(err);
888 }
889
890 cleanup:
891 svn_pool_destroy(subpool);
892
893 return APR_SUCCESS;
894 }
895
896
897 /* Register the cleanup_deltify function on POOL, which should be the
898 connection pool for the request. This way the time needed for
899 deltification won't delay the response to the client.
900
901 REPOS is the repository in which deltify, and REVISION is the
902 revision against which to deltify. POOL is both the pool on which
903 to register the cleanup function and the pool that will be used for
904 temporary allocations while deltifying. */
905 static void
register_deltification_cleanup(svn_repos_t * repos,svn_revnum_t revision,apr_pool_t * pool)906 register_deltification_cleanup(svn_repos_t *repos,
907 svn_revnum_t revision,
908 apr_pool_t *pool)
909 {
910 struct cleanup_deltify_baton *cdb = apr_palloc(pool, sizeof(*cdb));
911
912 cdb->repos_path = svn_repos_path(repos, pool);
913 cdb->revision = revision;
914 cdb->pool = pool;
915
916 apr_pool_cleanup_register(pool, cdb, cleanup_deltify, apr_pool_cleanup_null);
917 }
918
919
920 dav_error *
dav_svn__checkin(dav_resource * resource,int keep_checked_out,dav_resource ** version_resource)921 dav_svn__checkin(dav_resource *resource,
922 int keep_checked_out,
923 dav_resource **version_resource)
924 {
925 svn_error_t *serr;
926 dav_error *err;
927 apr_status_t apr_err;
928 const char *uri;
929 const char *shared_activity;
930 void *data;
931
932 /* ### mod_dav has a flawed architecture, in the sense that it first
933 tries to auto-checkin the modified resource, then attempts to
934 auto-checkin the parent resource (if the parent resource was
935 auto-checked-out). Instead, the provider should be in charge:
936 mod_dav should provide a *set* of resources that need
937 auto-checkin, and the provider can decide how to do it. (One
938 txn? Many txns? Etc.) */
939
940 if (resource->type != DAV_RESOURCE_TYPE_WORKING)
941 return dav_svn__new_error_svn(resource->pool, HTTP_INTERNAL_SERVER_ERROR,
942 SVN_ERR_UNSUPPORTED_FEATURE, 0,
943 "CHECKIN called on non-working resource");
944
945 /* If the global autoversioning activity still exists, that means
946 nobody's committed it yet. */
947 apr_err = apr_pool_userdata_get(&data,
948 DAV_SVN__AUTOVERSIONING_ACTIVITY,
949 resource->info->r->pool);
950 if (apr_err)
951 return dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
952 HTTP_INTERNAL_SERVER_ERROR,
953 "Error fetching pool userdata.",
954 resource->pool);
955 shared_activity = data;
956
957 /* Try to commit the txn if it exists. */
958 if (shared_activity
959 && (strcmp(shared_activity, resource->info->root.activity_id) == 0))
960 {
961 const char *shared_txn_name;
962 const char *conflict_msg;
963 svn_revnum_t new_rev;
964
965 shared_txn_name = dav_svn__get_txn(resource->info->repos,
966 shared_activity);
967 if (! shared_txn_name)
968 return dav_svn__new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR,
969 0, 0,
970 "Cannot look up a txn_name by activity");
971
972 /* Sanity checks */
973 if (resource->info->root.txn_name
974 && (strcmp(shared_txn_name, resource->info->root.txn_name) != 0))
975 return dav_svn__new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR,
976 0, 0,
977 "Internal txn_name doesn't match "
978 "autoversioning transaction.");
979
980 if (! resource->info->root.txn)
981 /* should already be open by checkout */
982 return dav_svn__new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR,
983 0, 0,
984 "Autoversioning txn isn't open "
985 "when it should be.");
986
987 err = set_auto_revprops(resource);
988 if (err)
989 return err;
990
991 serr = svn_repos_fs_commit_txn(&conflict_msg,
992 resource->info->repos->repos,
993 &new_rev,
994 resource->info->root.txn,
995 resource->pool);
996
997 if (SVN_IS_VALID_REVNUM(new_rev))
998 {
999 if (serr)
1000 {
1001 const char *post_commit_err = svn_repos__post_commit_error_str
1002 (serr, resource->pool);
1003 ap_log_perror(APLOG_MARK, APLOG_ERR, APR_EGENERAL,
1004 resource->pool,
1005 "commit of r%ld succeeded, but an error occurred "
1006 "after the commit: '%s'",
1007 new_rev,
1008 post_commit_err);
1009 svn_error_clear(serr);
1010 serr = SVN_NO_ERROR;
1011 }
1012 }
1013 else
1014 {
1015 const char *msg;
1016 svn_error_clear(svn_fs_abort_txn(resource->info->root.txn,
1017 resource->pool));
1018
1019 /* Attempt to destroy the shared activity. */
1020 dav_svn__delete_activity(resource->info->repos, shared_activity);
1021 apr_pool_userdata_set(NULL, DAV_SVN__AUTOVERSIONING_ACTIVITY,
1022 NULL, resource->info->r->pool);
1023
1024 if (serr)
1025 {
1026 int status;
1027
1028 if (serr->apr_err == SVN_ERR_FS_CONFLICT)
1029 {
1030 status = HTTP_CONFLICT;
1031 msg = apr_psprintf(resource->pool,
1032 "A conflict occurred during the CHECKIN "
1033 "processing. The problem occurred with "
1034 "the \"%s\" resource.",
1035 conflict_msg);
1036 }
1037 else
1038 {
1039 status = HTTP_INTERNAL_SERVER_ERROR;
1040 msg = "An error occurred while committing the transaction.";
1041 }
1042
1043 return dav_svn__convert_err(serr, status, msg, resource->pool);
1044 }
1045 else
1046 {
1047 return dav_svn__new_error(resource->pool,
1048 HTTP_INTERNAL_SERVER_ERROR,
1049 0, 0,
1050 "Commit failed but there was no error "
1051 "provided.");
1052 }
1053 }
1054
1055 /* Attempt to destroy the shared activity. */
1056 dav_svn__delete_activity(resource->info->repos, shared_activity);
1057 apr_pool_userdata_set(NULL, DAV_SVN__AUTOVERSIONING_ACTIVITY,
1058 NULL, resource->info->r->pool);
1059
1060 /* Commit was successful, so schedule deltification. */
1061 register_deltification_cleanup(resource->info->repos->repos,
1062 new_rev,
1063 resource->info->r->connection->pool);
1064
1065 /* If caller wants it, return the new VR that was created by
1066 the checkin. */
1067 if (version_resource)
1068 {
1069 uri = dav_svn__build_uri(resource->info->repos,
1070 DAV_SVN__BUILD_URI_VERSION,
1071 new_rev, resource->info->repos_path,
1072 FALSE /* add_href */, resource->pool);
1073
1074 err = dav_svn__create_version_resource(version_resource, uri,
1075 resource->pool);
1076 if (err)
1077 return err;
1078 }
1079 } /* end of commit stuff */
1080
1081 /* The shared activity was either nonexistent to begin with, or it's
1082 been committed and is only now nonexistent. The resource needs
1083 to forget about it. */
1084 resource->info->root.txn_name = NULL;
1085 resource->info->root.txn = NULL;
1086
1087 /* Convert the working resource back into an regular one. */
1088 if (! keep_checked_out)
1089 {
1090 resource->info->auto_checked_out = FALSE;
1091 return dav_svn__working_to_regular_resource(resource);
1092 }
1093
1094 return NULL;
1095 }
1096
1097
1098 static dav_error *
avail_reports(const dav_resource * resource,const dav_report_elem ** reports)1099 avail_reports(const dav_resource *resource, const dav_report_elem **reports)
1100 {
1101 /* ### further restrict to the public space? */
1102 if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
1103 *reports = NULL;
1104 return NULL;
1105 }
1106
1107 *reports = dav_svn__reports_list;
1108 return NULL;
1109 }
1110
1111
1112 static int
report_label_header_allowed(const apr_xml_doc * doc)1113 report_label_header_allowed(const apr_xml_doc *doc)
1114 {
1115 return 0;
1116 }
1117
1118
1119 static dav_error *
deliver_report(request_rec * r,const dav_resource * resource,const apr_xml_doc * doc,ap_filter_t * unused)1120 deliver_report(request_rec *r,
1121 const dav_resource *resource,
1122 const apr_xml_doc *doc,
1123 ap_filter_t *unused)
1124 {
1125 int ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
1126
1127 if (doc->root->ns == ns)
1128 {
1129 dav_svn__output *output;
1130
1131 output = dav_svn__output_create(resource->info->r, resource->pool);
1132
1133 /* ### note that these report names should have symbols... */
1134
1135 if (strcmp(doc->root->name, "update-report") == 0)
1136 {
1137 return dav_svn__update_report(resource, doc, output);
1138 }
1139 else if (strcmp(doc->root->name, "log-report") == 0)
1140 {
1141 return dav_svn__log_report(resource, doc, output);
1142 }
1143 else if (strcmp(doc->root->name, "dated-rev-report") == 0)
1144 {
1145 return dav_svn__dated_rev_report(resource, doc, output);
1146 }
1147 else if (strcmp(doc->root->name, "get-locations") == 0)
1148 {
1149 return dav_svn__get_locations_report(resource, doc, output);
1150 }
1151 else if (strcmp(doc->root->name, "get-location-segments") == 0)
1152 {
1153 return dav_svn__get_location_segments_report(resource, doc, output);
1154 }
1155 else if (strcmp(doc->root->name, "file-revs-report") == 0)
1156 {
1157 return dav_svn__file_revs_report(resource, doc, output);
1158 }
1159 else if (strcmp(doc->root->name, "get-locks-report") == 0)
1160 {
1161 return dav_svn__get_locks_report(resource, doc, output);
1162 }
1163 else if (strcmp(doc->root->name, "replay-report") == 0)
1164 {
1165 return dav_svn__replay_report(resource, doc, output);
1166 }
1167 else if (strcmp(doc->root->name, SVN_DAV__MERGEINFO_REPORT) == 0)
1168 {
1169 return dav_svn__get_mergeinfo_report(resource, doc, output);
1170 }
1171 else if (strcmp(doc->root->name, "get-deleted-rev-report") == 0)
1172 {
1173 return dav_svn__get_deleted_rev_report(resource, doc, output);
1174 }
1175 else if (strcmp(doc->root->name, SVN_DAV__INHERITED_PROPS_REPORT) == 0)
1176 {
1177 return dav_svn__get_inherited_props_report(resource, doc, output);
1178 }
1179 else if (strcmp(doc->root->name, "list-report") == 0)
1180 {
1181 return dav_svn__list_report(resource, doc, output);
1182 }
1183 /* NOTE: if you add a report, don't forget to add it to the
1184 * dav_svn__reports_list[] array.
1185 */
1186 }
1187
1188 /* ### what is a good error for an unknown report? */
1189 return dav_svn__new_error_svn(resource->pool, HTTP_NOT_IMPLEMENTED,
1190 SVN_ERR_UNSUPPORTED_FEATURE, 0,
1191 "The requested report is unknown");
1192 }
1193
1194
1195 static int
can_be_activity(const dav_resource * resource)1196 can_be_activity(const dav_resource *resource)
1197 {
1198 /* If our resource is marked as auto_checked_out'd, then we allow this to
1199 * be an activity URL. Otherwise, it must be a real activity URL that
1200 * doesn't already exist.
1201 */
1202 return (resource->info->auto_checked_out ||
1203 (resource->type == DAV_RESOURCE_TYPE_ACTIVITY &&
1204 !resource->exists));
1205 }
1206
1207
1208 static dav_error *
make_activity(dav_resource * resource)1209 make_activity(dav_resource *resource)
1210 {
1211 const char *activity_id = resource->info->root.activity_id;
1212 const char *txn_name;
1213 dav_error *err;
1214
1215 /* sanity check: make sure the resource is a valid activity, in
1216 case an older mod_dav doesn't do the check for us. */
1217 if (! can_be_activity(resource))
1218 return dav_svn__new_error_svn(resource->pool, HTTP_FORBIDDEN,
1219 SVN_ERR_APMOD_MALFORMED_URI, 0,
1220 "Activities cannot be created at that "
1221 "location; query the "
1222 "DAV:activity-collection-set property");
1223
1224 err = dav_svn__create_txn(resource->info->repos, &txn_name,
1225 NULL, resource->pool);
1226 if (err != NULL)
1227 return err;
1228
1229 err = dav_svn__store_activity(resource->info->repos, activity_id, txn_name);
1230 if (err != NULL)
1231 return err;
1232
1233 /* everything is happy. update the resource */
1234 resource->info->root.txn_name = txn_name;
1235 resource->exists = 1;
1236 return NULL;
1237 }
1238
1239
1240 dav_error *
dav_svn__build_lock_hash(apr_hash_t ** locks,request_rec * r,const char * path_prefix,apr_pool_t * pool)1241 dav_svn__build_lock_hash(apr_hash_t **locks,
1242 request_rec *r,
1243 const char *path_prefix,
1244 apr_pool_t *pool)
1245 {
1246 apr_status_t apr_err;
1247 dav_error *derr;
1248 void *data = NULL;
1249 apr_xml_doc *doc = NULL;
1250 apr_xml_elem *child, *lockchild;
1251 int ns;
1252 apr_hash_t *hash = apr_hash_make(pool);
1253
1254 /* Grab the request body out of r->pool, as it contains all of the
1255 lock tokens. It should have been stashed already by our custom
1256 input filter. */
1257 apr_err = apr_pool_userdata_get(&data, "svn-request-body", r->pool);
1258 if (apr_err)
1259 return dav_svn__convert_err(svn_error_create(apr_err, 0, NULL),
1260 HTTP_INTERNAL_SERVER_ERROR,
1261 "Error fetching pool userdata.",
1262 pool);
1263 doc = data;
1264 if (! doc)
1265 {
1266 *locks = hash;
1267 return NULL;
1268 }
1269
1270 /* Sanity check. */
1271 ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE);
1272 if (ns == -1)
1273 {
1274 /* If there's no svn: namespace in the body, then there are
1275 definitely no lock-tokens to harvest. This is likely a
1276 request from an old client. */
1277 *locks = hash;
1278 return NULL;
1279 }
1280
1281 if ((doc->root->ns == ns)
1282 && (strcmp(doc->root->name, "lock-token-list") == 0))
1283 {
1284 child = doc->root;
1285 }
1286 else
1287 {
1288 /* Search doc's children until we find the <lock-token-list>. */
1289 for (child = doc->root->first_child; child != NULL; child = child->next)
1290 {
1291 /* if this element isn't one of ours, then skip it */
1292 if (child->ns != ns)
1293 continue;
1294
1295 if (strcmp(child->name, "lock-token-list") == 0)
1296 break;
1297 }
1298 }
1299
1300 /* Did we find what we were looking for? */
1301 if (! child)
1302 {
1303 *locks = hash;
1304 return NULL;
1305 }
1306
1307 /* Then look for N different <lock> structures within. */
1308 for (lockchild = child->first_child; lockchild != NULL;
1309 lockchild = lockchild->next)
1310 {
1311 const char *lockpath = NULL, *locktoken = NULL;
1312 apr_xml_elem *lfchild;
1313
1314 if (strcmp(lockchild->name, "lock") != 0)
1315 continue;
1316
1317 for (lfchild = lockchild->first_child; lfchild != NULL;
1318 lfchild = lfchild->next)
1319 {
1320 if (strcmp(lfchild->name, "lock-path") == 0)
1321 {
1322 const char *cdata = dav_xml_get_cdata(lfchild, pool, 0);
1323 if ((derr = dav_svn__test_canonical(cdata, pool)))
1324 return derr;
1325
1326 /* Create an absolute fs-path */
1327 lockpath = svn_fspath__join(path_prefix, cdata, pool);
1328 if (lockpath && locktoken)
1329 {
1330 svn_hash_sets(hash, lockpath, locktoken);
1331 lockpath = NULL;
1332 locktoken = NULL;
1333 }
1334 }
1335 else if (strcmp(lfchild->name, "lock-token") == 0)
1336 {
1337 locktoken = dav_xml_get_cdata(lfchild, pool, 1);
1338 if (lockpath && *locktoken)
1339 {
1340 svn_hash_sets(hash, lockpath, locktoken);
1341 lockpath = NULL;
1342 locktoken = NULL;
1343 }
1344 }
1345 }
1346 }
1347
1348 *locks = hash;
1349 return NULL;
1350 }
1351
1352
1353 dav_error *
dav_svn__push_locks(dav_resource * resource,apr_hash_t * locks,apr_pool_t * pool)1354 dav_svn__push_locks(dav_resource *resource,
1355 apr_hash_t *locks,
1356 apr_pool_t *pool)
1357 {
1358 svn_fs_access_t *fsaccess;
1359 apr_hash_index_t *hi;
1360 svn_error_t *serr;
1361
1362 serr = svn_fs_get_access(&fsaccess, resource->info->repos->fs);
1363 if (serr || !fsaccess)
1364 {
1365 /* If an authenticated user name was attached to the request,
1366 then dav_svn_get_resource() should have already noticed and
1367 created an fs_access_t in the filesystem. */
1368 if (serr == NULL)
1369 serr = svn_error_create(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, NULL);
1370 return dav_svn__sanitize_error(serr, "Lock token(s) in request, but "
1371 "missing an user name", HTTP_BAD_REQUEST,
1372 resource->info->r);
1373 }
1374
1375 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1376 {
1377 const char *path, *token;
1378 const void *key;
1379 void *val;
1380 apr_hash_this(hi, &key, NULL, &val);
1381 path = key, token = val;
1382
1383 serr = svn_fs_access_add_lock_token2(fsaccess, path, token);
1384 if (serr)
1385 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1386 "Error pushing token into filesystem.",
1387 pool);
1388 }
1389
1390 return NULL;
1391 }
1392
1393 /* Implements svn_fs_lock_callback_t. */
1394 static svn_error_t *
unlock_many_cb(void * lock_baton,const char * path,const svn_lock_t * lock,svn_error_t * fs_err,apr_pool_t * pool)1395 unlock_many_cb(void *lock_baton,
1396 const char *path,
1397 const svn_lock_t *lock,
1398 svn_error_t *fs_err,
1399 apr_pool_t *pool)
1400 {
1401 request_rec *r = lock_baton;
1402
1403 if (fs_err)
1404 ap_log_rerror(APLOG_MARK, APLOG_ERR, fs_err->apr_err, r,
1405 "%s", fs_err->message);
1406
1407 return SVN_NO_ERROR;
1408 }
1409
1410
1411 /* Helper for merge(). Free every lock in LOCKS. The locks
1412 live in REPOS. Log any errors for REQUEST. Use POOL for temporary
1413 work.*/
1414 static svn_error_t *
release_locks(apr_hash_t * locks,svn_repos_t * repos,request_rec * r,apr_pool_t * pool)1415 release_locks(apr_hash_t *locks,
1416 svn_repos_t *repos,
1417 request_rec *r,
1418 apr_pool_t *pool)
1419 {
1420 apr_pool_t *subpool = svn_pool_create(pool);
1421 svn_error_t *err;
1422
1423 err = svn_repos_fs_unlock_many(repos, locks, FALSE, unlock_many_cb, r,
1424 subpool, subpool);
1425
1426 if (err) /* If we got an error, just log it and move along. */
1427 ap_log_rerror(APLOG_MARK, APLOG_ERR, err->apr_err, r,
1428 "%s", err->message);
1429 svn_error_clear(err);
1430
1431 svn_pool_destroy(subpool);
1432
1433 return SVN_NO_ERROR;
1434 }
1435
1436
1437 static dav_error *
merge(dav_resource * target,dav_resource * source,int no_auto_merge,int no_checkout,apr_xml_elem * prop_elem,ap_filter_t * unused)1438 merge(dav_resource *target,
1439 dav_resource *source,
1440 int no_auto_merge,
1441 int no_checkout,
1442 apr_xml_elem *prop_elem,
1443 ap_filter_t *unused)
1444 {
1445 apr_pool_t *pool;
1446 dav_error *err;
1447 svn_fs_txn_t *txn;
1448 const char *conflict;
1449 svn_error_t *serr;
1450 const char *post_commit_err = NULL;
1451 svn_revnum_t new_rev;
1452 apr_hash_t *locks;
1453 svn_boolean_t disable_merge_response = FALSE;
1454 dav_svn__output *output;
1455
1456 /* We'll use the target's pool for our operation. We happen to know that
1457 it matches the request pool, which (should) have the proper lifetime. */
1458 pool = target->pool;
1459
1460 /* ### what to verify on the target? */
1461
1462 /* ### anything else for the source? */
1463 if (! (source->type == DAV_RESOURCE_TYPE_ACTIVITY
1464 || (source->type == DAV_RESOURCE_TYPE_PRIVATE
1465 && source->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION)))
1466 {
1467 return dav_svn__new_error_svn(pool, HTTP_METHOD_NOT_ALLOWED,
1468 SVN_ERR_INCORRECT_PARAMS, 0,
1469 "MERGE can only be performed using an "
1470 "activity or transaction resource as the "
1471 "source");
1472 }
1473 if (! source->exists)
1474 {
1475 return dav_svn__new_error_svn(pool, HTTP_METHOD_NOT_ALLOWED,
1476 SVN_ERR_INCORRECT_PARAMS, 0,
1477 "MERGE activity or transaction resource "
1478 "does not exist");
1479 }
1480
1481 /* Before attempting the final commit, we need to push any incoming
1482 lock-tokens into the filesystem's access_t. Normally they come
1483 in via 'If:' header, and dav_svn_get_resource() automatically
1484 notices them and does this work for us. In the case of MERGE,
1485 however, svn clients are sending them in the request body. */
1486
1487 err = dav_svn__build_lock_hash(&locks, target->info->r,
1488 target->info->repos_path,
1489 pool);
1490 if (err != NULL)
1491 return err;
1492
1493 if (apr_hash_count(locks))
1494 {
1495 err = dav_svn__push_locks(source, locks, pool);
1496 if (err != NULL)
1497 return err;
1498 }
1499
1500 /* We will ignore no_auto_merge and no_checkout. We can't do those, but the
1501 client has no way to assert that we *should* do them. This should be fine
1502 because, presumably, the client has no way to do the various checkouts
1503 and things that would necessitate an auto-merge or checkout during the
1504 MERGE processing. */
1505
1506 /* open the transaction that we're going to commit. */
1507 if ((err = open_txn(&txn, source->info->repos->fs,
1508 source->info->root.txn_name, pool)) != NULL)
1509 return err;
1510
1511 /* all righty... commit the bugger. */
1512 serr = svn_repos_fs_commit_txn(&conflict, source->info->repos->repos,
1513 &new_rev, txn, pool);
1514
1515 /* ### TODO: Figure out if the MERGE response can grow a means by
1516 which to marshal back both the success of the commit (and its
1517 commit info) and the failure of the post-commit hook. */
1518 if (SVN_IS_VALID_REVNUM(new_rev))
1519 {
1520 if (serr)
1521 {
1522 /* ### Any error from svn_fs_commit_txn() itself, and not
1523 ### the post-commit script, should be reported to the
1524 ### client some other way than hijacking the post-commit
1525 ### error message.*/
1526 post_commit_err = svn_repos__post_commit_error_str(serr, pool);
1527 ap_log_perror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, pool,
1528 "commit of r%ld succeeded, but an error occurred "
1529 "after the commit: '%s'",
1530 new_rev,
1531 post_commit_err);
1532 svn_error_clear(serr);
1533 serr = SVN_NO_ERROR;
1534 }
1535
1536 /* HTTPv2 doesn't send DELETE after a successful MERGE so if
1537 using the optional vtxn name mapping then delete it here. */
1538 if (source->info->root.vtxn_name)
1539 dav_svn__delete_activity(source->info->repos,
1540 source->info->root.vtxn_name);
1541 }
1542 else
1543 {
1544 svn_error_clear(svn_fs_abort_txn(txn, pool));
1545
1546 if (serr)
1547 {
1548 const char *msg;
1549 int status;
1550
1551 if (serr->apr_err == SVN_ERR_FS_CONFLICT)
1552 {
1553 status = HTTP_CONFLICT;
1554 /* ### we need to convert the conflict path into a URI */
1555 msg = apr_psprintf(pool,
1556 "A conflict occurred during the MERGE "
1557 "processing. The problem occurred with the "
1558 "\"%s\" resource.",
1559 conflict);
1560 }
1561 else
1562 {
1563 status = HTTP_INTERNAL_SERVER_ERROR;
1564 msg = "An error occurred while committing the transaction.";
1565 }
1566
1567 return dav_svn__convert_err(serr, status, msg, pool);
1568 }
1569 else
1570 {
1571 return dav_svn__new_error(pool,
1572 HTTP_INTERNAL_SERVER_ERROR,
1573 0, 0,
1574 "Commit failed but there was no error "
1575 "provided.");
1576 }
1577 }
1578
1579 /* Commit was successful, so schedule deltification. */
1580 register_deltification_cleanup(source->info->repos->repos, new_rev,
1581 source->info->r->connection->pool);
1582
1583 /* We've detected a 'high level' svn action to log. */
1584 dav_svn__operational_log(target->info,
1585 svn_log__commit(new_rev, target->info->r->pool));
1586
1587 /* Since the commit was successful, the txn ID is no longer valid.
1588 If we're using activities, store an empty txn ID in the activity
1589 database so that when the client deletes the activity, we don't
1590 try to open and abort the transaction. */
1591 if (source->type == DAV_RESOURCE_TYPE_ACTIVITY)
1592 {
1593 err = dav_svn__store_activity(source->info->repos,
1594 source->info->root.activity_id, "");
1595 if (err != NULL)
1596 return err;
1597 }
1598
1599 /* Check the dav_resource->info area for information about the
1600 special X-SVN-Options: header that may have come in the http
1601 request. */
1602 if (source->info->svn_client_options != NULL)
1603 {
1604 /* The client might want us to release all locks sent in the
1605 MERGE request. */
1606 if ((NULL != (ap_strstr_c(source->info->svn_client_options,
1607 SVN_DAV_OPTION_RELEASE_LOCKS)))
1608 && apr_hash_count(locks))
1609 {
1610 serr = release_locks(locks, source->info->repos->repos,
1611 source->info->r, pool);
1612 if (serr != NULL)
1613 return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
1614 "Error releasing locks", pool);
1615 }
1616
1617 /* The client might want us to disable the merge response altogether. */
1618 if (NULL != (ap_strstr_c(source->info->svn_client_options,
1619 SVN_DAV_OPTION_NO_MERGE_RESPONSE)))
1620 disable_merge_response = TRUE;
1621 }
1622
1623 /* process the response for the new revision. */
1624 output = dav_svn__output_create(target->info->r, pool);
1625 return dav_svn__merge_response(output, source->info->repos, new_rev,
1626 post_commit_err, prop_elem,
1627 disable_merge_response, pool);
1628 }
1629
1630
1631 const dav_hooks_vsn dav_svn__hooks_vsn = {
1632 get_vsn_options,
1633 get_option,
1634 versionable,
1635 auto_versionable,
1636 vsn_control,
1637 dav_svn__checkout,
1638 uncheckout,
1639 dav_svn__checkin,
1640 avail_reports,
1641 report_label_header_allowed,
1642 deliver_report,
1643 NULL, /* update */
1644 NULL, /* add_label */
1645 NULL, /* remove_label */
1646 NULL, /* can_be_workspace */
1647 NULL, /* make_workspace */
1648 can_be_activity,
1649 make_activity,
1650 merge,
1651 };
1652