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