1 /*
2  * commit.c :  entry point for commit RA functions for ra_serf
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_uri.h>
25 #include <serf.h>
26 
27 #include "svn_hash.h"
28 #include "svn_pools.h"
29 #include "svn_ra.h"
30 #include "svn_dav.h"
31 #include "svn_xml.h"
32 #include "svn_config.h"
33 #include "svn_delta.h"
34 #include "svn_base64.h"
35 #include "svn_dirent_uri.h"
36 #include "svn_path.h"
37 #include "svn_props.h"
38 
39 #include "svn_private_config.h"
40 #include "private/svn_dep_compat.h"
41 #include "private/svn_fspath.h"
42 #include "private/svn_skel.h"
43 
44 #include "ra_serf.h"
45 #include "../libsvn_ra/ra_loader.h"
46 
47 
48 /* Baton passed back with the commit editor. */
49 typedef struct commit_context_t {
50   /* Pool for our commit. */
51   apr_pool_t *pool;
52 
53   svn_ra_serf__session_t *session;
54 
55   apr_hash_t *revprop_table;
56 
57   svn_commit_callback2_t callback;
58   void *callback_baton;
59 
60   apr_hash_t *lock_tokens;
61   svn_boolean_t keep_locks;
62   apr_hash_t *deleted_entries;   /* deleted files (for delete+add detection) */
63 
64   /* HTTP v2 stuff */
65   const char *txn_url;           /* txn URL (!svn/txn/TXN_NAME) */
66   const char *txn_root_url;      /* commit anchor txn root URL */
67 
68   /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
69   const char *activity_url;      /* activity base URL... */
70   const char *baseline_url;      /* the working-baseline resource */
71   const char *checked_in_url;    /* checked-in root to base CHECKOUTs from */
72   const char *vcc_url;           /* vcc url */
73 
74   int open_batons;               /* Number of open batons */
75 } commit_context_t;
76 
77 #define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
78 
79 /* Structure associated with a PROPPATCH request. */
80 typedef struct proppatch_context_t {
81   apr_pool_t *pool;
82 
83   const char *relpath;
84   const char *path;
85 
86   commit_context_t *commit_ctx;
87 
88   /* Changed properties. const char * -> svn_prop_t * */
89   apr_hash_t *prop_changes;
90 
91   /* Same, for the old value, or NULL. */
92   apr_hash_t *old_props;
93 
94   /* In HTTP v2, this is the file/directory version we think we're changing. */
95   svn_revnum_t base_revision;
96 
97 } proppatch_context_t;
98 
99 typedef struct delete_context_t {
100   const char *relpath;
101 
102   svn_revnum_t revision;
103 
104   commit_context_t *commit_ctx;
105 
106   svn_boolean_t non_recursive_if; /* Only create a non-recursive If header */
107 } delete_context_t;
108 
109 /* Represents a directory. */
110 typedef struct dir_context_t {
111   /* Pool for our directory. */
112   apr_pool_t *pool;
113 
114   /* The root commit we're in progress for. */
115   commit_context_t *commit_ctx;
116 
117   /* URL to operate against (used for CHECKOUT and PROPPATCH before
118      HTTP v2, for PROPPATCH in HTTP v2).  */
119   const char *url;
120 
121   /* Is this directory being added?  (Otherwise, just opened.) */
122   svn_boolean_t added;
123 
124   /* Our parent */
125   struct dir_context_t *parent_dir;
126 
127   /* The directory name; if "", we're the 'root' */
128   const char *relpath;
129 
130   /* The basename of the directory. "" for the 'root' */
131   const char *name;
132 
133   /* The base revision of the dir. */
134   svn_revnum_t base_revision;
135 
136   const char *copy_path;
137   svn_revnum_t copy_revision;
138 
139   /* Changed properties (const char * -> svn_prop_t *) */
140   apr_hash_t *prop_changes;
141 
142   /* The checked-out working resource for this directory.  May be NULL; if so
143      call checkout_dir() first.  */
144   const char *working_url;
145 } dir_context_t;
146 
147 /* Represents a file to be committed. */
148 typedef struct file_context_t {
149   /* Pool for our file. */
150   apr_pool_t *pool;
151 
152   /* The root commit we're in progress for. */
153   commit_context_t *commit_ctx;
154 
155   /* Is this file being added?  (Otherwise, just opened.) */
156   svn_boolean_t added;
157 
158   dir_context_t *parent_dir;
159 
160   const char *relpath;
161   const char *name;
162 
163   /* The checked-out working resource for this file. */
164   const char *working_url;
165 
166   /* The base revision of the file. */
167   svn_revnum_t base_revision;
168 
169   /* Copy path and revision */
170   const char *copy_path;
171   svn_revnum_t copy_revision;
172 
173   /* Stream for collecting the svndiff. */
174   svn_stream_t *stream;
175 
176   /* Buffer holding the svndiff (can spill to disk). */
177   svn_ra_serf__request_body_t *svndiff;
178 
179   /* Did we send the svndiff in apply_textdelta_stream()? */
180   svn_boolean_t svndiff_sent;
181 
182   /* Our base checksum as reported by the WC. */
183   const char *base_checksum;
184 
185   /* Our resulting checksum as reported by the WC. */
186   const char *result_checksum;
187 
188   /* Our resulting checksum as reported by the server. */
189   svn_checksum_t *remote_result_checksum;
190 
191   /* Changed properties (const char * -> svn_prop_t *) */
192   apr_hash_t *prop_changes;
193 
194   /* URL to PUT the file at. */
195   const char *url;
196 
197 } file_context_t;
198 
199 
200 /* Setup routines and handlers for various requests we'll invoke. */
201 
202 /* Implements svn_ra_serf__request_body_delegate_t */
203 static svn_error_t *
create_checkout_body(serf_bucket_t ** bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)204 create_checkout_body(serf_bucket_t **bkt,
205                      void *baton,
206                      serf_bucket_alloc_t *alloc,
207                      apr_pool_t *pool /* request pool */,
208                      apr_pool_t *scratch_pool)
209 {
210   const char *activity_url = baton;
211   serf_bucket_t *body_bkt;
212 
213   body_bkt = serf_bucket_aggregate_create(alloc);
214 
215   svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
216   svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
217                                     "xmlns:D", "DAV:",
218                                     SVN_VA_NULL);
219   svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set",
220                                     SVN_VA_NULL);
221   svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href",
222                                     SVN_VA_NULL);
223 
224   SVN_ERR_ASSERT(activity_url != NULL);
225   svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
226                                      activity_url,
227                                      strlen(activity_url));
228 
229   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
230   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
231   svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc,
232                                      "D:apply-to-version", SVN_VA_NULL);
233   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
234 
235   *bkt = body_bkt;
236   return SVN_NO_ERROR;
237 }
238 
239 
240 /* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
241    given COMMIT_CTX. The resulting working resource will be returned in
242    *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
243    are performed in SCRATCH_POOL.
244 
245    ### are these URLs actually repos relpath values? or fspath? or maybe
246    ### the abspath portion of the full URL.
247 
248    This function operates synchronously.
249 
250    Strictly speaking, we could perform "all" of the CHECKOUT requests
251    when the commit starts, and only block when we need a specific
252    answer. Or, at a minimum, send off these individual requests async
253    and block when we need the answer (eg PUT or PROPPATCH).
254 
255    However: the investment to speed this up is not worthwhile, given
256    that CHECKOUT (and the related round trip) is completely obviated
257    in HTTPv2.
258 */
259 static svn_error_t *
checkout_node(const char ** working_url,const commit_context_t * commit_ctx,const char * node_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)260 checkout_node(const char **working_url,
261               const commit_context_t *commit_ctx,
262               const char *node_url,
263               apr_pool_t *result_pool,
264               apr_pool_t *scratch_pool)
265 {
266   svn_ra_serf__handler_t *handler;
267   apr_status_t status;
268   apr_uri_t uri;
269 
270   /* HANDLER_POOL is the scratch pool since we don't need to remember
271      anything from the handler. We just want the working resource.  */
272   handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
273 
274   handler->body_delegate = create_checkout_body;
275   handler->body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
276   handler->body_type = "text/xml";
277 
278   handler->response_handler = svn_ra_serf__expect_empty_body;
279   handler->response_baton = handler;
280 
281   handler->method = "CHECKOUT";
282   handler->path = node_url;
283 
284   SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
285 
286   if (handler->sline.code != 201)
287     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
288 
289   if (handler->location == NULL)
290     return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
291                             _("No Location header received"));
292 
293   /* We only want the path portion of the Location header.
294      (code.google.com sometimes returns an 'http:' scheme for an
295      'https:' transaction ... we'll work around that by stripping the
296      scheme, host, and port here and re-adding the correct ones
297      later.  */
298   status = apr_uri_parse(scratch_pool, handler->location, &uri);
299   if (status)
300     return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
301                             _("Error parsing Location header value"));
302 
303   *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
304 
305   return SVN_NO_ERROR;
306 }
307 
308 
309 /* This is a wrapper around checkout_node() (which see for
310    documentation) which simply retries the CHECKOUT request when it
311    fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
312    server.
313 
314    See https://issues.apache.org/jira/browse/SVN-4127 for
315    details.
316 */
317 static svn_error_t *
retry_checkout_node(const char ** working_url,const commit_context_t * commit_ctx,const char * node_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)318 retry_checkout_node(const char **working_url,
319                     const commit_context_t *commit_ctx,
320                     const char *node_url,
321                     apr_pool_t *result_pool,
322                     apr_pool_t *scratch_pool)
323 {
324   svn_error_t *err = SVN_NO_ERROR;
325   int retry_count = 5; /* Magic, arbitrary number. */
326 
327   do
328     {
329       svn_error_clear(err);
330 
331       err = checkout_node(working_url, commit_ctx, node_url,
332                           result_pool, scratch_pool);
333 
334       /* There's a small chance of a race condition here if Apache is
335          experiencing heavy commit concurrency or if the network has
336          long latency.  It's possible that the value of HEAD changed
337          between the time we fetched the latest baseline and the time
338          we try to CHECKOUT that baseline.  If that happens, Apache
339          will throw us a BAD_BASELINE error (deltaV says you can only
340          checkout the latest baseline).  We just ignore that specific
341          error and retry a few times, asking for the latest baseline
342          again. */
343       if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
344         return svn_error_trace(err);
345     }
346   while (err && retry_count--);
347 
348   return svn_error_trace(err);
349 }
350 
351 
352 static svn_error_t *
checkout_dir(dir_context_t * dir,apr_pool_t * scratch_pool)353 checkout_dir(dir_context_t *dir,
354              apr_pool_t *scratch_pool)
355 {
356   dir_context_t *c_dir = dir;
357   const char *checkout_url;
358   const char **working;
359 
360   if (dir->working_url)
361     {
362       return SVN_NO_ERROR;
363     }
364 
365   /* Is this directory or one of our parent dirs newly added?
366    * If so, we're already implicitly checked out. */
367   while (c_dir)
368     {
369       if (c_dir->added)
370         {
371           /* Calculate the working_url by skipping the shared ancestor between
372            * the c_dir_parent->relpath and dir->relpath. This is safe since an
373            * add is guaranteed to have a parent that is checked out. */
374           dir_context_t *c_dir_parent = c_dir->parent_dir;
375           const char *relpath = svn_relpath_skip_ancestor(c_dir_parent->relpath,
376                                                           dir->relpath);
377 
378           /* Implicitly checkout this dir now. */
379           SVN_ERR_ASSERT(c_dir_parent->working_url);
380           dir->working_url = svn_path_url_add_component2(
381                                    c_dir_parent->working_url,
382                                    relpath, dir->pool);
383           return SVN_NO_ERROR;
384         }
385       c_dir = c_dir->parent_dir;
386     }
387 
388   /* We could be called twice for the root: once to checkout the baseline;
389    * once to checkout the directory itself if we need to do so.
390    * Note: CHECKOUT_URL should live longer than HANDLER.
391    */
392   if (!dir->parent_dir && !dir->commit_ctx->baseline_url)
393     {
394       checkout_url = dir->commit_ctx->vcc_url;
395       working = &dir->commit_ctx->baseline_url;
396     }
397   else
398     {
399       checkout_url = dir->url;
400       working = &dir->working_url;
401     }
402 
403   /* Checkout our directory into the activity URL now. */
404   return svn_error_trace(retry_checkout_node(working, dir->commit_ctx,
405                                              checkout_url,
406                                              dir->pool, scratch_pool));
407 }
408 
409 
410 /* Set *CHECKED_IN_URL to the appropriate DAV version url for
411  * RELPATH (relative to the root of SESSION).
412  *
413  * Try to find this version url in three ways:
414  * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
415  * version url from the working copy properties.
416  * Second, if the version url of the parent directory PARENT_VSN_URL is
417  * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
418  * RELPATH.
419  * Else, fetch the version url for the root of SESSION using CONN and
420  * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
421  * with RELPATH.
422  *
423  * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
424  * temporary allocation.
425  */
426 static svn_error_t *
get_version_url(const char ** checked_in_url,svn_ra_serf__session_t * session,const char * relpath,svn_revnum_t base_revision,const char * parent_vsn_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)427 get_version_url(const char **checked_in_url,
428                 svn_ra_serf__session_t *session,
429                 const char *relpath,
430                 svn_revnum_t base_revision,
431                 const char *parent_vsn_url,
432                 apr_pool_t *result_pool,
433                 apr_pool_t *scratch_pool)
434 {
435   const char *root_checkout;
436 
437   if (session->wc_callbacks->get_wc_prop)
438     {
439       const svn_string_t *current_version;
440 
441       SVN_ERR(session->wc_callbacks->get_wc_prop(
442                 session->wc_callback_baton,
443                 relpath,
444                 SVN_RA_SERF__WC_CHECKED_IN_URL,
445                 &current_version, scratch_pool));
446 
447       if (current_version)
448         {
449           *checked_in_url =
450             svn_urlpath__canonicalize(current_version->data, result_pool);
451           return SVN_NO_ERROR;
452         }
453     }
454 
455   if (parent_vsn_url)
456     {
457       root_checkout = parent_vsn_url;
458     }
459   else
460     {
461       const char *propfind_url;
462 
463       if (SVN_IS_VALID_REVNUM(base_revision))
464         {
465           /* mod_dav_svn can't handle the "Label:" header that
466              svn_ra_serf__deliver_props() is going to try to use for
467              this lookup, so we'll do things the hard(er) way, by
468              looking up the version URL from a resource in the
469              baseline collection. */
470           SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
471                                               NULL /* latest_revnum */,
472                                               session,
473                                               NULL /* url */, base_revision,
474                                               scratch_pool, scratch_pool));
475         }
476       else
477         {
478           propfind_url = session->session_url.path;
479         }
480 
481       SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout, session,
482                                           propfind_url, base_revision,
483                                           "checked-in",
484                                           scratch_pool, scratch_pool));
485       if (!root_checkout)
486         return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
487                                  _("Path '%s' not present"),
488                                  session->session_url.path);
489 
490       root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
491     }
492 
493   *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
494                                                 result_pool);
495 
496   return SVN_NO_ERROR;
497 }
498 
499 static svn_error_t *
checkout_file(file_context_t * file,apr_pool_t * scratch_pool)500 checkout_file(file_context_t *file,
501               apr_pool_t *scratch_pool)
502 {
503   dir_context_t *parent_dir = file->parent_dir;
504   const char *checkout_url;
505 
506   /* Is one of our parent dirs newly added?  If so, we're already
507    * implicitly checked out.
508    */
509   while (parent_dir)
510     {
511       if (parent_dir->added)
512         {
513           /* Implicitly checkout this file now. */
514           SVN_ERR_ASSERT(parent_dir->working_url);
515           file->working_url = svn_path_url_add_component2(
516                                     parent_dir->working_url,
517                                     svn_relpath_skip_ancestor(
518                                       parent_dir->relpath, file->relpath),
519                                     file->pool);
520           return SVN_NO_ERROR;
521         }
522       parent_dir = parent_dir->parent_dir;
523     }
524 
525   SVN_ERR(get_version_url(&checkout_url,
526                           file->commit_ctx->session,
527                           file->relpath, file->base_revision,
528                           NULL, scratch_pool, scratch_pool));
529 
530   /* Checkout our file into the activity URL now. */
531   return svn_error_trace(retry_checkout_node(&file->working_url,
532                                              file->commit_ctx, checkout_url,
533                                               file->pool, scratch_pool));
534 }
535 
536 /* Helper function for proppatch_walker() below. */
537 static svn_error_t *
get_encoding_and_cdata(const char ** encoding_p,const svn_string_t ** encoded_value_p,serf_bucket_alloc_t * alloc,const svn_string_t * value,apr_pool_t * result_pool,apr_pool_t * scratch_pool)538 get_encoding_and_cdata(const char **encoding_p,
539                        const svn_string_t **encoded_value_p,
540                        serf_bucket_alloc_t *alloc,
541                        const svn_string_t *value,
542                        apr_pool_t *result_pool,
543                        apr_pool_t *scratch_pool)
544 {
545   if (value == NULL)
546     {
547       *encoding_p = NULL;
548       *encoded_value_p = NULL;
549       return SVN_NO_ERROR;
550     }
551 
552   /* If a property is XML-safe, XML-encode it.  Else, base64-encode
553      it. */
554   if (svn_xml_is_xml_safe(value->data, value->len))
555     {
556       svn_stringbuf_t *xml_esc = NULL;
557       svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
558       *encoding_p = NULL;
559       *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
560     }
561   else
562     {
563       *encoding_p = "base64";
564       *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
565     }
566 
567   return SVN_NO_ERROR;
568 }
569 
570 /* Helper for create_proppatch_body. Writes per property xml to body */
571 static svn_error_t *
write_prop_xml(const proppatch_context_t * proppatch,serf_bucket_t * body_bkt,serf_bucket_alloc_t * alloc,const svn_prop_t * prop,apr_pool_t * result_pool,apr_pool_t * scratch_pool)572 write_prop_xml(const proppatch_context_t *proppatch,
573                serf_bucket_t *body_bkt,
574                serf_bucket_alloc_t *alloc,
575                const svn_prop_t *prop,
576                apr_pool_t *result_pool,
577                apr_pool_t *scratch_pool)
578 {
579   serf_bucket_t *cdata_bkt;
580   const char *encoding;
581   const svn_string_t *encoded_value;
582   const char *prop_name;
583   const svn_prop_t *old_prop;
584 
585   SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, prop->value,
586                                  result_pool, scratch_pool));
587   if (encoded_value)
588     {
589       cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
590                                                 encoded_value->len,
591                                                 alloc);
592     }
593   else
594     {
595       cdata_bkt = NULL;
596     }
597 
598   /* Use the namespace prefix instead of adding the xmlns attribute to support
599      property names containing ':' */
600   if (strncmp(prop->name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
601     {
602       prop_name = apr_pstrcat(result_pool,
603                               "S:", prop->name + sizeof(SVN_PROP_PREFIX) - 1,
604                               SVN_VA_NULL);
605     }
606   else
607     {
608       prop_name = apr_pstrcat(result_pool,
609                               "C:", prop->name,
610                               SVN_VA_NULL);
611     }
612 
613   if (cdata_bkt)
614     svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
615                                       "V:encoding", encoding,
616                                       SVN_VA_NULL);
617   else
618     svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
619                                       "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
620                                       SVN_VA_NULL);
621 
622   old_prop = proppatch->old_props
623                           ? svn_hash_gets(proppatch->old_props, prop->name)
624                           : NULL;
625   if (old_prop)
626     {
627       const char *encoding2;
628       const svn_string_t *encoded_value2;
629       serf_bucket_t *cdata_bkt2;
630 
631       SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
632                                      alloc, old_prop->value,
633                                      result_pool, scratch_pool));
634 
635       if (encoded_value2)
636         {
637           cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
638                                                      encoded_value2->len,
639                                                      alloc);
640         }
641       else
642         {
643           cdata_bkt2 = NULL;
644         }
645 
646       if (cdata_bkt2)
647         svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
648                                           "V:" SVN_DAV__OLD_VALUE,
649                                           "V:encoding", encoding2,
650                                           SVN_VA_NULL);
651       else
652         svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
653                                           "V:" SVN_DAV__OLD_VALUE,
654                                           "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
655                                           SVN_VA_NULL);
656 
657       if (cdata_bkt2)
658         serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
659 
660       svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
661                                          "V:" SVN_DAV__OLD_VALUE);
662     }
663   if (cdata_bkt)
664     serf_bucket_aggregate_append(body_bkt, cdata_bkt);
665   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
666 
667   return SVN_NO_ERROR;
668 }
669 
670 /* Possible add the lock-token "If:" precondition header to HEADERS if
671    an examination of COMMIT_CTX and RELPATH indicates that this is the
672    right thing to do.
673 
674    Generally speaking, if the client provided a lock token for
675    RELPATH, it's the right thing to do.  There is a notable instance
676    where this is not the case, however.  If the file at RELPATH was
677    explicitly deleted in this commit already, then mod_dav removed its
678    lock token when it fielded the DELETE request, so we don't want to
679    set the lock precondition again.  (See
680    https://issues.apache.org/jira/browse/SVN-3674 for details.)
681 */
682 static svn_error_t *
maybe_set_lock_token_header(serf_bucket_t * headers,commit_context_t * commit_ctx,const char * relpath,apr_pool_t * pool)683 maybe_set_lock_token_header(serf_bucket_t *headers,
684                             commit_context_t *commit_ctx,
685                             const char *relpath,
686                             apr_pool_t *pool)
687 {
688   const char *token;
689 
690   if (! commit_ctx->lock_tokens)
691     return SVN_NO_ERROR;
692 
693   if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
694     {
695       token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
696       if (token)
697         {
698           const char *token_header;
699           const char *token_uri;
700           apr_uri_t uri = commit_ctx->session->session_url;
701 
702           /* Supplying the optional URI affects apache response when
703              the lock is broken, see issue 4369.  When present any URI
704              must be absolute (RFC 2518 9.4). */
705           uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
706                                                          pool);
707           token_uri = apr_uri_unparse(pool, &uri, 0);
708 
709           token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
710                                      SVN_VA_NULL);
711           serf_bucket_headers_set(headers, "If", token_header);
712         }
713     }
714 
715   return SVN_NO_ERROR;
716 }
717 
718 static svn_error_t *
setup_proppatch_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)719 setup_proppatch_headers(serf_bucket_t *headers,
720                         void *baton,
721                         apr_pool_t *pool /* request pool */,
722                         apr_pool_t *scratch_pool)
723 {
724   proppatch_context_t *proppatch = baton;
725 
726   if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
727     {
728       serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
729                               apr_psprintf(pool, "%ld",
730                                            proppatch->base_revision));
731     }
732 
733   if (proppatch->relpath && proppatch->commit_ctx)
734     SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit_ctx,
735                                         proppatch->relpath, pool));
736 
737   return SVN_NO_ERROR;
738 }
739 
740 
741 /* Implements svn_ra_serf__request_body_delegate_t */
742 static svn_error_t *
create_proppatch_body(serf_bucket_t ** bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)743 create_proppatch_body(serf_bucket_t **bkt,
744                       void *baton,
745                       serf_bucket_alloc_t *alloc,
746                       apr_pool_t *pool /* request pool */,
747                       apr_pool_t *scratch_pool)
748 {
749   proppatch_context_t *ctx = baton;
750   serf_bucket_t *body_bkt;
751   svn_boolean_t opened = FALSE;
752   apr_hash_index_t *hi;
753 
754   body_bkt = serf_bucket_aggregate_create(alloc);
755 
756   svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
757   svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
758                                     "xmlns:D", "DAV:",
759                                     "xmlns:V", SVN_DAV_PROP_NS_DAV,
760                                     "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
761                                     "xmlns:S", SVN_DAV_PROP_NS_SVN,
762                                     SVN_VA_NULL);
763 
764   /* First we write property SETs */
765   for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
766        hi;
767        hi = apr_hash_next(hi))
768     {
769       svn_prop_t *prop = apr_hash_this_val(hi);
770 
771       if (prop->value
772           || (ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
773         {
774           if (!opened)
775             {
776               opened = TRUE;
777               svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set",
778                                                 SVN_VA_NULL);
779               svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
780                                                 SVN_VA_NULL);
781             }
782 
783           SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
784                                  pool, scratch_pool));
785         }
786     }
787 
788   if (opened)
789     {
790       svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
791       svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
792     }
793 
794   /* And then property REMOVEs */
795   opened = FALSE;
796 
797   for (hi = apr_hash_first(scratch_pool, ctx->prop_changes);
798        hi;
799        hi = apr_hash_next(hi))
800     {
801       svn_prop_t *prop = apr_hash_this_val(hi);
802 
803       if (!prop->value
804           && !(ctx->old_props && svn_hash_gets(ctx->old_props, prop->name)))
805         {
806           if (!opened)
807             {
808               opened = TRUE;
809               svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove",
810                                                 SVN_VA_NULL);
811               svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop",
812                                                 SVN_VA_NULL);
813             }
814 
815           SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop,
816                                  pool, scratch_pool));
817         }
818     }
819 
820   if (opened)
821     {
822       svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
823       svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
824     }
825 
826   svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
827 
828   *bkt = body_bkt;
829   return SVN_NO_ERROR;
830 }
831 
832 static svn_error_t*
proppatch_resource(svn_ra_serf__session_t * session,proppatch_context_t * proppatch,apr_pool_t * pool)833 proppatch_resource(svn_ra_serf__session_t *session,
834                    proppatch_context_t *proppatch,
835                    apr_pool_t *pool)
836 {
837   svn_ra_serf__handler_t *handler;
838   svn_error_t *err;
839 
840   handler = svn_ra_serf__create_handler(session, pool);
841 
842   handler->method = "PROPPATCH";
843   handler->path = proppatch->path;
844 
845   handler->header_delegate = setup_proppatch_headers;
846   handler->header_delegate_baton = proppatch;
847 
848   handler->body_delegate = create_proppatch_body;
849   handler->body_delegate_baton = proppatch;
850   handler->body_type = "text/xml";
851 
852   handler->response_handler = svn_ra_serf__handle_multistatus_only;
853   handler->response_baton = handler;
854 
855   err = svn_ra_serf__context_run_one(handler, pool);
856 
857   if (!err && handler->sline.code != 207)
858     err = svn_error_trace(svn_ra_serf__unexpected_status(handler));
859 
860   /* Use specific error code for property handling errors.
861      Use loop to provide the right result with tracing */
862   if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
863     {
864       svn_error_t *e = err;
865 
866       while (e && e->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
867         {
868           e->apr_err = SVN_ERR_RA_DAV_PROPPATCH_FAILED;
869           e = e->child;
870         }
871     }
872 
873   return svn_error_trace(err);
874 }
875 
876 /* Implements svn_ra_serf__request_body_delegate_t */
877 static svn_error_t *
create_empty_put_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)878 create_empty_put_body(serf_bucket_t **body_bkt,
879                       void *baton,
880                       serf_bucket_alloc_t *alloc,
881                       apr_pool_t *pool /* request pool */,
882                       apr_pool_t *scratch_pool)
883 {
884   *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
885   return SVN_NO_ERROR;
886 }
887 
888 static svn_error_t *
setup_put_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)889 setup_put_headers(serf_bucket_t *headers,
890                   void *baton,
891                   apr_pool_t *pool /* request pool */,
892                   apr_pool_t *scratch_pool)
893 {
894   file_context_t *ctx = baton;
895 
896   if (SVN_IS_VALID_REVNUM(ctx->base_revision))
897     {
898       serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
899                               apr_psprintf(pool, "%ld", ctx->base_revision));
900     }
901 
902   if (ctx->base_checksum)
903     {
904       serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
905                               ctx->base_checksum);
906     }
907 
908   if (ctx->result_checksum)
909     {
910       serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
911                               ctx->result_checksum);
912     }
913 
914   SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit_ctx,
915                                       ctx->relpath, pool));
916 
917   return APR_SUCCESS;
918 }
919 
920 static svn_error_t *
setup_copy_file_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)921 setup_copy_file_headers(serf_bucket_t *headers,
922                         void *baton,
923                         apr_pool_t *pool /* request pool */,
924                         apr_pool_t *scratch_pool)
925 {
926   file_context_t *file = baton;
927   apr_uri_t uri;
928   const char *absolute_uri;
929 
930   /* The Dest URI must be absolute.  Bummer. */
931   uri = file->commit_ctx->session->session_url;
932   uri.path = (char*)file->url;
933   absolute_uri = apr_uri_unparse(pool, &uri, 0);
934 
935   serf_bucket_headers_set(headers, "Destination", absolute_uri);
936 
937   serf_bucket_headers_setn(headers, "Overwrite", "F");
938 
939   return SVN_NO_ERROR;
940 }
941 
942 static svn_error_t *
setup_if_header_recursive(svn_boolean_t * added,serf_bucket_t * headers,commit_context_t * commit_ctx,const char * rq_relpath,apr_pool_t * pool)943 setup_if_header_recursive(svn_boolean_t *added,
944                           serf_bucket_t *headers,
945                           commit_context_t *commit_ctx,
946                           const char *rq_relpath,
947                           apr_pool_t *pool)
948 {
949   svn_stringbuf_t *sb = NULL;
950   apr_hash_index_t *hi;
951   apr_pool_t *iterpool = NULL;
952 
953   if (!commit_ctx->lock_tokens)
954     {
955       *added = FALSE;
956       return SVN_NO_ERROR;
957     }
958 
959   /* We try to create a directory, so within the Subversion world that
960      would imply that there is nothing here, but mod_dav_svn still sees
961      locks on the old nodes here as in DAV it is perfectly legal to lock
962      something that is not there...
963 
964      Let's make mod_dav, mod_dav_svn and the DAV RFC happy by providing
965      the locks we know of with the request */
966 
967   for (hi = apr_hash_first(pool, commit_ctx->lock_tokens);
968        hi;
969        hi = apr_hash_next(hi))
970     {
971       const char *relpath = apr_hash_this_key(hi);
972       apr_uri_t uri;
973 
974       if (!svn_relpath_skip_ancestor(rq_relpath, relpath))
975         continue;
976       else if (svn_hash_gets(commit_ctx->deleted_entries, relpath))
977         {
978           /* When a path is already explicit deleted then its lock
979              will be removed by mod_dav. But mod_dav doesn't remove
980              locks on descendants */
981           continue;
982         }
983 
984       if (!iterpool)
985         iterpool = svn_pool_create(pool);
986       else
987         svn_pool_clear(iterpool);
988 
989       if (sb == NULL)
990         sb = svn_stringbuf_create("", pool);
991       else
992         svn_stringbuf_appendbyte(sb, ' ');
993 
994       uri = commit_ctx->session->session_url;
995       uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
996                                                     iterpool);
997 
998       svn_stringbuf_appendbyte(sb, '<');
999       svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0));
1000       svn_stringbuf_appendcstr(sb, "> (<");
1001       svn_stringbuf_appendcstr(sb, apr_hash_this_val(hi));
1002       svn_stringbuf_appendcstr(sb, ">)");
1003     }
1004 
1005   if (iterpool)
1006     svn_pool_destroy(iterpool);
1007 
1008   if (sb)
1009     {
1010       serf_bucket_headers_set(headers, "If", sb->data);
1011       *added = TRUE;
1012     }
1013   else
1014     *added = FALSE;
1015 
1016   return SVN_NO_ERROR;
1017 }
1018 
1019 static svn_error_t *
setup_add_dir_common_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)1020 setup_add_dir_common_headers(serf_bucket_t *headers,
1021                              void *baton,
1022                              apr_pool_t *pool /* request pool */,
1023                              apr_pool_t *scratch_pool)
1024 {
1025   dir_context_t *dir = baton;
1026   svn_boolean_t added;
1027 
1028   return svn_error_trace(
1029       setup_if_header_recursive(&added, headers, dir->commit_ctx, dir->relpath,
1030                                 pool));
1031 }
1032 
1033 static svn_error_t *
setup_copy_dir_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)1034 setup_copy_dir_headers(serf_bucket_t *headers,
1035                        void *baton,
1036                        apr_pool_t *pool /* request pool */,
1037                        apr_pool_t *scratch_pool)
1038 {
1039   dir_context_t *dir = baton;
1040   apr_uri_t uri;
1041   const char *absolute_uri;
1042 
1043   /* The Dest URI must be absolute.  Bummer. */
1044   uri = dir->commit_ctx->session->session_url;
1045 
1046   if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1047     {
1048       uri.path = (char *)dir->url;
1049     }
1050   else
1051     {
1052       uri.path = (char *)svn_path_url_add_component2(
1053                                     dir->parent_dir->working_url,
1054                                     dir->name, pool);
1055     }
1056   absolute_uri = apr_uri_unparse(pool, &uri, 0);
1057 
1058   serf_bucket_headers_set(headers, "Destination", absolute_uri);
1059 
1060   serf_bucket_headers_setn(headers, "Depth", "infinity");
1061   serf_bucket_headers_setn(headers, "Overwrite", "F");
1062 
1063   /* Implicitly checkout this dir now. */
1064   dir->working_url = apr_pstrdup(dir->pool, uri.path);
1065 
1066   return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool,
1067                                                       scratch_pool));
1068 }
1069 
1070 static svn_error_t *
setup_delete_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)1071 setup_delete_headers(serf_bucket_t *headers,
1072                      void *baton,
1073                      apr_pool_t *pool /* request pool */,
1074                      apr_pool_t *scratch_pool)
1075 {
1076   delete_context_t *del = baton;
1077   svn_boolean_t added;
1078 
1079   serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
1080                           apr_ltoa(pool, del->revision));
1081 
1082   if (! del->non_recursive_if)
1083     SVN_ERR(setup_if_header_recursive(&added, headers, del->commit_ctx,
1084                                       del->relpath, pool));
1085   else
1086     {
1087       SVN_ERR(maybe_set_lock_token_header(headers, del->commit_ctx,
1088                                           del->relpath, pool));
1089       added = TRUE;
1090     }
1091 
1092   if (added && del->commit_ctx->keep_locks)
1093     serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
1094                                       SVN_DAV_OPTION_KEEP_LOCKS);
1095 
1096   return SVN_NO_ERROR;
1097 }
1098 
1099 /* POST against 'me' resource handlers. */
1100 
1101 /* Implements svn_ra_serf__request_body_delegate_t */
1102 static svn_error_t *
create_txn_post_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)1103 create_txn_post_body(serf_bucket_t **body_bkt,
1104                      void *baton,
1105                      serf_bucket_alloc_t *alloc,
1106                      apr_pool_t *pool /* request pool */,
1107                      apr_pool_t *scratch_pool)
1108 {
1109   apr_hash_t *revprops = baton;
1110   svn_skel_t *request_skel;
1111   svn_stringbuf_t *skel_str;
1112 
1113   request_skel = svn_skel__make_empty_list(pool);
1114   if (revprops)
1115     {
1116       svn_skel_t *proplist_skel;
1117 
1118       SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
1119       svn_skel__prepend(proplist_skel, request_skel);
1120       svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
1121       skel_str = svn_skel__unparse(request_skel, pool);
1122       *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
1123     }
1124   else
1125     {
1126       *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
1127     }
1128 
1129   return SVN_NO_ERROR;
1130 }
1131 
1132 /* Implements svn_ra_serf__request_header_delegate_t */
1133 static svn_error_t *
setup_post_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)1134 setup_post_headers(serf_bucket_t *headers,
1135                    void *baton,
1136                    apr_pool_t *pool /* request pool */,
1137                    apr_pool_t *scratch_pool)
1138 {
1139 #ifdef SVN_DAV_SEND_VTXN_NAME
1140   /* Enable this to exercise the VTXN-NAME code based on a client
1141      supplied transaction name. */
1142   serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
1143                           svn_uuid_generate(pool));
1144 #endif
1145 
1146   return SVN_NO_ERROR;
1147 }
1148 
1149 
1150 /* Handler baton for POST request. */
1151 typedef struct post_response_ctx_t
1152 {
1153   svn_ra_serf__handler_t *handler;
1154   commit_context_t *commit_ctx;
1155 } post_response_ctx_t;
1156 
1157 
1158 /* This implements serf_bucket_headers_do_callback_fn_t.   */
1159 static int
post_headers_iterator_callback(void * baton,const char * key,const char * val)1160 post_headers_iterator_callback(void *baton,
1161                                const char *key,
1162                                const char *val)
1163 {
1164   post_response_ctx_t *prc = baton;
1165   commit_context_t *prc_cc = prc->commit_ctx;
1166   svn_ra_serf__session_t *sess = prc_cc->session;
1167 
1168   /* If we provided a UUID to the POST request, we should get back
1169      from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
1170      expect the SVN_DAV_TXN_NAME_HEADER.  We certainly don't expect to
1171      see both. */
1172 
1173   if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
1174     {
1175       /* Build out txn and txn-root URLs using the txn name we're
1176          given, and store the whole lot of it in the commit context.  */
1177       prc_cc->txn_url =
1178         svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
1179       prc_cc->txn_root_url =
1180         svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
1181     }
1182 
1183   if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
1184     {
1185       /* Build out vtxn and vtxn-root URLs using the vtxn name we're
1186          given, and store the whole lot of it in the commit context.  */
1187       prc_cc->txn_url =
1188         svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
1189       prc_cc->txn_root_url =
1190         svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
1191     }
1192 
1193   return 0;
1194 }
1195 
1196 
1197 /* A custom serf_response_handler_t which is mostly a wrapper around
1198    svn_ra_serf__expect_empty_body -- it just notices POST response
1199    headers, too.
1200 
1201    Implements svn_ra_serf__response_handler_t */
1202 static svn_error_t *
post_response_handler(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * scratch_pool)1203 post_response_handler(serf_request_t *request,
1204                       serf_bucket_t *response,
1205                       void *baton,
1206                       apr_pool_t *scratch_pool)
1207 {
1208   post_response_ctx_t *prc = baton;
1209   serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
1210 
1211   /* Then see which ones we can discover. */
1212   serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
1213 
1214   /* Execute the 'real' response handler to XML-parse the repsonse body. */
1215   return svn_ra_serf__expect_empty_body(request, response,
1216                                         prc->handler, scratch_pool);
1217 }
1218 
1219 
1220 
1221 /* Commit baton callbacks */
1222 
1223 static svn_error_t *
open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * dir_pool,void ** root_baton)1224 open_root(void *edit_baton,
1225           svn_revnum_t base_revision,
1226           apr_pool_t *dir_pool,
1227           void **root_baton)
1228 {
1229   commit_context_t *commit_ctx = edit_baton;
1230   svn_ra_serf__handler_t *handler;
1231   proppatch_context_t *proppatch_ctx;
1232   dir_context_t *dir;
1233   apr_hash_index_t *hi;
1234   const char *proppatch_target = NULL;
1235   apr_pool_t *scratch_pool = svn_pool_create(dir_pool);
1236 
1237   commit_ctx->open_batons++;
1238 
1239   if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(commit_ctx->session))
1240     {
1241       post_response_ctx_t *prc;
1242       const char *rel_path;
1243       svn_boolean_t post_with_revprops
1244         = (NULL != svn_hash_gets(commit_ctx->session->supported_posts,
1245                                  "create-txn-with-props"));
1246 
1247       /* Create our activity URL now on the server. */
1248       handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1249 
1250       handler->method = "POST";
1251       handler->body_type = SVN_SKEL_MIME_TYPE;
1252       handler->body_delegate = create_txn_post_body;
1253       handler->body_delegate_baton =
1254           post_with_revprops ? commit_ctx->revprop_table : NULL;
1255       handler->header_delegate = setup_post_headers;
1256       handler->header_delegate_baton = NULL;
1257       handler->path = commit_ctx->session->me_resource;
1258 
1259       prc = apr_pcalloc(scratch_pool, sizeof(*prc));
1260       prc->handler = handler;
1261       prc->commit_ctx = commit_ctx;
1262 
1263       handler->response_handler = post_response_handler;
1264       handler->response_baton = prc;
1265 
1266       SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1267 
1268       if (handler->sline.code != 201)
1269         return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1270 
1271       if (! (commit_ctx->txn_root_url && commit_ctx->txn_url))
1272         {
1273           return svn_error_createf(
1274             SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1275             _("POST request did not return transaction information"));
1276         }
1277 
1278       /* Fixup the txn_root_url to point to the anchor of the commit. */
1279       SVN_ERR(svn_ra_serf__get_relative_path(
1280                                         &rel_path,
1281                                         commit_ctx->session->session_url.path,
1282                                         commit_ctx->session,
1283                                         scratch_pool));
1284       commit_ctx->txn_root_url = svn_path_url_add_component2(
1285                                         commit_ctx->txn_root_url,
1286                                         rel_path, commit_ctx->pool);
1287 
1288       /* Build our directory baton. */
1289       dir = apr_pcalloc(dir_pool, sizeof(*dir));
1290       dir->pool = dir_pool;
1291       dir->commit_ctx = commit_ctx;
1292       dir->base_revision = base_revision;
1293       dir->relpath = "";
1294       dir->name = "";
1295       dir->prop_changes = apr_hash_make(dir->pool);
1296       dir->url = apr_pstrdup(dir->pool, commit_ctx->txn_root_url);
1297 
1298       /* If we included our revprops in the POST, we need not
1299          PROPPATCH them. */
1300       proppatch_target = post_with_revprops ? NULL : commit_ctx->txn_url;
1301     }
1302   else
1303     {
1304       const char *activity_str = commit_ctx->session->activity_collection_url;
1305 
1306       if (!activity_str)
1307         SVN_ERR(svn_ra_serf__v1_get_activity_collection(
1308                                     &activity_str,
1309                                     commit_ctx->session,
1310                                     scratch_pool, scratch_pool));
1311 
1312       commit_ctx->activity_url = svn_path_url_add_component2(
1313                                     activity_str,
1314                                     svn_uuid_generate(scratch_pool),
1315                                     commit_ctx->pool);
1316 
1317       /* Create our activity URL now on the server. */
1318       handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool);
1319 
1320       handler->method = "MKACTIVITY";
1321       handler->path = commit_ctx->activity_url;
1322 
1323       handler->response_handler = svn_ra_serf__expect_empty_body;
1324       handler->response_baton = handler;
1325 
1326       SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1327 
1328       if (handler->sline.code != 201)
1329         return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1330 
1331       /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
1332       SVN_ERR(svn_ra_serf__discover_vcc(&(commit_ctx->vcc_url),
1333                                         commit_ctx->session, scratch_pool));
1334 
1335 
1336       /* Build our directory baton. */
1337       dir = apr_pcalloc(dir_pool, sizeof(*dir));
1338       dir->pool = dir_pool;
1339       dir->commit_ctx = commit_ctx;
1340       dir->base_revision = base_revision;
1341       dir->relpath = "";
1342       dir->name = "";
1343       dir->prop_changes = apr_hash_make(dir->pool);
1344 
1345       SVN_ERR(get_version_url(&dir->url, dir->commit_ctx->session,
1346                               dir->relpath,
1347                               dir->base_revision, commit_ctx->checked_in_url,
1348                               dir->pool, scratch_pool));
1349       commit_ctx->checked_in_url = apr_pstrdup(commit_ctx->pool, dir->url);
1350 
1351       /* Checkout our root dir */
1352       SVN_ERR(checkout_dir(dir, scratch_pool));
1353 
1354       proppatch_target = commit_ctx->baseline_url;
1355     }
1356 
1357   /* Unless this is NULL -- which means we don't need to PROPPATCH the
1358      transaction with our revprops -- then, you know, PROPPATCH the
1359      transaction with our revprops.  */
1360   if (proppatch_target)
1361     {
1362       proppatch_ctx = apr_pcalloc(scratch_pool, sizeof(*proppatch_ctx));
1363       proppatch_ctx->pool = scratch_pool;
1364       proppatch_ctx->commit_ctx = NULL; /* No lock info */
1365       proppatch_ctx->path = proppatch_target;
1366       proppatch_ctx->prop_changes = apr_hash_make(proppatch_ctx->pool);
1367       proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
1368 
1369       for (hi = apr_hash_first(scratch_pool, commit_ctx->revprop_table);
1370            hi;
1371            hi = apr_hash_next(hi))
1372         {
1373           svn_prop_t *prop = apr_palloc(scratch_pool, sizeof(*prop));
1374 
1375           prop->name = apr_hash_this_key(hi);
1376           prop->value = apr_hash_this_val(hi);
1377 
1378           svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
1379         }
1380 
1381       SVN_ERR(proppatch_resource(commit_ctx->session,
1382                                  proppatch_ctx, scratch_pool));
1383     }
1384 
1385   svn_pool_destroy(scratch_pool);
1386 
1387   *root_baton = dir;
1388 
1389   return SVN_NO_ERROR;
1390 }
1391 
1392 /* Implements svn_ra_serf__request_body_delegate_t */
1393 static svn_error_t *
create_delete_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)1394 create_delete_body(serf_bucket_t **body_bkt,
1395                    void *baton,
1396                    serf_bucket_alloc_t *alloc,
1397                    apr_pool_t *pool /* request pool */,
1398                    apr_pool_t *scratch_pool)
1399 {
1400   delete_context_t *ctx = baton;
1401   serf_bucket_t *body;
1402 
1403   body = serf_bucket_aggregate_create(alloc);
1404 
1405   svn_ra_serf__add_xml_header_buckets(body, alloc);
1406 
1407   svn_ra_serf__merge_lock_token_list(ctx->commit_ctx->lock_tokens,
1408                                      ctx->relpath, body, alloc, pool);
1409 
1410   *body_bkt = body;
1411   return SVN_NO_ERROR;
1412 }
1413 
1414 static svn_error_t *
delete_entry(const char * path,svn_revnum_t revision,void * parent_baton,apr_pool_t * pool)1415 delete_entry(const char *path,
1416              svn_revnum_t revision,
1417              void *parent_baton,
1418              apr_pool_t *pool)
1419 {
1420   dir_context_t *dir = parent_baton;
1421   delete_context_t *delete_ctx;
1422   svn_ra_serf__handler_t *handler;
1423   const char *delete_target;
1424 
1425   if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1426     {
1427       delete_target = svn_path_url_add_component2(
1428                                     dir->commit_ctx->txn_root_url,
1429                                     path, dir->pool);
1430     }
1431   else
1432     {
1433       /* Ensure our directory has been checked out */
1434       SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
1435       delete_target = svn_path_url_add_component2(dir->working_url,
1436                                                   svn_relpath_basename(path,
1437                                                                        NULL),
1438                                                   pool);
1439     }
1440 
1441   /* DELETE our entry */
1442   delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
1443   delete_ctx->relpath = apr_pstrdup(pool, path);
1444   delete_ctx->revision = revision;
1445   delete_ctx->commit_ctx = dir->commit_ctx;
1446 
1447   handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1448 
1449   handler->response_handler = svn_ra_serf__expect_empty_body;
1450   handler->response_baton = handler;
1451 
1452   handler->header_delegate = setup_delete_headers;
1453   handler->header_delegate_baton = delete_ctx;
1454 
1455   handler->method = "DELETE";
1456   handler->path = delete_target;
1457   handler->no_fail_on_http_failure_status = TRUE;
1458 
1459   SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1460 
1461   if (handler->sline.code == 400)
1462     {
1463       /* Try again with non-standard body to overcome Apache Httpd
1464          header limit */
1465       delete_ctx->non_recursive_if = TRUE;
1466 
1467       handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool);
1468 
1469       handler->response_handler = svn_ra_serf__expect_empty_body;
1470       handler->response_baton = handler;
1471 
1472       handler->header_delegate = setup_delete_headers;
1473       handler->header_delegate_baton = delete_ctx;
1474 
1475       handler->method = "DELETE";
1476       handler->path = delete_target;
1477 
1478       handler->body_type = "text/xml";
1479       handler->body_delegate = create_delete_body;
1480       handler->body_delegate_baton = delete_ctx;
1481 
1482       SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
1483     }
1484 
1485   if (handler->server_error)
1486     return svn_ra_serf__server_error_create(handler, pool);
1487 
1488   /* 204 No Content: item successfully deleted */
1489   if (handler->sline.code != 204)
1490     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1491 
1492   svn_hash_sets(dir->commit_ctx->deleted_entries,
1493                 apr_pstrdup(dir->commit_ctx->pool, path), (void *)1);
1494 
1495   return SVN_NO_ERROR;
1496 }
1497 
1498 static svn_error_t *
add_directory(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * dir_pool,void ** child_baton)1499 add_directory(const char *path,
1500               void *parent_baton,
1501               const char *copyfrom_path,
1502               svn_revnum_t copyfrom_revision,
1503               apr_pool_t *dir_pool,
1504               void **child_baton)
1505 {
1506   dir_context_t *parent = parent_baton;
1507   dir_context_t *dir;
1508   svn_ra_serf__handler_t *handler;
1509   apr_status_t status;
1510   const char *mkcol_target;
1511 
1512   dir = apr_pcalloc(dir_pool, sizeof(*dir));
1513 
1514   dir->pool = dir_pool;
1515   dir->parent_dir = parent;
1516   dir->commit_ctx = parent->commit_ctx;
1517   dir->added = TRUE;
1518   dir->base_revision = SVN_INVALID_REVNUM;
1519   dir->copy_revision = copyfrom_revision;
1520   dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path);
1521   dir->relpath = apr_pstrdup(dir->pool, path);
1522   dir->name = svn_relpath_basename(dir->relpath, NULL);
1523   dir->prop_changes = apr_hash_make(dir->pool);
1524 
1525   dir->commit_ctx->open_batons++;
1526 
1527   if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1528     {
1529       dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1530                                              path, dir->pool);
1531       mkcol_target = dir->url;
1532     }
1533   else
1534     {
1535       /* Ensure our parent is checked out. */
1536       SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
1537 
1538       dir->url = svn_path_url_add_component2(parent->commit_ctx->checked_in_url,
1539                                              dir->name, dir->pool);
1540       mkcol_target = svn_path_url_add_component2(
1541                                parent->working_url,
1542                                dir->name, dir->pool);
1543     }
1544 
1545   handler = svn_ra_serf__create_handler(dir->commit_ctx->session, dir->pool);
1546 
1547   handler->response_handler = svn_ra_serf__expect_empty_body;
1548   handler->response_baton = handler;
1549   if (!dir->copy_path)
1550     {
1551       handler->method = "MKCOL";
1552       handler->path = mkcol_target;
1553 
1554       handler->header_delegate = setup_add_dir_common_headers;
1555       handler->header_delegate_baton = dir;
1556     }
1557   else
1558     {
1559       apr_uri_t uri;
1560       const char *req_url;
1561 
1562       status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
1563       if (status)
1564         {
1565           return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1566                                    _("Unable to parse URL '%s'"),
1567                                    dir->copy_path);
1568         }
1569 
1570       SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1571                                           dir->commit_ctx->session,
1572                                           uri.path, dir->copy_revision,
1573                                           dir_pool, dir_pool));
1574 
1575       handler->method = "COPY";
1576       handler->path = req_url;
1577 
1578       handler->header_delegate = setup_copy_dir_headers;
1579       handler->header_delegate_baton = dir;
1580     }
1581   /* We have the same problem as with DELETE here: if there are too many
1582      locks, the request fails. But in this case there is no way to retry
1583      with a non-standard request. #### How to fix? */
1584   SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
1585 
1586   if (handler->sline.code != 201)
1587     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1588 
1589   *child_baton = dir;
1590 
1591   return SVN_NO_ERROR;
1592 }
1593 
1594 static svn_error_t *
open_directory(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * dir_pool,void ** child_baton)1595 open_directory(const char *path,
1596                void *parent_baton,
1597                svn_revnum_t base_revision,
1598                apr_pool_t *dir_pool,
1599                void **child_baton)
1600 {
1601   dir_context_t *parent = parent_baton;
1602   dir_context_t *dir;
1603 
1604   dir = apr_pcalloc(dir_pool, sizeof(*dir));
1605 
1606   dir->pool = dir_pool;
1607 
1608   dir->parent_dir = parent;
1609   dir->commit_ctx = parent->commit_ctx;
1610 
1611   dir->added = FALSE;
1612   dir->base_revision = base_revision;
1613   dir->relpath = apr_pstrdup(dir->pool, path);
1614   dir->name = svn_relpath_basename(dir->relpath, NULL);
1615   dir->prop_changes = apr_hash_make(dir->pool);
1616 
1617   dir->commit_ctx->open_batons++;
1618 
1619   if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1620     {
1621       dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1622                                              path, dir->pool);
1623     }
1624   else
1625     {
1626       SVN_ERR(get_version_url(&dir->url,
1627                               dir->commit_ctx->session,
1628                               dir->relpath, dir->base_revision,
1629                               dir->commit_ctx->checked_in_url,
1630                               dir->pool, dir->pool /* scratch_pool */));
1631     }
1632   *child_baton = dir;
1633 
1634   return SVN_NO_ERROR;
1635 }
1636 
1637 static svn_error_t *
change_dir_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)1638 change_dir_prop(void *dir_baton,
1639                 const char *name,
1640                 const svn_string_t *value,
1641                 apr_pool_t *scratch_pool)
1642 {
1643   dir_context_t *dir = dir_baton;
1644   svn_prop_t *prop;
1645 
1646   if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1647     {
1648       /* Ensure we have a checked out dir. */
1649       SVN_ERR(checkout_dir(dir, scratch_pool));
1650     }
1651 
1652   prop = apr_palloc(dir->pool, sizeof(*prop));
1653 
1654   prop->name = apr_pstrdup(dir->pool, name);
1655   prop->value = svn_string_dup(value, dir->pool);
1656 
1657   svn_hash_sets(dir->prop_changes, prop->name, prop);
1658 
1659   return SVN_NO_ERROR;
1660 }
1661 
1662 static svn_error_t *
close_directory(void * dir_baton,apr_pool_t * pool)1663 close_directory(void *dir_baton,
1664                 apr_pool_t *pool)
1665 {
1666   dir_context_t *dir = dir_baton;
1667 
1668   /* Huh?  We're going to be called before the texts are sent.  Ugh.
1669    * Therefore, just wave politely at our caller.
1670    */
1671 
1672   /* PROPPATCH our prop change and pass it along.  */
1673   if (apr_hash_count(dir->prop_changes))
1674     {
1675       proppatch_context_t *proppatch_ctx;
1676 
1677       proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
1678       proppatch_ctx->pool = pool;
1679       proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */;
1680       proppatch_ctx->relpath = dir->relpath;
1681       proppatch_ctx->prop_changes = dir->prop_changes;
1682       proppatch_ctx->base_revision = dir->base_revision;
1683 
1684       if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1685         {
1686           proppatch_ctx->path = dir->url;
1687         }
1688       else
1689         {
1690           proppatch_ctx->path = dir->working_url;
1691         }
1692 
1693       SVN_ERR(proppatch_resource(dir->commit_ctx->session,
1694                                  proppatch_ctx, dir->pool));
1695     }
1696 
1697   dir->commit_ctx->open_batons--;
1698 
1699   return SVN_NO_ERROR;
1700 }
1701 
1702 static svn_error_t *
add_file(const char * path,void * parent_baton,const char * copy_path,svn_revnum_t copy_revision,apr_pool_t * file_pool,void ** file_baton)1703 add_file(const char *path,
1704          void *parent_baton,
1705          const char *copy_path,
1706          svn_revnum_t copy_revision,
1707          apr_pool_t *file_pool,
1708          void **file_baton)
1709 {
1710   dir_context_t *dir = parent_baton;
1711   file_context_t *new_file;
1712   const char *deleted_parent = path;
1713   apr_pool_t *scratch_pool = svn_pool_create(file_pool);
1714 
1715   new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1716   new_file->pool = file_pool;
1717 
1718   new_file->parent_dir = dir;
1719   new_file->commit_ctx = dir->commit_ctx;
1720   new_file->relpath = apr_pstrdup(new_file->pool, path);
1721   new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1722   new_file->added = TRUE;
1723   new_file->base_revision = SVN_INVALID_REVNUM;
1724   new_file->copy_path = apr_pstrdup(new_file->pool, copy_path);
1725   new_file->copy_revision = copy_revision;
1726   new_file->prop_changes = apr_hash_make(new_file->pool);
1727 
1728   dir->commit_ctx->open_batons++;
1729 
1730   /* Ensure that the file doesn't exist by doing a HEAD on the
1731      resource.  If we're using HTTP v2, we'll just look into the
1732      transaction root tree for this thing.  */
1733   if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
1734     {
1735       new_file->url = svn_path_url_add_component2(dir->commit_ctx->txn_root_url,
1736                                                   path, new_file->pool);
1737     }
1738   else
1739     {
1740       /* Ensure our parent directory has been checked out */
1741       SVN_ERR(checkout_dir(dir, scratch_pool));
1742 
1743       new_file->url =
1744         svn_path_url_add_component2(dir->working_url,
1745                                     new_file->name, new_file->pool);
1746     }
1747 
1748   while (deleted_parent && deleted_parent[0] != '\0')
1749     {
1750       if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent))
1751         {
1752           break;
1753         }
1754       deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
1755     }
1756 
1757   if (copy_path)
1758     {
1759       svn_ra_serf__handler_t *handler;
1760       apr_uri_t uri;
1761       const char *req_url;
1762       apr_status_t status;
1763 
1764       /* Create the copy directly as cheap 'does exist/out of date'
1765          check. We update the copy (if needed) from close_file() */
1766 
1767       status = apr_uri_parse(scratch_pool, copy_path, &uri);
1768       if (status)
1769         return svn_ra_serf__wrap_err(status, NULL);
1770 
1771       SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
1772                                           dir->commit_ctx->session,
1773                                           uri.path, copy_revision,
1774                                           scratch_pool, scratch_pool));
1775 
1776       handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1777                                             scratch_pool);
1778       handler->method = "COPY";
1779       handler->path = req_url;
1780 
1781       handler->response_handler = svn_ra_serf__expect_empty_body;
1782       handler->response_baton = handler;
1783 
1784       handler->header_delegate = setup_copy_file_headers;
1785       handler->header_delegate_baton = new_file;
1786 
1787       SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
1788 
1789       if (handler->sline.code != 201)
1790         return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1791     }
1792   else if (! ((dir->added && !dir->copy_path) ||
1793            (deleted_parent && deleted_parent[0] != '\0')))
1794     {
1795       svn_ra_serf__handler_t *handler;
1796       svn_error_t *err;
1797 
1798       handler = svn_ra_serf__create_handler(dir->commit_ctx->session,
1799                                             scratch_pool);
1800       handler->method = "HEAD";
1801       handler->path = svn_path_url_add_component2(
1802                                         dir->commit_ctx->session->session_url.path,
1803                                         path, scratch_pool);
1804       handler->response_handler = svn_ra_serf__expect_empty_body;
1805       handler->response_baton = handler;
1806       handler->no_dav_headers = TRUE; /* Read only operation outside txn */
1807 
1808       err = svn_ra_serf__context_run_one(handler, scratch_pool);
1809 
1810       if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1811         {
1812           svn_error_clear(err); /* Great. We can create a new file! */
1813         }
1814       else if (err)
1815         return svn_error_trace(err);
1816       else
1817         return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1818                                  _("File '%s' already exists"), path);
1819     }
1820 
1821   svn_pool_destroy(scratch_pool);
1822   *file_baton = new_file;
1823 
1824   return SVN_NO_ERROR;
1825 }
1826 
1827 static svn_error_t *
open_file(const char * path,void * parent_baton,svn_revnum_t base_revision,apr_pool_t * file_pool,void ** file_baton)1828 open_file(const char *path,
1829           void *parent_baton,
1830           svn_revnum_t base_revision,
1831           apr_pool_t *file_pool,
1832           void **file_baton)
1833 {
1834   dir_context_t *parent = parent_baton;
1835   file_context_t *new_file;
1836 
1837   new_file = apr_pcalloc(file_pool, sizeof(*new_file));
1838   new_file->pool = file_pool;
1839 
1840   new_file->parent_dir = parent;
1841   new_file->commit_ctx = parent->commit_ctx;
1842   new_file->relpath = apr_pstrdup(new_file->pool, path);
1843   new_file->name = svn_relpath_basename(new_file->relpath, NULL);
1844   new_file->added = FALSE;
1845   new_file->base_revision = base_revision;
1846   new_file->prop_changes = apr_hash_make(new_file->pool);
1847 
1848   parent->commit_ctx->open_batons++;
1849 
1850   if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx))
1851     {
1852       new_file->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
1853                                                   path, new_file->pool);
1854     }
1855   else
1856     {
1857       /* CHECKOUT the file into our activity. */
1858       SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
1859 
1860       new_file->url = new_file->working_url;
1861     }
1862 
1863   *file_baton = new_file;
1864 
1865   return SVN_NO_ERROR;
1866 }
1867 
1868 static void
negotiate_put_encoding(int * svndiff_version_p,int * svndiff_compression_level_p,svn_ra_serf__session_t * session)1869 negotiate_put_encoding(int *svndiff_version_p,
1870                        int *svndiff_compression_level_p,
1871                        svn_ra_serf__session_t *session)
1872 {
1873   int svndiff_version;
1874   int compression_level;
1875 
1876   if (session->using_compression == svn_tristate_unknown)
1877     {
1878       /* With http-compression=auto, prefer svndiff2 to svndiff1 with a
1879        * low latency connection (assuming the underlying network has high
1880        * bandwidth), as it is faster and in this case, we don't care about
1881        * worse compression ratio.
1882        *
1883        * Note: For future compatibility, we also handle a theoretically
1884        * possible case where the server has advertised only svndiff2 support.
1885        */
1886       if (session->supports_svndiff2 &&
1887           svn_ra_serf__is_low_latency_connection(session))
1888         svndiff_version = 2;
1889       else if (session->supports_svndiff1)
1890         svndiff_version = 1;
1891       else if (session->supports_svndiff2)
1892         svndiff_version = 2;
1893       else
1894         svndiff_version = 0;
1895     }
1896   else if (session->using_compression == svn_tristate_true)
1897     {
1898       /* Otherwise, prefer svndiff1, as svndiff2 is not a reasonable
1899        * substitute for svndiff1 with default compression level.  (It gives
1900        * better speed and compression ratio comparable to svndiff1 with
1901        * compression level 1, but not 5).
1902        *
1903        * Note: For future compatibility, we also handle a theoretically
1904        * possible case where the server has advertised only svndiff2 support.
1905        */
1906       if (session->supports_svndiff1)
1907         svndiff_version = 1;
1908       else if (session->supports_svndiff2)
1909         svndiff_version = 2;
1910       else
1911         svndiff_version = 0;
1912     }
1913   else
1914     {
1915       /* Difference between svndiff formats 0 and 1/2 that format 1/2 allows
1916        * compression.  Uncompressed svndiff0 should also be slightly more
1917        * effective if the compression is not required at all.
1918        *
1919        * If the server cannot handle svndiff1/2, or compression is disabled
1920        * with the 'http-compression = no' client configuration option, fall
1921        * back to uncompressed svndiff0 format.  As a bonus, users can force
1922        * the usage of the uncompressed format by setting the corresponding
1923        * client configuration option, if they want to.
1924        */
1925       svndiff_version = 0;
1926     }
1927 
1928   if (svndiff_version == 0)
1929     compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
1930   else
1931     compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
1932 
1933   *svndiff_version_p = svndiff_version;
1934   *svndiff_compression_level_p = compression_level;
1935 }
1936 
1937 static svn_error_t *
apply_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)1938 apply_textdelta(void *file_baton,
1939                 const char *base_checksum,
1940                 apr_pool_t *pool,
1941                 svn_txdelta_window_handler_t *handler,
1942                 void **handler_baton)
1943 {
1944   file_context_t *ctx = file_baton;
1945   int svndiff_version;
1946   int compression_level;
1947 
1948   /* Construct a holder for the request body; we'll give it to serf when we
1949    * close this file.
1950    *
1951    * Please note that if this callback is used, large request bodies will
1952    * be spilled into temporary files (that requires disk space and prevents
1953    * simultaneous processing by the server and the client).  A better approach
1954    * that streams the request body is implemented in apply_textdelta_stream().
1955    * It will be used with most recent servers having the "send result checksum
1956    * in response to a PUT" capability, and only if the editor driver uses the
1957    * new callback.
1958    */
1959   ctx->svndiff =
1960     svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
1961                                      ctx->pool);
1962   ctx->stream = svn_ra_serf__request_body_get_stream(ctx->svndiff);
1963 
1964   negotiate_put_encoding(&svndiff_version, &compression_level,
1965                          ctx->commit_ctx->session);
1966   /* Disown the stream; we'll close it explicitly in close_file(). */
1967   svn_txdelta_to_svndiff3(handler, handler_baton,
1968                           svn_stream_disown(ctx->stream, pool),
1969                           svndiff_version, compression_level, pool);
1970 
1971   if (base_checksum)
1972     ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
1973 
1974   return SVN_NO_ERROR;
1975 }
1976 
1977 typedef struct open_txdelta_baton_t
1978 {
1979   svn_ra_serf__session_t *session;
1980   svn_txdelta_stream_open_func_t open_func;
1981   void *open_baton;
1982   svn_error_t *err;
1983 } open_txdelta_baton_t;
1984 
1985 static void
txdelta_stream_errfunc(void * baton,svn_error_t * err)1986 txdelta_stream_errfunc(void *baton, svn_error_t *err)
1987 {
1988   open_txdelta_baton_t *b = baton;
1989 
1990   /* Remember extended error info from the stream bucket.  Note that
1991    * theoretically this errfunc could be called multiple times -- say,
1992    * if the request gets restarted after an error.  Compose the errors
1993    * so we don't leak one of them if this happens. */
1994   b->err = svn_error_compose_create(b->err, svn_error_dup(err));
1995 }
1996 
1997 /* Implements svn_ra_serf__request_body_delegate_t */
1998 static svn_error_t *
create_body_from_txdelta_stream(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)1999 create_body_from_txdelta_stream(serf_bucket_t **body_bkt,
2000                                 void *baton,
2001                                 serf_bucket_alloc_t *alloc,
2002                                 apr_pool_t *pool /* request pool */,
2003                                 apr_pool_t *scratch_pool)
2004 {
2005   open_txdelta_baton_t *b = baton;
2006   svn_txdelta_stream_t *txdelta_stream;
2007   svn_stream_t *stream;
2008   int svndiff_version;
2009   int compression_level;
2010 
2011   SVN_ERR(b->open_func(&txdelta_stream, b->open_baton, pool, scratch_pool));
2012 
2013   negotiate_put_encoding(&svndiff_version, &compression_level, b->session);
2014   stream = svn_txdelta_to_svndiff_stream(txdelta_stream, svndiff_version,
2015                                          compression_level, pool);
2016   *body_bkt = svn_ra_serf__create_stream_bucket(stream, alloc,
2017                                                 txdelta_stream_errfunc, b);
2018 
2019   return SVN_NO_ERROR;
2020 }
2021 
2022 /* Handler baton for PUT request. */
2023 typedef struct put_response_ctx_t
2024 {
2025   svn_ra_serf__handler_t *handler;
2026   file_context_t *file_ctx;
2027 } put_response_ctx_t;
2028 
2029 /* Implements svn_ra_serf__response_handler_t */
2030 static svn_error_t *
put_response_handler(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * scratch_pool)2031 put_response_handler(serf_request_t *request,
2032                      serf_bucket_t *response,
2033                      void *baton,
2034                      apr_pool_t *scratch_pool)
2035 {
2036   put_response_ctx_t *prc = baton;
2037   serf_bucket_t *hdrs;
2038   const char *val;
2039 
2040   hdrs = serf_bucket_response_get_headers(response);
2041   val = serf_bucket_headers_get(hdrs, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER);
2042   SVN_ERR(svn_checksum_parse_hex(&prc->file_ctx->remote_result_checksum,
2043                                  svn_checksum_md5, val, prc->file_ctx->pool));
2044 
2045   return svn_error_trace(
2046            svn_ra_serf__expect_empty_body(request, response,
2047                                           prc->handler, scratch_pool));
2048 }
2049 
2050 static svn_error_t *
apply_textdelta_stream(const svn_delta_editor_t * editor,void * file_baton,const char * base_checksum,svn_txdelta_stream_open_func_t open_func,void * open_baton,apr_pool_t * scratch_pool)2051 apply_textdelta_stream(const svn_delta_editor_t *editor,
2052                        void *file_baton,
2053                        const char *base_checksum,
2054                        svn_txdelta_stream_open_func_t open_func,
2055                        void *open_baton,
2056                        apr_pool_t *scratch_pool)
2057 {
2058   file_context_t *ctx = file_baton;
2059   open_txdelta_baton_t open_txdelta_baton = {0};
2060   svn_ra_serf__handler_t *handler;
2061   put_response_ctx_t *prc;
2062   int expected_result;
2063   svn_error_t *err;
2064 
2065   /* Remember that we have sent the svndiff.  A case when we need to
2066    * perform a zero-byte file PUT (during add_file, close_file editor
2067    * sequences) is handled in close_file().
2068    */
2069   ctx->svndiff_sent = TRUE;
2070   ctx->base_checksum = base_checksum;
2071 
2072   handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
2073                                         scratch_pool);
2074   handler->method = "PUT";
2075   handler->path = ctx->url;
2076 
2077   prc = apr_pcalloc(scratch_pool, sizeof(*prc));
2078   prc->handler = handler;
2079   prc->file_ctx = ctx;
2080 
2081   handler->response_handler = put_response_handler;
2082   handler->response_baton = prc;
2083 
2084   open_txdelta_baton.session = ctx->commit_ctx->session;
2085   open_txdelta_baton.open_func = open_func;
2086   open_txdelta_baton.open_baton = open_baton;
2087   open_txdelta_baton.err = SVN_NO_ERROR;
2088 
2089   handler->body_delegate = create_body_from_txdelta_stream;
2090   handler->body_delegate_baton = &open_txdelta_baton;
2091   handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2092 
2093   handler->header_delegate = setup_put_headers;
2094   handler->header_delegate_baton = ctx;
2095 
2096   err = svn_ra_serf__context_run_one(handler, scratch_pool);
2097   /* Do we have an error from the stream bucket?  If yes, use it. */
2098   if (open_txdelta_baton.err)
2099     {
2100       svn_error_clear(err);
2101       return svn_error_trace(open_txdelta_baton.err);
2102     }
2103   else if (err)
2104     return svn_error_trace(err);
2105 
2106   if (ctx->added && !ctx->copy_path)
2107     expected_result = 201; /* Created */
2108   else
2109     expected_result = 204; /* Updated */
2110 
2111   if (handler->sline.code != expected_result)
2112     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2113 
2114   return SVN_NO_ERROR;
2115 }
2116 
2117 static svn_error_t *
change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)2118 change_file_prop(void *file_baton,
2119                  const char *name,
2120                  const svn_string_t *value,
2121                  apr_pool_t *pool)
2122 {
2123   file_context_t *file = file_baton;
2124   svn_prop_t *prop;
2125 
2126   prop = apr_palloc(file->pool, sizeof(*prop));
2127 
2128   prop->name = apr_pstrdup(file->pool, name);
2129   prop->value = svn_string_dup(value, file->pool);
2130 
2131   svn_hash_sets(file->prop_changes, prop->name, prop);
2132 
2133   return SVN_NO_ERROR;
2134 }
2135 
2136 static svn_error_t *
close_file(void * file_baton,const char * text_checksum,apr_pool_t * scratch_pool)2137 close_file(void *file_baton,
2138            const char *text_checksum,
2139            apr_pool_t *scratch_pool)
2140 {
2141   file_context_t *ctx = file_baton;
2142   svn_boolean_t put_empty_file = FALSE;
2143 
2144   ctx->result_checksum = text_checksum;
2145 
2146   /* If we got no stream of changes, but this is an added-without-history
2147    * file, make a note that we'll be PUTting a zero-byte file to the server.
2148    */
2149   if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path))
2150     put_empty_file = TRUE;
2151 
2152   /* If we have a stream of changes, push them to the server... */
2153   if ((ctx->svndiff || put_empty_file) && !ctx->svndiff_sent)
2154     {
2155       svn_ra_serf__handler_t *handler;
2156       int expected_result;
2157 
2158       handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
2159                                             scratch_pool);
2160 
2161       handler->method = "PUT";
2162       handler->path = ctx->url;
2163 
2164       handler->response_handler = svn_ra_serf__expect_empty_body;
2165       handler->response_baton = handler;
2166 
2167       if (put_empty_file)
2168         {
2169           handler->body_delegate = create_empty_put_body;
2170           handler->body_delegate_baton = ctx;
2171           handler->body_type = "text/plain";
2172         }
2173       else
2174         {
2175           SVN_ERR(svn_stream_close(ctx->stream));
2176 
2177           svn_ra_serf__request_body_get_delegate(&handler->body_delegate,
2178                                                  &handler->body_delegate_baton,
2179                                                  ctx->svndiff);
2180           handler->body_type = SVN_SVNDIFF_MIME_TYPE;
2181         }
2182 
2183       handler->header_delegate = setup_put_headers;
2184       handler->header_delegate_baton = ctx;
2185 
2186       SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
2187 
2188       if (ctx->added && ! ctx->copy_path)
2189         expected_result = 201; /* Created */
2190       else
2191         expected_result = 204; /* Updated */
2192 
2193       if (handler->sline.code != expected_result)
2194         return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2195     }
2196 
2197   /* Don't keep open file handles longer than necessary. */
2198   if (ctx->svndiff)
2199     SVN_ERR(svn_ra_serf__request_body_cleanup(ctx->svndiff, scratch_pool));
2200 
2201   /* If we had any prop changes, push them via PROPPATCH. */
2202   if (apr_hash_count(ctx->prop_changes))
2203     {
2204       proppatch_context_t *proppatch;
2205 
2206       proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch));
2207       proppatch->pool = scratch_pool;
2208       proppatch->relpath = ctx->relpath;
2209       proppatch->path = ctx->url;
2210       proppatch->commit_ctx = ctx->commit_ctx;
2211       proppatch->prop_changes = ctx->prop_changes;
2212       proppatch->base_revision = ctx->base_revision;
2213 
2214       SVN_ERR(proppatch_resource(ctx->commit_ctx->session,
2215                                  proppatch, scratch_pool));
2216     }
2217 
2218   if (ctx->result_checksum && ctx->remote_result_checksum)
2219     {
2220       svn_checksum_t *result_checksum;
2221 
2222       SVN_ERR(svn_checksum_parse_hex(&result_checksum, svn_checksum_md5,
2223                                      ctx->result_checksum, scratch_pool));
2224 
2225       if (!svn_checksum_match(result_checksum, ctx->remote_result_checksum))
2226         return svn_checksum_mismatch_err(result_checksum,
2227                                          ctx->remote_result_checksum,
2228                                          scratch_pool,
2229                                          _("Checksum mismatch for '%s'"),
2230                                          svn_dirent_local_style(ctx->relpath,
2231                                                                 scratch_pool));
2232     }
2233 
2234   ctx->commit_ctx->open_batons--;
2235 
2236   return SVN_NO_ERROR;
2237 }
2238 
2239 static svn_error_t *
close_edit(void * edit_baton,apr_pool_t * pool)2240 close_edit(void *edit_baton,
2241            apr_pool_t *pool)
2242 {
2243   commit_context_t *ctx = edit_baton;
2244   const char *merge_target =
2245     ctx->activity_url ? ctx->activity_url : ctx->txn_url;
2246   const svn_commit_info_t *commit_info;
2247   svn_error_t *err = NULL;
2248 
2249   if (ctx->open_batons > 0)
2250     return svn_error_create(
2251               SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION, NULL,
2252               _("Closing editor with directories or files open"));
2253 
2254   /* MERGE our activity */
2255   SVN_ERR(svn_ra_serf__run_merge(&commit_info,
2256                                  ctx->session,
2257                                  merge_target,
2258                                  ctx->lock_tokens,
2259                                  ctx->keep_locks,
2260                                  pool, pool));
2261 
2262   ctx->txn_url = NULL; /* If HTTPv2, the txn is now done */
2263 
2264   /* Inform the WC that we did a commit.  */
2265   if (ctx->callback)
2266     err = ctx->callback(commit_info, ctx->callback_baton, pool);
2267 
2268   /* If we're using activities, DELETE our completed activity.  */
2269   if (ctx->activity_url)
2270     {
2271       svn_ra_serf__handler_t *handler;
2272 
2273       handler = svn_ra_serf__create_handler(ctx->session, pool);
2274 
2275       handler->method = "DELETE";
2276       handler->path = ctx->activity_url;
2277 
2278       handler->response_handler = svn_ra_serf__expect_empty_body;
2279       handler->response_baton = handler;
2280 
2281       ctx->activity_url = NULL; /* Don't try again in abort_edit() on fail */
2282 
2283       SVN_ERR(svn_error_compose_create(
2284                   err,
2285                   svn_ra_serf__context_run_one(handler, pool)));
2286 
2287       if (handler->sline.code != 204)
2288         return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2289     }
2290 
2291   SVN_ERR(err);
2292 
2293   return SVN_NO_ERROR;
2294 }
2295 
2296 static svn_error_t *
abort_edit(void * edit_baton,apr_pool_t * pool)2297 abort_edit(void *edit_baton,
2298            apr_pool_t *pool)
2299 {
2300   commit_context_t *ctx = edit_baton;
2301   svn_ra_serf__handler_t *handler;
2302 
2303   /* If an activity or transaction wasn't even created, don't bother
2304      trying to delete it. */
2305   if (! (ctx->activity_url || ctx->txn_url))
2306     return SVN_NO_ERROR;
2307 
2308   /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
2309      had a problem. We need to reset it, in order to use it again.  */
2310   serf_connection_reset(ctx->session->conns[0]->conn);
2311 
2312   /* DELETE our aborted activity */
2313   handler = svn_ra_serf__create_handler(ctx->session, pool);
2314 
2315   handler->method = "DELETE";
2316 
2317   handler->response_handler = svn_ra_serf__expect_empty_body;
2318   handler->response_baton = handler;
2319   handler->no_fail_on_http_failure_status = TRUE;
2320 
2321   if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
2322     handler->path = ctx->txn_url;
2323   else
2324     handler->path = ctx->activity_url;
2325 
2326   SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
2327 
2328   /* 204 if deleted,
2329      403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
2330      404 if the activity wasn't found. */
2331   if (handler->sline.code != 204
2332       && handler->sline.code != 403
2333       && handler->sline.code != 404)
2334     {
2335       return svn_error_trace(svn_ra_serf__unexpected_status(handler));
2336     }
2337 
2338   /* Don't delete again if somebody aborts twice */
2339   ctx->activity_url = NULL;
2340   ctx->txn_url = NULL;
2341 
2342   return SVN_NO_ERROR;
2343 }
2344 
2345 svn_error_t *
svn_ra_serf__get_commit_editor(svn_ra_session_t * ra_session,const svn_delta_editor_t ** ret_editor,void ** edit_baton,apr_hash_t * revprop_table,svn_commit_callback2_t callback,void * callback_baton,apr_hash_t * lock_tokens,svn_boolean_t keep_locks,apr_pool_t * pool)2346 svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
2347                                const svn_delta_editor_t **ret_editor,
2348                                void **edit_baton,
2349                                apr_hash_t *revprop_table,
2350                                svn_commit_callback2_t callback,
2351                                void *callback_baton,
2352                                apr_hash_t *lock_tokens,
2353                                svn_boolean_t keep_locks,
2354                                apr_pool_t *pool)
2355 {
2356   svn_ra_serf__session_t *session = ra_session->priv;
2357   svn_delta_editor_t *editor;
2358   commit_context_t *ctx;
2359   const char *repos_root;
2360   const char *base_relpath;
2361   svn_boolean_t supports_ephemeral_props;
2362 
2363   ctx = apr_pcalloc(pool, sizeof(*ctx));
2364 
2365   ctx->pool = pool;
2366 
2367   ctx->session = session;
2368 
2369   ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
2370 
2371   /* If the server supports ephemeral properties, add some carrying
2372      interesting version information. */
2373   SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
2374                                       SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
2375                                       pool));
2376   if (supports_ephemeral_props)
2377     {
2378       svn_hash_sets(ctx->revprop_table,
2379                     apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
2380                     svn_string_create(SVN_VER_NUMBER, pool));
2381       svn_hash_sets(ctx->revprop_table,
2382                     apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
2383                     svn_string_create(session->useragent, pool));
2384     }
2385 
2386   ctx->callback = callback;
2387   ctx->callback_baton = callback_baton;
2388 
2389   ctx->lock_tokens = (lock_tokens && apr_hash_count(lock_tokens))
2390                        ? lock_tokens : NULL;
2391   ctx->keep_locks = keep_locks;
2392 
2393   ctx->deleted_entries = apr_hash_make(ctx->pool);
2394 
2395   editor = svn_delta_default_editor(pool);
2396   editor->open_root = open_root;
2397   editor->delete_entry = delete_entry;
2398   editor->add_directory = add_directory;
2399   editor->open_directory = open_directory;
2400   editor->change_dir_prop = change_dir_prop;
2401   editor->close_directory = close_directory;
2402   editor->add_file = add_file;
2403   editor->open_file = open_file;
2404   editor->apply_textdelta = apply_textdelta;
2405   editor->change_file_prop = change_file_prop;
2406   editor->close_file = close_file;
2407   editor->close_edit = close_edit;
2408   editor->abort_edit = abort_edit;
2409   /* Only install the callback that allows streaming PUT request bodies
2410    * if the server has the necessary capability.  Otherwise, this will
2411    * fallback to the default implementation using the temporary files.
2412    * See default_editor.c:apply_textdelta_stream(). */
2413   if (session->supports_put_result_checksum)
2414     editor->apply_textdelta_stream = apply_textdelta_stream;
2415 
2416   *ret_editor = editor;
2417   *edit_baton = ctx;
2418 
2419   SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
2420   base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
2421                                        pool);
2422 
2423   SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
2424                                    *edit_baton, repos_root, base_relpath,
2425                                    session->shim_callbacks, pool, pool));
2426 
2427   return SVN_NO_ERROR;
2428 }
2429 
2430 svn_error_t *
svn_ra_serf__change_rev_prop(svn_ra_session_t * ra_session,svn_revnum_t rev,const char * name,const svn_string_t * const * old_value_p,const svn_string_t * value,apr_pool_t * pool)2431 svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
2432                              svn_revnum_t rev,
2433                              const char *name,
2434                              const svn_string_t *const *old_value_p,
2435                              const svn_string_t *value,
2436                              apr_pool_t *pool)
2437 {
2438   svn_ra_serf__session_t *session = ra_session->priv;
2439   proppatch_context_t *proppatch_ctx;
2440   const char *proppatch_target;
2441   const svn_string_t *tmp_old_value;
2442   svn_boolean_t atomic_capable = FALSE;
2443   svn_prop_t *prop;
2444   svn_error_t *err;
2445 
2446   if (old_value_p || !value)
2447     SVN_ERR(svn_ra_serf__has_capability(ra_session, &atomic_capable,
2448                                         SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
2449                                         pool));
2450 
2451   if (old_value_p)
2452     {
2453       /* How did you get past the same check in svn_ra_change_rev_prop2()? */
2454       SVN_ERR_ASSERT(atomic_capable);
2455     }
2456   else if (! value && atomic_capable)
2457     {
2458       svn_string_t *old_value;
2459       /* mod_dav_svn doesn't report a failure when a property delete fails. The
2460          atomic revprop change behavior is a nice workaround, to allow getting
2461          access to the error anyway.
2462 
2463          Somehow the mod_dav maintainers think that returning an error from
2464          mod_dav's property delete is an RFC violation.
2465          See https://issues.apache.org/bugzilla/show_bug.cgi?id=53525 */
2466 
2467       SVN_ERR(svn_ra_serf__rev_prop(ra_session, rev, name, &old_value,
2468                                     pool));
2469 
2470       if (!old_value)
2471         return SVN_NO_ERROR; /* Nothing to delete */
2472 
2473       /* The api expects a double const pointer. Let's make one */
2474       tmp_old_value = old_value;
2475       old_value_p = &tmp_old_value;
2476     }
2477 
2478   if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2479     {
2480       proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
2481     }
2482   else
2483     {
2484       const char *vcc_url;
2485 
2486       SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
2487 
2488       SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
2489                                           session, vcc_url, rev, "href",
2490                                           pool, pool));
2491     }
2492 
2493   /* PROPPATCH our log message and pass it along.  */
2494   proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
2495   proppatch_ctx->pool = pool;
2496   proppatch_ctx->commit_ctx = NULL; /* No lock headers */
2497   proppatch_ctx->path = proppatch_target;
2498   proppatch_ctx->prop_changes = apr_hash_make(pool);
2499   proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
2500 
2501   if (old_value_p)
2502     {
2503       prop = apr_palloc(pool, sizeof (*prop));
2504 
2505       prop->name = name;
2506       prop->value = *old_value_p;
2507 
2508       proppatch_ctx->old_props = apr_hash_make(pool);
2509       svn_hash_sets(proppatch_ctx->old_props, prop->name, prop);
2510     }
2511 
2512   prop = apr_palloc(pool, sizeof (*prop));
2513 
2514   prop->name = name;
2515   prop->value = value;
2516   svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop);
2517 
2518   err = proppatch_resource(session, proppatch_ctx, pool);
2519 
2520   /* Use specific error code for old property value mismatch.
2521      Use loop to provide the right result with tracing */
2522   if (err && err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2523     {
2524       svn_error_t *e = err;
2525 
2526       while (e && e->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED)
2527         {
2528           e->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
2529           e = e->child;
2530         }
2531     }
2532 
2533   return svn_error_trace(err);
2534 }
2535