1 /*
2  * update.c :  entry point for update 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 
25 
26 #define APR_WANT_STRFUNC
27 #include <apr_version.h>
28 #include <apr_want.h>
29 
30 #include <apr_uri.h>
31 
32 #include <serf.h>
33 
34 #include "svn_hash.h"
35 #include "svn_pools.h"
36 #include "svn_ra.h"
37 #include "svn_dav.h"
38 #include "svn_xml.h"
39 #include "svn_delta.h"
40 #include "svn_path.h"
41 #include "svn_base64.h"
42 #include "svn_props.h"
43 
44 #include "svn_private_config.h"
45 #include "private/svn_dep_compat.h"
46 #include "private/svn_fspath.h"
47 #include "private/svn_string_private.h"
48 
49 #include "ra_serf.h"
50 #include "../libsvn_ra/ra_loader.h"
51 
52 
53 
54 /*
55  * This enum represents the current state of our XML parsing for a REPORT.
56  *
57  * A little explanation of how the parsing works.  Every time we see
58  * an open-directory tag, we enter the OPEN_DIR state.  Likewise, for
59  * add-directory, open-file, etc.  When we see the closing variant of the
60  * open-directory tag, we'll 'pop' out of that state.
61  *
62  * Each state has a pool associated with it that can have temporary
63  * allocations that will live as long as the tag is opened.  Once
64  * the tag is 'closed', the pool will be reused.
65  */
66 typedef enum report_state_e {
67   INITIAL = XML_STATE_INITIAL /* = 0 */,
68   UPDATE_REPORT,
69   TARGET_REVISION,
70 
71   OPEN_DIR,
72   ADD_DIR,
73 
74   OPEN_FILE,
75   ADD_FILE,
76 
77   DELETE_ENTRY,
78   ABSENT_DIR,
79   ABSENT_FILE,
80 
81   SET_PROP,
82   REMOVE_PROP,
83 
84   PROP,
85 
86   FETCH_FILE,
87   FETCH_PROPS,
88   TXDELTA,
89 
90   CHECKED_IN,
91   CHECKED_IN_HREF,
92 
93   MD5_CHECKSUM,
94 
95   VERSION_NAME,
96   CREATIONDATE,
97   CREATOR_DISPLAYNAME
98 } report_state_e;
99 
100 
101 #define D_ "DAV:"
102 #define S_ SVN_XML_NAMESPACE
103 #define V_ SVN_DAV_PROP_NS_DAV
104 static const svn_ra_serf__xml_transition_t update_ttable[] = {
105   { INITIAL, S_, "update-report", UPDATE_REPORT,
106     FALSE, { "?inline-props", "?send-all", NULL }, TRUE },
107 
108   { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
109     FALSE, { "rev", NULL }, TRUE },
110 
111   { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
112     FALSE, { "rev", NULL }, TRUE },
113 
114   { OPEN_DIR, S_, "open-directory", OPEN_DIR,
115     FALSE, { "rev", "name", NULL }, TRUE },
116 
117   { ADD_DIR, S_, "open-directory", OPEN_DIR,
118     FALSE, { "rev", "name", NULL }, TRUE },
119 
120   { OPEN_DIR, S_, "add-directory", ADD_DIR,
121     FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/
122               NULL }, TRUE },
123 
124   { ADD_DIR, S_, "add-directory", ADD_DIR,
125     FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/
126               NULL }, TRUE },
127 
128   { OPEN_DIR, S_, "open-file", OPEN_FILE,
129     FALSE, { "rev", "name", NULL }, TRUE },
130 
131   { ADD_DIR, S_, "open-file", OPEN_FILE,
132     FALSE, { "rev", "name", NULL }, TRUE },
133 
134   { OPEN_DIR, S_, "add-file", ADD_FILE,
135     FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
136              "?sha1-checksum", NULL }, TRUE },
137 
138   { ADD_DIR, S_, "add-file", ADD_FILE,
139     FALSE, { "name", "?copyfrom-path", "?copyfrom-rev",
140              "?sha1-checksum", NULL }, TRUE },
141 
142   { OPEN_DIR, S_, "delete-entry", DELETE_ENTRY,
143     FALSE, { "?rev", "name", NULL }, TRUE },
144 
145   { ADD_DIR, S_, "delete-entry", DELETE_ENTRY,
146     FALSE, { "?rev", "name", NULL }, TRUE },
147 
148   { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
149     FALSE, { "name", NULL }, TRUE },
150 
151   { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
152     FALSE, { "name", NULL }, TRUE },
153 
154   { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
155     FALSE, { "name", NULL }, TRUE },
156 
157   { ADD_DIR, S_, "absent-file", ABSENT_FILE,
158     FALSE, { "name", NULL }, TRUE },
159 
160 
161   { OPEN_DIR, D_, "checked-in", CHECKED_IN,
162     FALSE, { NULL }, FALSE },
163 
164   { ADD_DIR, D_, "checked-in", CHECKED_IN,
165     FALSE, { NULL }, FALSE },
166 
167   { OPEN_FILE, D_, "checked-in", CHECKED_IN,
168     FALSE, { NULL }, FALSE },
169 
170   { ADD_FILE, D_, "checked-in", CHECKED_IN,
171     FALSE, { NULL }, FALSE },
172 
173 
174   { OPEN_DIR, S_, "set-prop", SET_PROP,
175     TRUE, { "name", "?encoding", NULL }, TRUE },
176 
177   { ADD_DIR, S_, "set-prop", SET_PROP,
178     TRUE, { "name", "?encoding", NULL }, TRUE },
179 
180   { OPEN_FILE, S_, "set-prop", SET_PROP,
181     TRUE, { "name", "?encoding", NULL }, TRUE },
182 
183   { ADD_FILE, S_, "set-prop", SET_PROP,
184     TRUE, { "name", "?encoding", NULL }, TRUE },
185 
186 
187   { OPEN_DIR, S_, "remove-prop", REMOVE_PROP,
188     TRUE, { "name", NULL }, TRUE },
189 
190   { ADD_DIR, S_, "remove-prop", REMOVE_PROP,
191     TRUE, { "name", NULL }, TRUE },
192 
193   { OPEN_FILE, S_, "remove-prop", REMOVE_PROP,
194     TRUE, { "name", NULL }, TRUE },
195 
196   { ADD_FILE, S_, "remove-prop", REMOVE_PROP,
197     TRUE, { "name", NULL }, TRUE },
198 
199   { OPEN_FILE, S_, "prop", PROP,
200     FALSE, { NULL }, FALSE },
201   { OPEN_DIR, S_, "prop", PROP,
202     FALSE, { NULL }, FALSE },
203   { ADD_FILE, S_, "prop", PROP,
204     FALSE, { NULL }, FALSE },
205   { ADD_DIR, S_, "prop", PROP,
206     FALSE, { NULL }, FALSE },
207 
208   { OPEN_FILE, S_, "txdelta", TXDELTA,
209     FALSE, { "?base-checksum" }, TRUE },
210 
211   { ADD_FILE, S_, "txdelta", TXDELTA,
212     FALSE, { "?base-checksum" }, TRUE },
213 
214   { OPEN_FILE, S_, "fetch-file", FETCH_FILE,
215     FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE},
216 
217   { ADD_FILE, S_, "fetch-file", FETCH_FILE,
218     FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE },
219 
220   { CHECKED_IN, D_, "href", CHECKED_IN_HREF,
221     TRUE, { NULL }, TRUE },
222 
223   { PROP, V_, "md5-checksum", MD5_CHECKSUM,
224     TRUE, { NULL }, TRUE },
225 
226   /* These are only reported for <= 1.6.x mod_dav_svn */
227   { OPEN_DIR, S_, "fetch-props", FETCH_PROPS,
228     FALSE, { NULL }, FALSE },
229   { OPEN_FILE, S_, "fetch-props", FETCH_PROPS,
230     FALSE, { NULL }, FALSE },
231 
232   { PROP, D_, "version-name", VERSION_NAME,
233     TRUE, { NULL }, TRUE },
234   { PROP, D_, "creationdate", CREATIONDATE,
235     TRUE, { NULL }, TRUE },
236   { PROP, D_, "creator-displayname", CREATOR_DISPLAYNAME,
237     TRUE, { NULL }, TRUE },
238   { 0 }
239 };
240 
241 /* While we process the REPORT response, we will queue up GET and PROPFIND
242    requests. For a very large checkout, it is very easy to queue requests
243    faster than they are resolved. Thus, we need to pause the XML processing
244    (which queues more requests) to avoid queueing too many, with their
245    attendant memory costs. When the queue count drops low enough, we will
246    resume XML processing.
247 
248    Note that we don't want the count to drop to zero. We have multiple
249    connections that we want to keep busy. These are also heuristic numbers
250    since network and parsing behavior (ie. it doesn't pause immediately)
251    can make the measurements quite imprecise.
252 
253    We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and
254    NUM_ACTIVE_PROPFINDS in the report_context_t structure.  */
255 #define REQUEST_COUNT_TO_PAUSE 50
256 #define REQUEST_COUNT_TO_RESUME 40
257 
258 #define SPILLBUF_BLOCKSIZE 4096
259 #define SPILLBUF_MAXBUFFSIZE 131072
260 
261 #define PARSE_CHUNK_SIZE 8000 /* Copied from xml.c ### Needs tuning */
262 
263 /* Forward-declare our report context. */
264 typedef struct report_context_t report_context_t;
265 typedef struct body_create_baton_t body_create_baton_t;
266 /*
267  * This structure represents the information for a directory.
268  */
269 typedef struct dir_baton_t
270 {
271   struct dir_baton_t *parent_dir;       /* NULL when root */
272 
273   apr_pool_t *pool;                     /* Subpool for this directory */
274 
275   /* Pointer back to our original report context. */
276   report_context_t *ctx;
277 
278   const char *relpath;                  /* session relative path */
279   const char *base_name;                /* Name of item "" for root */
280 
281   /* the canonical url for this directory after updating. (received) */
282   const char *url;
283 
284   /* The original repos_relpath of this url (via the reporter)
285   directly, or via an ancestor. */
286   const char *repos_relpath;
287 
288   svn_revnum_t base_rev;                /* base revision or NULL for Add */
289 
290   const char *copyfrom_path;            /* NULL for open */
291   svn_revnum_t copyfrom_rev;            /* SVN_INVALID_REVNUM for open */
292 
293   /* controlling dir baton - this is only created in ensure_dir_opened() */
294   svn_boolean_t dir_opened;
295   void *dir_baton;
296 
297   /* How many references to this directory do we still have open? */
298   apr_size_t ref_count;
299 
300   svn_boolean_t fetch_props;                 /* Use PROPFIND request? */
301   svn_ra_serf__handler_t *propfind_handler;
302   apr_hash_t *remove_props;
303 
304 } dir_baton_t;
305 
306 /*
307 * This structure represents the information for a file.
308 *
309 * This structure is created as we parse the REPORT response and
310 * once the element is completed, we may create a fetch_ctx_t structure
311 * to give to serf to retrieve this file.
312 */
313 typedef struct file_baton_t
314 {
315   dir_baton_t *parent_dir;              /* The parent */
316   apr_pool_t *pool;                     /* Subpool for this file*/
317 
318   const char *relpath;                  /* session relative path */
319   const char *base_name;
320 
321   /* the canonical url for this directory after updating. (received) */
322   const char *url;
323 
324   /* The original repos_relpath of this url as reported. */
325   const char *repos_relpath;
326 
327   /* lock token, if we had one to start off with. */
328   const char *lock_token;
329 
330   svn_revnum_t base_rev;                /* SVN_INVALID_REVNUM for Add */
331 
332   const char *copyfrom_path;            /* NULL for open */
333   svn_revnum_t copyfrom_rev;            /* SVN_INVALID_REVNUM for open */
334 
335   /* controlling dir baton - this is only created in ensure_file_opened() */
336   svn_boolean_t file_opened;
337   void *file_baton;
338 
339   svn_boolean_t fetch_props;            /* Use PROPFIND request? */
340   svn_ra_serf__handler_t *propfind_handler;
341   svn_boolean_t found_lock_prop;
342   apr_hash_t *remove_props;
343 
344   /* Has the server told us to go fetch - only valid if we had it already */
345   svn_boolean_t fetch_file;
346 
347   /* controlling file_baton and textdelta handler */
348   svn_txdelta_window_handler_t txdelta;
349   void *txdelta_baton;
350 
351   svn_checksum_t *base_md5_checksum;
352   svn_checksum_t *final_md5_checksum;
353   svn_checksum_t *final_sha1_checksum;
354 
355   svn_stream_t *txdelta_stream;         /* Stream that feeds windows when
356                                            written to within txdelta*/
357 } file_baton_t;
358 
359 /*
360  * This structure represents a single request to GET (fetch) a file with
361  * its associated Serf session/connection.
362  */
363 typedef struct fetch_ctx_t {
364 
365   /* The handler representing this particular fetch.  */
366   svn_ra_serf__handler_t *handler;
367 
368   svn_ra_serf__session_t *session;
369 
370   /* Stores the information for the file we want to fetch. */
371   file_baton_t *file;
372 
373   /* Have we read our response headers yet? */
374   svn_boolean_t read_headers;
375 
376   /* This flag is set when our response is aborted before we reach the
377    * end and we decide to requeue this request.
378    */
379   svn_boolean_t aborted_read;
380   apr_off_t aborted_read_size;
381 
382   /* This is the amount of data that we have read so far. */
383   apr_off_t read_size;
384 
385   /* If we're writing this file to a stream, this will be non-NULL. */
386   svn_stream_t *result_stream;
387 
388   /* The base-rev header  */
389   const char *delta_base;
390 
391 } fetch_ctx_t;
392 
393 /*
394  * The master structure for a REPORT request and response.
395  */
396 struct report_context_t {
397   apr_pool_t *pool;
398 
399   svn_ra_serf__session_t *sess;
400 
401   /* Source path and destination path */
402   const char *source;
403   const char *destination;
404 
405   /* Our update target. */
406   const char *update_target;
407 
408   /* What is the target revision that we want for this REPORT? */
409   svn_revnum_t target_rev;
410 
411   /* Where are we (used while parsing) */
412   dir_baton_t *cur_dir;
413   file_baton_t *cur_file;
414 
415   /* Have we been asked to ignore ancestry or textdeltas? */
416   svn_boolean_t ignore_ancestry;
417   svn_boolean_t text_deltas;
418 
419   /* Do we want the server to send copyfrom args or not? */
420   svn_boolean_t send_copyfrom_args;
421 
422   /* Is the server sending everything in one response? */
423   svn_boolean_t send_all_mode;
424 
425   /* Is the server including properties inline for newly added
426      files/dirs? */
427   svn_boolean_t add_props_included;
428 
429   /* Path -> const char *repos_relpath mapping */
430   apr_hash_t *switched_paths;
431 
432   /* Our master update editor and baton. */
433   const svn_delta_editor_t *editor;
434   void *editor_baton;
435 
436   /* Stream for collecting the request body. */
437   svn_stream_t *body_template;
438 
439   /* Buffer holding request body for the REPORT (can spill to disk). */
440   svn_ra_serf__request_body_t *body;
441 
442   /* number of pending GET requests */
443   unsigned int num_active_fetches;
444 
445   /* number of pending PROPFIND requests */
446   unsigned int num_active_propfinds;
447 
448   /* Are we done parsing the REPORT response? */
449   svn_boolean_t done;
450 
451   /* Did we receive all data from the network? */
452   svn_boolean_t report_received;
453 
454   /* Did we close the root directory? */
455   svn_boolean_t closed_root;
456 };
457 
458 static svn_error_t *
create_dir_baton(dir_baton_t ** new_dir,report_context_t * ctx,const char * name,apr_pool_t * scratch_pool)459 create_dir_baton(dir_baton_t **new_dir,
460                  report_context_t *ctx,
461                  const char *name,
462                  apr_pool_t *scratch_pool)
463 {
464   dir_baton_t *parent = ctx->cur_dir;
465   apr_pool_t *dir_pool;
466   dir_baton_t *dir;
467 
468   if (parent)
469     dir_pool = svn_pool_create(parent->pool);
470   else
471     dir_pool = svn_pool_create(ctx->pool);
472 
473   dir = apr_pcalloc(dir_pool, sizeof(*dir));
474   dir->pool = dir_pool;
475   dir->ctx = ctx;
476 
477   if (parent)
478     {
479       dir->parent_dir = parent;
480       parent->ref_count++;
481     }
482 
483   dir->relpath = parent ? svn_relpath_join(parent->relpath, name, dir_pool)
484                         : apr_pstrdup(dir_pool, name);
485   dir->base_name = svn_relpath_basename(dir->relpath, NULL);
486 
487   dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->relpath);
488   if (!dir->repos_relpath)
489     {
490       if (parent)
491         dir->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
492                                               dir_pool);
493       else
494         dir->repos_relpath = svn_uri_skip_ancestor(ctx->sess->repos_root_str,
495                                                    ctx->sess->session_url_str,
496                                                    dir_pool);
497     }
498 
499   dir->base_rev = SVN_INVALID_REVNUM;
500   dir->copyfrom_rev = SVN_INVALID_REVNUM;
501 
502   dir->ref_count = 1;
503 
504   ctx->cur_dir = dir;
505 
506   *new_dir = dir;
507   return SVN_NO_ERROR;
508 }
509 
510 static svn_error_t *
create_file_baton(file_baton_t ** new_file,report_context_t * ctx,const char * name,apr_pool_t * scratch_pool)511 create_file_baton(file_baton_t **new_file,
512                   report_context_t *ctx,
513                   const char *name,
514                   apr_pool_t *scratch_pool)
515 {
516   dir_baton_t *parent = ctx->cur_dir;
517   apr_pool_t *file_pool;
518   file_baton_t *file;
519 
520   file_pool = svn_pool_create(parent->pool);
521 
522   file = apr_pcalloc(file_pool, sizeof(*file));
523   file->pool = file_pool;
524 
525   file->parent_dir = parent;
526   parent->ref_count++;
527 
528   file->relpath = svn_relpath_join(parent->relpath, name, file_pool);
529   file->base_name = svn_relpath_basename(file->relpath, NULL);
530 
531   file->repos_relpath = svn_hash_gets(ctx->switched_paths, file->relpath);
532   if (!file->repos_relpath)
533     file->repos_relpath = svn_relpath_join(parent->repos_relpath, name,
534                                            file_pool);
535 
536   /* Sane defaults */
537   file->base_rev = SVN_INVALID_REVNUM;
538   file->copyfrom_rev = SVN_INVALID_REVNUM;
539 
540   *new_file = file;
541 
542   ctx->cur_file = file;
543 
544   return SVN_NO_ERROR;
545 }
546 
547 /** Minimum nr. of outstanding requests needed before a new connection is
548  *  opened. */
549 #define REQS_PER_CONN 8
550 
551 /** This function creates a new connection for this serf session, but only
552  * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
553  * only one main connection open.
554  */
555 static svn_error_t *
open_connection_if_needed(svn_ra_serf__session_t * sess,int num_active_reqs)556 open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
557 {
558   /* For each REQS_PER_CONN outstanding requests open a new connection, with
559    * a minimum of 1 extra connection. */
560   if (sess->num_conns == 1 ||
561       ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
562     {
563       int cur = sess->num_conns;
564       apr_status_t status;
565 
566       sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
567       sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
568                                                                  NULL, NULL);
569       sess->conns[cur]->last_status_code = -1;
570       sess->conns[cur]->session = sess;
571       status = serf_connection_create2(&sess->conns[cur]->conn,
572                                        sess->context,
573                                        sess->session_url,
574                                        svn_ra_serf__conn_setup,
575                                        sess->conns[cur],
576                                        svn_ra_serf__conn_closed,
577                                        sess->conns[cur],
578                                        sess->pool);
579       if (status)
580         return svn_ra_serf__wrap_err(status, NULL);
581 
582       sess->num_conns++;
583     }
584 
585   return SVN_NO_ERROR;
586 }
587 
588 /* Returns best connection for fetching files/properties. */
589 static svn_ra_serf__connection_t *
get_best_connection(report_context_t * ctx)590 get_best_connection(report_context_t *ctx)
591 {
592   svn_ra_serf__connection_t *conn;
593   int first_conn = 1;
594 
595   /* Skip the first connection if the REPORT response hasn't been completely
596      received yet or if we're being told to limit our connections to
597      2 (because this could be an attempt to ensure that we do all our
598      auxiliary GETs/PROPFINDs on a single connection).
599 
600      ### FIXME: This latter requirement (max_connections > 2) is
601      ### really just a hack to work around the fact that some update
602      ### editor implementations (such as svnrdump's dump editor)
603      ### simply can't handle the way ra_serf violates the editor v1
604      ### drive ordering requirements.
605      ###
606      ### See https://issues.apache.org/jira/browse/SVN-4116.
607   */
608   if (ctx->report_received && (ctx->sess->max_connections > 2))
609     first_conn = 0;
610 
611   /* If there's only one available auxiliary connection to use, don't bother
612      doing all the cur_conn math -- just return that one connection.  */
613   if (ctx->sess->num_conns - first_conn == 1)
614     {
615       conn = ctx->sess->conns[first_conn];
616     }
617   else
618     {
619 #if SERF_VERSION_AT_LEAST(1, 4, 0)
620       /* Often one connection is slower than others, e.g. because the server
621          process/thread has to do more work for the particular set of requests.
622          In the worst case, when REQUEST_COUNT_TO_RESUME requests are queued
623          on such a slow connection, ra_serf will completely stop sending
624          requests.
625 
626          The method used here selects the connection with the least amount of
627          pending requests, thereby giving more work to lightly loaded server
628          processes.
629        */
630       int i, best_conn = first_conn;
631       unsigned int min = INT_MAX;
632       for (i = first_conn; i < ctx->sess->num_conns; i++)
633         {
634           serf_connection_t *sc = ctx->sess->conns[i]->conn;
635           unsigned int pending = serf_connection_pending_requests(sc);
636           if (pending < min)
637             {
638               min = pending;
639               best_conn = i;
640             }
641         }
642       conn = ctx->sess->conns[best_conn];
643 #else
644     /* We don't know how many requests are pending per connection, so just
645        cycle them. */
646       conn = ctx->sess->conns[ctx->sess->cur_conn];
647       ctx->sess->cur_conn++;
648       if (ctx->sess->cur_conn >= ctx->sess->num_conns)
649         ctx->sess->cur_conn = first_conn;
650 #endif
651     }
652   return conn;
653 }
654 
655 /** Helpers to open and close directories */
656 
657 static svn_error_t*
ensure_dir_opened(dir_baton_t * dir,apr_pool_t * scratch_pool)658 ensure_dir_opened(dir_baton_t *dir,
659                   apr_pool_t *scratch_pool)
660 {
661   report_context_t *ctx = dir->ctx;
662 
663   if (dir->dir_opened)
664     return SVN_NO_ERROR;
665 
666   if (dir->base_name[0] == '\0')
667     {
668       if (ctx->destination
669           && ctx->sess->wc_callbacks->invalidate_wc_props)
670         {
671           SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
672                       ctx->sess->wc_callback_baton,
673                       ctx->update_target,
674                       SVN_RA_SERF__WC_CHECKED_IN_URL, scratch_pool));
675         }
676 
677       SVN_ERR(ctx->editor->open_root(ctx->editor_baton, dir->base_rev,
678                                      dir->pool,
679                                      &dir->dir_baton));
680     }
681   else
682     {
683       SVN_ERR(ensure_dir_opened(dir->parent_dir, scratch_pool));
684 
685       if (SVN_IS_VALID_REVNUM(dir->base_rev))
686         {
687           SVN_ERR(ctx->editor->open_directory(dir->relpath,
688                                               dir->parent_dir->dir_baton,
689                                               dir->base_rev,
690                                               dir->pool,
691                                               &dir->dir_baton));
692         }
693       else
694         {
695           SVN_ERR(ctx->editor->add_directory(dir->relpath,
696                                              dir->parent_dir->dir_baton,
697                                              dir->copyfrom_path,
698                                              dir->copyfrom_rev,
699                                              dir->pool,
700                                              &dir->dir_baton));
701         }
702     }
703 
704   dir->dir_opened = TRUE;
705 
706   return SVN_NO_ERROR;
707 }
708 
709 static svn_error_t *
maybe_close_dir(dir_baton_t * dir)710 maybe_close_dir(dir_baton_t *dir)
711 {
712   apr_pool_t *scratch_pool = dir->pool;
713   dir_baton_t *parent = dir->parent_dir;
714   report_context_t *ctx = dir->ctx;
715 
716   if (--dir->ref_count)
717     {
718       return SVN_NO_ERROR;
719     }
720 
721   SVN_ERR(ensure_dir_opened(dir, dir->pool));
722 
723   if (dir->remove_props)
724     {
725       apr_hash_index_t *hi;
726 
727       for (hi = apr_hash_first(scratch_pool, dir->remove_props);
728            hi;
729            hi = apr_hash_next(hi))
730         {
731           SVN_ERR(ctx->editor->change_file_prop(dir->dir_baton,
732                                                 apr_hash_this_key(hi),
733                                                 NULL /* value */,
734                                                 scratch_pool));
735         }
736     }
737 
738   SVN_ERR(dir->ctx->editor->close_directory(dir->dir_baton, scratch_pool));
739 
740   svn_pool_destroy(dir->pool /* scratch_pool */);
741 
742   if (parent)
743     return svn_error_trace(maybe_close_dir(parent));
744   else
745     return SVN_NO_ERROR;
746 }
747 
748 static svn_error_t *
ensure_file_opened(file_baton_t * file,apr_pool_t * scratch_pool)749 ensure_file_opened(file_baton_t *file,
750                    apr_pool_t *scratch_pool)
751 {
752   const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
753 
754   if (file->file_opened)
755     return SVN_NO_ERROR;
756 
757   /* Ensure our parent is open. */
758   SVN_ERR(ensure_dir_opened(file->parent_dir, scratch_pool));
759 
760   /* Open (or add) the file. */
761   if (SVN_IS_VALID_REVNUM(file->base_rev))
762     {
763       SVN_ERR(editor->open_file(file->relpath,
764                                 file->parent_dir->dir_baton,
765                                 file->base_rev,
766                                 file->pool,
767                                 &file->file_baton));
768     }
769   else
770     {
771       SVN_ERR(editor->add_file(file->relpath,
772                                file->parent_dir->dir_baton,
773                                file->copyfrom_path,
774                                file->copyfrom_rev,
775                                file->pool,
776                                &file->file_baton));
777     }
778 
779   file->file_opened = TRUE;
780 
781   return SVN_NO_ERROR;
782 }
783 
784 
785 /** Routines called when we are fetching a file */
786 
787 static svn_error_t *
headers_fetch(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)788 headers_fetch(serf_bucket_t *headers,
789               void *baton,
790               apr_pool_t *pool /* request pool */,
791               apr_pool_t *scratch_pool)
792 {
793   fetch_ctx_t *fetch_ctx = baton;
794 
795   /* note that we have old VC URL */
796   if (fetch_ctx->delta_base)
797     {
798       serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
799                                fetch_ctx->delta_base);
800       svn_ra_serf__setup_svndiff_accept_encoding(headers, fetch_ctx->session);
801     }
802   else if (fetch_ctx->session->using_compression != svn_tristate_false)
803     {
804       serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
805     }
806 
807   return SVN_NO_ERROR;
808 }
809 
810 static svn_error_t *
cancel_fetch(serf_request_t * request,serf_bucket_t * response,int status_code,void * baton)811 cancel_fetch(serf_request_t *request,
812              serf_bucket_t *response,
813              int status_code,
814              void *baton)
815 {
816   fetch_ctx_t *fetch_ctx = baton;
817 
818   /* Uh-oh.  Our connection died on us.
819    *
820    * The core ra_serf layer will requeue our request - we just need to note
821    * that we got cut off in the middle of our song.
822    */
823   if (!response)
824     {
825       /* If we already started the fetch and opened the file handle, we need
826        * to hold subsequent read() ops until we get back to where we were
827        * before the close and we can then resume the textdelta() calls.
828        */
829       if (fetch_ctx->read_headers)
830         {
831           if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
832             {
833               fetch_ctx->aborted_read = TRUE;
834               fetch_ctx->aborted_read_size = fetch_ctx->read_size;
835             }
836           fetch_ctx->read_size = 0;
837         }
838 
839       return SVN_NO_ERROR;
840     }
841 
842   /* We have no idea what went wrong. */
843   SVN_ERR_MALFUNCTION();
844 }
845 
846 /* Wield the editor referenced by INFO to open (or add) the file
847    file also associated with INFO, setting properties on the file and
848    calling the editor's apply_textdelta() function on it if necessary
849    (or if FORCE_APPLY_TEXTDELTA is set).
850 
851    Callers will probably want to also see the function that serves
852    the opposite purpose of this one, close_updated_file().  */
853 static svn_error_t *
open_file_txdelta(file_baton_t * file,apr_pool_t * scratch_pool)854 open_file_txdelta(file_baton_t *file,
855                   apr_pool_t *scratch_pool)
856 {
857   const svn_delta_editor_t *editor = file->parent_dir->ctx->editor;
858 
859   SVN_ERR_ASSERT(file->txdelta == NULL);
860 
861   SVN_ERR(ensure_file_opened(file, scratch_pool));
862 
863   /* Get (maybe) a textdelta window handler for transmitting file
864      content changes. */
865   SVN_ERR(editor->apply_textdelta(file->file_baton,
866                                   svn_checksum_to_cstring(
867                                                   file->base_md5_checksum,
868                                                   scratch_pool),
869                                   file->pool,
870                                   &file->txdelta,
871                                   &file->txdelta_baton));
872 
873   return SVN_NO_ERROR;
874 }
875 
876 /* Close the file, handling loose ends and cleanup */
877 static svn_error_t *
close_file(file_baton_t * file,apr_pool_t * scratch_pool)878 close_file(file_baton_t *file,
879            apr_pool_t *scratch_pool)
880 {
881   dir_baton_t *parent_dir = file->parent_dir;
882   report_context_t *ctx = parent_dir->ctx;
883 
884   SVN_ERR(ensure_file_opened(file, scratch_pool));
885 
886   /* Set all of the properties we received */
887   if (file->remove_props)
888     {
889       apr_hash_index_t *hi;
890 
891       for (hi = apr_hash_first(scratch_pool, file->remove_props);
892            hi;
893            hi = apr_hash_next(hi))
894         {
895           SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
896                                                 apr_hash_this_key(hi),
897                                                 NULL /* value */,
898                                                 scratch_pool));
899         }
900     }
901 
902   /* Check for lock information. */
903 
904   /* This works around a bug in some older versions of mod_dav_svn in that it
905    * will not send remove-prop in the update report when a lock property
906    * disappears when send-all is false.
907 
908    ### Given that we only fetch props on additions, is this really necessary?
909        Or is it covering up old local copy bugs where we copied locks to other
910        paths? */
911   if (!ctx->add_props_included
912       && file->lock_token && !file->found_lock_prop
913       && SVN_IS_VALID_REVNUM(file->base_rev) /* file_is_added */)
914     {
915       SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
916                                             SVN_PROP_ENTRY_LOCK_TOKEN,
917                                             NULL,
918                                             scratch_pool));
919     }
920 
921   if (file->url)
922     {
923       SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
924                                             SVN_RA_SERF__WC_CHECKED_IN_URL,
925                                             svn_string_create(file->url,
926                                                               scratch_pool),
927                                             scratch_pool));
928     }
929 
930   /* Close the file via the editor. */
931   SVN_ERR(ctx->editor->close_file(file->file_baton,
932                                   svn_checksum_to_cstring(
933                                         file->final_md5_checksum,
934                                         scratch_pool),
935                                   scratch_pool));
936 
937   svn_pool_destroy(file->pool);
938 
939   SVN_ERR(maybe_close_dir(parent_dir)); /* Remove reference */
940 
941   return SVN_NO_ERROR;
942 }
943 
944 /* Implements svn_ra_serf__response_handler_t */
945 static svn_error_t *
handle_fetch(serf_request_t * request,serf_bucket_t * response,void * handler_baton,apr_pool_t * pool)946 handle_fetch(serf_request_t *request,
947              serf_bucket_t *response,
948              void *handler_baton,
949              apr_pool_t *pool)
950 {
951   const char *data;
952   apr_size_t len;
953   apr_status_t status;
954   fetch_ctx_t *fetch_ctx = handler_baton;
955   file_baton_t *file = fetch_ctx->file;
956 
957   /* ### new field. make sure we didn't miss some initialization.  */
958   SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
959 
960   if (!fetch_ctx->read_headers)
961     {
962       serf_bucket_t *hdrs;
963       const char *val;
964 
965       /* If the error code wasn't 200, something went wrong. Don't use the
966        * returned data as its probably an error message. Just bail out instead.
967        */
968       if (fetch_ctx->handler->sline.code != 200)
969         {
970           fetch_ctx->handler->discard_body = TRUE;
971           return SVN_NO_ERROR; /* Will return an error in the DONE handler */
972         }
973 
974       hdrs = serf_bucket_response_get_headers(response);
975       val = serf_bucket_headers_get(hdrs, "Content-Type");
976 
977       if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
978         {
979           fetch_ctx->result_stream =
980               svn_txdelta_parse_svndiff(file->txdelta,
981                                         file->txdelta_baton,
982                                         TRUE, file->pool);
983 
984           /* Validate the delta base claimed by the server matches
985              what we asked for! */
986           val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
987           if (val && fetch_ctx->delta_base == NULL)
988             {
989               /* We recieved response with delta base header while we didn't
990                  requested it -- report it as error. */
991               return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
992                                        _("GET request returned unexpected "
993                                          "delta base: %s"), val);
994             }
995           else if (val && (strcmp(val, fetch_ctx->delta_base) != 0))
996             {
997               return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
998                                        _("GET request returned unexpected "
999                                          "delta base: %s"), val);
1000             }
1001         }
1002       else
1003         {
1004           fetch_ctx->result_stream = NULL;
1005         }
1006 
1007       fetch_ctx->read_headers = TRUE;
1008     }
1009 
1010   while (TRUE)
1011     {
1012       svn_txdelta_window_t delta_window = { 0 };
1013       svn_txdelta_op_t delta_op;
1014       svn_string_t window_data;
1015 
1016       status = serf_bucket_read(response, 8000, &data, &len);
1017       if (SERF_BUCKET_READ_ERROR(status))
1018         {
1019           return svn_ra_serf__wrap_err(status, NULL);
1020         }
1021 
1022       fetch_ctx->read_size += len;
1023 
1024       if (fetch_ctx->aborted_read)
1025         {
1026           apr_off_t skip;
1027           /* We haven't caught up to where we were before. */
1028           if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1029             {
1030               /* Eek.  What did the file shrink or something? */
1031               if (APR_STATUS_IS_EOF(status))
1032                 {
1033                   SVN_ERR_MALFUNCTION();
1034                 }
1035 
1036               /* Skip on to the next iteration of this loop. */
1037               if (status /* includes EAGAIN */)
1038                 return svn_ra_serf__wrap_err(status, NULL);
1039 
1040               continue;
1041             }
1042 
1043           /* Woo-hoo.  We're back. */
1044           fetch_ctx->aborted_read = FALSE;
1045 
1046           /* Update data and len to just provide the new data. */
1047           skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
1048           data += skip;
1049           len -= (apr_size_t)skip;
1050         }
1051 
1052       if (fetch_ctx->result_stream)
1053         SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, &len));
1054 
1055       /* otherwise, manually construct the text delta window. */
1056       else if (len)
1057         {
1058           window_data.data = data;
1059           window_data.len = len;
1060 
1061           delta_op.action_code = svn_txdelta_new;
1062           delta_op.offset = 0;
1063           delta_op.length = len;
1064 
1065           delta_window.tview_len = len;
1066           delta_window.num_ops = 1;
1067           delta_window.ops = &delta_op;
1068           delta_window.new_data = &window_data;
1069 
1070           /* write to the file located in the info. */
1071           SVN_ERR(file->txdelta(&delta_window, file->txdelta_baton));
1072         }
1073 
1074       if (APR_STATUS_IS_EOF(status))
1075         {
1076           if (fetch_ctx->result_stream)
1077             SVN_ERR(svn_stream_close(fetch_ctx->result_stream));
1078           else
1079             SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
1080         }
1081 
1082       /* Report EOF, EEAGAIN and other special errors to serf */
1083       if (status)
1084         return svn_ra_serf__wrap_err(status, NULL);
1085     }
1086 }
1087 
1088 /* --------------------------------------------------------- */
1089 
1090 /** Wrappers around our various property walkers **/
1091 
1092 /* Implements svn_ra_serf__prop_func */
1093 static svn_error_t *
set_file_props(void * baton,const char * path,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * scratch_pool)1094 set_file_props(void *baton,
1095                const char *path,
1096                const char *ns,
1097                const char *name,
1098                const svn_string_t *val,
1099                apr_pool_t *scratch_pool)
1100 {
1101   file_baton_t *file = baton;
1102   report_context_t *ctx = file->parent_dir->ctx;
1103   const char *prop_name;
1104 
1105   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
1106 
1107   if (!prop_name)
1108     {
1109       /* This works around a bug in some older versions of
1110        * mod_dav_svn in that it will not send remove-prop in the update
1111        * report when a lock property disappears when send-all is false.
1112        *
1113        * Therefore, we'll try to look at our properties and see if there's
1114        * an active lock.  If not, then we'll assume there isn't a lock
1115        * anymore.
1116        */
1117       /* assert(!ctx->add_props_included); // Or we wouldn't be here */
1118       if (file->lock_token
1119           && !file->found_lock_prop
1120           && val
1121           && strcmp(ns, "DAV:") == 0
1122           && strcmp(name, "lockdiscovery") == 0)
1123         {
1124           char *new_lock;
1125           new_lock = apr_pstrdup(scratch_pool, val->data);
1126           apr_collapse_spaces(new_lock, new_lock);
1127 
1128           if (new_lock[0] != '\0')
1129             file->found_lock_prop = TRUE;
1130         }
1131 
1132       return SVN_NO_ERROR;
1133     }
1134 
1135   SVN_ERR(ensure_file_opened(file, scratch_pool));
1136 
1137   SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1138                                         prop_name, val,
1139                                         scratch_pool));
1140 
1141   return SVN_NO_ERROR;
1142 }
1143 
1144 /* Implements svn_ra_serf__response_done_delegate_t */
1145 static svn_error_t *
file_props_done(serf_request_t * request,void * baton,apr_pool_t * scratch_pool)1146 file_props_done(serf_request_t *request,
1147                 void *baton,
1148                 apr_pool_t *scratch_pool)
1149 {
1150   file_baton_t *file = baton;
1151   svn_ra_serf__handler_t *handler = file->propfind_handler;
1152 
1153   if (handler->server_error)
1154       return svn_error_trace(svn_ra_serf__server_error_create(handler,
1155                                                               scratch_pool));
1156 
1157   if (handler->sline.code != 207)
1158     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1159 
1160   file->parent_dir->ctx->num_active_propfinds--;
1161 
1162   file->fetch_props = FALSE;
1163 
1164   if (file->fetch_file)
1165     return SVN_NO_ERROR; /* Still processing file request */
1166 
1167   /* Closing the file will automatically deliver the propfind props.
1168    *
1169    * Note that closing the directory may dispose the pool containing the
1170    * handler, which is only a valid operation in this callback, as only
1171    * after this callback our serf plumbing assumes the request is done. */
1172 
1173   return svn_error_trace(close_file(file, scratch_pool));
1174 }
1175 
1176 static svn_error_t *
file_fetch_done(serf_request_t * request,void * baton,apr_pool_t * scratch_pool)1177 file_fetch_done(serf_request_t *request,
1178                 void *baton,
1179                 apr_pool_t *scratch_pool)
1180 {
1181   fetch_ctx_t *fetch_ctx = baton;
1182   file_baton_t *file = fetch_ctx->file;
1183   svn_ra_serf__handler_t *handler = fetch_ctx->handler;
1184 
1185   if (handler->server_error)
1186       return svn_error_trace(svn_ra_serf__server_error_create(handler,
1187                                                               scratch_pool));
1188 
1189   if (handler->sline.code != 200)
1190     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1191 
1192   file->parent_dir->ctx->num_active_fetches--;
1193 
1194   file->fetch_file = FALSE;
1195 
1196   if (file->fetch_props)
1197     return SVN_NO_ERROR; /* Still processing PROPFIND request */
1198 
1199   /* Closing the file will automatically deliver the propfind props.
1200    *
1201    * Note that closing the directory may dispose the pool containing the
1202    * handler, fetch_ctx, etc. which is only a valid operation in this
1203    * callback, as only after this callback our serf plumbing assumes the
1204    * request is done. */
1205   return svn_error_trace(close_file(file, scratch_pool));
1206 }
1207 
1208 /* Initiates additional requests needed for a file when not in "send-all" mode.
1209  */
1210 static svn_error_t *
fetch_for_file(file_baton_t * file,apr_pool_t * scratch_pool)1211 fetch_for_file(file_baton_t *file,
1212                apr_pool_t *scratch_pool)
1213 {
1214   report_context_t *ctx = file->parent_dir->ctx;
1215   svn_ra_serf__connection_t *conn;
1216   svn_ra_serf__handler_t *handler;
1217 
1218   /* Open extra connections if we have enough requests to send. */
1219   if (ctx->sess->num_conns < ctx->sess->max_connections)
1220     SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
1221                                                  ctx->num_active_propfinds));
1222 
1223   /* What connection should we go on? */
1224   conn = get_best_connection(ctx);
1225 
1226   /* Note that we (still) use conn for both requests.. Should we send
1227      them out on different connections? */
1228 
1229   if (file->fetch_file)
1230     {
1231       SVN_ERR(open_file_txdelta(file, scratch_pool));
1232 
1233       if (!ctx->text_deltas
1234           || file->txdelta == svn_delta_noop_window_handler)
1235         {
1236           SVN_ERR(file->txdelta(NULL, file->txdelta_baton));
1237           file->fetch_file = FALSE;
1238         }
1239 
1240       if (file->fetch_file
1241           && file->final_sha1_checksum
1242           && ctx->sess->wc_callbacks->get_wc_contents)
1243         {
1244           svn_error_t *err;
1245           svn_stream_t *cached_contents = NULL;
1246 
1247           err = ctx->sess->wc_callbacks->get_wc_contents(
1248                                                 ctx->sess->wc_callback_baton,
1249                                                 &cached_contents,
1250                                                 file->final_sha1_checksum,
1251                                                 scratch_pool);
1252 
1253           if (err || !cached_contents)
1254             svn_error_clear(err); /* ### Can we return some/most errors? */
1255           else
1256             {
1257               /* ### For debugging purposes we could validate the md5 here,
1258                      but our implementations in libsvn_client already do that
1259                      for us... */
1260               SVN_ERR(svn_txdelta_send_stream(cached_contents,
1261                                               file->txdelta,
1262                                               file->txdelta_baton,
1263                                               NULL, scratch_pool));
1264               SVN_ERR(svn_stream_close(cached_contents));
1265               file->fetch_file = FALSE;
1266             }
1267         }
1268 
1269       if (file->fetch_file)
1270         {
1271           fetch_ctx_t *fetch_ctx;
1272 
1273           /* Let's fetch the file with a GET request... */
1274           SVN_ERR_ASSERT(file->url && file->repos_relpath);
1275 
1276           /* Otherwise, we use a GET request for the file's contents. */
1277 
1278           fetch_ctx = apr_pcalloc(file->pool, sizeof(*fetch_ctx));
1279           fetch_ctx->file = file;
1280           fetch_ctx->session = ctx->sess;
1281 
1282           /* Can we somehow get away with just obtaining a DIFF? */
1283           if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
1284             {
1285               /* If this file is switched vs the editor root we should provide
1286                  its real url instead of the one calculated from the session root.
1287               */
1288               if (SVN_IS_VALID_REVNUM(file->base_rev))
1289                 {
1290                   fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
1291                                                        ctx->sess->rev_root_stub,
1292                                                        file->base_rev,
1293                                                        svn_path_uri_encode(
1294                                                           file->repos_relpath,
1295                                                           scratch_pool));
1296                 }
1297               else if (file->copyfrom_path)
1298                 {
1299                   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(file->copyfrom_rev));
1300 
1301                   fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s",
1302                                                        ctx->sess->rev_root_stub,
1303                                                        file->copyfrom_rev,
1304                                                        svn_path_uri_encode(
1305                                                           file->copyfrom_path+1,
1306                                                           scratch_pool));
1307                 }
1308             }
1309           else if (ctx->sess->wc_callbacks->get_wc_prop)
1310             {
1311               /* If we have a WC, we might be able to dive all the way into the WC
1312               * to get the previous URL so we can do a differential GET with the
1313               * base URL.
1314               */
1315               const svn_string_t *value = NULL;
1316               SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
1317                                                 ctx->sess->wc_callback_baton,
1318                                                 file->relpath,
1319                                                 SVN_RA_SERF__WC_CHECKED_IN_URL,
1320                                                 &value, scratch_pool));
1321 
1322               fetch_ctx->delta_base = value
1323                                         ? apr_pstrdup(file->pool, value->data)
1324                                         : NULL;
1325             }
1326 
1327           handler = svn_ra_serf__create_handler(ctx->sess, file->pool);
1328 
1329           handler->method = "GET";
1330           handler->path = file->url;
1331 
1332           handler->conn = conn; /* Explicit scheduling */
1333 
1334           handler->custom_accept_encoding = TRUE;
1335           handler->no_dav_headers = TRUE;
1336           handler->header_delegate = headers_fetch;
1337           handler->header_delegate_baton = fetch_ctx;
1338 
1339           handler->response_handler = handle_fetch;
1340           handler->response_baton = fetch_ctx;
1341 
1342           handler->response_error = cancel_fetch;
1343           handler->response_error_baton = fetch_ctx;
1344 
1345           handler->done_delegate = file_fetch_done;
1346           handler->done_delegate_baton = fetch_ctx;
1347 
1348           fetch_ctx->handler = handler;
1349 
1350           svn_ra_serf__request_create(handler);
1351 
1352           ctx->num_active_fetches++;
1353         }
1354     }
1355 
1356   /* If needed, create the PROPFIND to retrieve the file's properties. */
1357   if (file->fetch_props)
1358     {
1359       SVN_ERR(svn_ra_serf__create_propfind_handler(&file->propfind_handler,
1360                                                    ctx->sess, file->url,
1361                                                    ctx->target_rev, "0",
1362                                                    all_props,
1363                                                    set_file_props, file,
1364                                                    file->pool));
1365       file->propfind_handler->conn = conn; /* Explicit scheduling */
1366 
1367       file->propfind_handler->done_delegate = file_props_done;
1368       file->propfind_handler->done_delegate_baton = file;
1369 
1370       /* Create a serf request for the PROPFIND.  */
1371       svn_ra_serf__request_create(file->propfind_handler);
1372 
1373       ctx->num_active_propfinds++;
1374     }
1375 
1376   if (file->fetch_props || file->fetch_file)
1377       return SVN_NO_ERROR;
1378 
1379 
1380   /* Somehow we are done; probably via the local cache.
1381      Close the file and release memory, etc. */
1382 
1383   return svn_error_trace(close_file(file, scratch_pool));
1384 }
1385 
1386 /* Implements svn_ra_serf__prop_func */
1387 static svn_error_t *
set_dir_prop(void * baton,const char * path,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * scratch_pool)1388 set_dir_prop(void *baton,
1389              const char *path,
1390              const char *ns,
1391              const char *name,
1392              const svn_string_t *val,
1393              apr_pool_t *scratch_pool)
1394 {
1395   dir_baton_t *dir = baton;
1396   report_context_t *ctx = dir->ctx;
1397   const char *prop_name;
1398 
1399   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
1400   if (prop_name == NULL)
1401     return SVN_NO_ERROR;
1402 
1403   SVN_ERR(ensure_dir_opened(dir, scratch_pool));
1404 
1405   SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
1406                                        prop_name, val,
1407                                        scratch_pool));
1408   return SVN_NO_ERROR;
1409 }
1410 
1411 /* Implements svn_ra_serf__response_done_delegate_t */
1412 static svn_error_t *
dir_props_done(serf_request_t * request,void * baton,apr_pool_t * scratch_pool)1413 dir_props_done(serf_request_t *request,
1414                void *baton,
1415                apr_pool_t *scratch_pool)
1416 {
1417   dir_baton_t *dir = baton;
1418   svn_ra_serf__handler_t *handler = dir->propfind_handler;
1419 
1420   if (handler->server_error)
1421     return svn_ra_serf__server_error_create(handler, scratch_pool);
1422 
1423   if (handler->sline.code != 207)
1424     return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1425 
1426   dir->ctx->num_active_propfinds--;
1427 
1428   /* Closing the directory will automatically deliver the propfind props.
1429    *
1430    * Note that closing the directory may dispose the pool containing the
1431    * handler, which is only a valid operation in this callback, as after
1432    * this callback serf assumes the request is done. */
1433 
1434   return svn_error_trace(maybe_close_dir(dir));
1435 }
1436 
1437 /* Initiates additional requests needed for a directory when not in "send-all"
1438  * mode */
1439 static svn_error_t *
fetch_for_dir(dir_baton_t * dir,apr_pool_t * scratch)1440 fetch_for_dir(dir_baton_t *dir,
1441               apr_pool_t *scratch)
1442 {
1443   report_context_t *ctx = dir->ctx;
1444   svn_ra_serf__connection_t *conn;
1445 
1446   /* Open extra connections if we have enough requests to send. */
1447   if (ctx->sess->num_conns < ctx->sess->max_connections)
1448     SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches +
1449                                                  ctx->num_active_propfinds));
1450 
1451   /* What connection should we go on? */
1452   conn = get_best_connection(ctx);
1453 
1454   /* If needed, create the PROPFIND to retrieve the file's properties. */
1455   if (dir->fetch_props)
1456     {
1457       SVN_ERR(svn_ra_serf__create_propfind_handler(&dir->propfind_handler,
1458                                                    ctx->sess, dir->url,
1459                                                    ctx->target_rev, "0",
1460                                                    all_props,
1461                                                    set_dir_prop, dir,
1462                                                    dir->pool));
1463 
1464       dir->propfind_handler->conn = conn;
1465       dir->propfind_handler->done_delegate = dir_props_done;
1466       dir->propfind_handler->done_delegate_baton = dir;
1467 
1468       /* Create a serf request for the PROPFIND.  */
1469       svn_ra_serf__request_create(dir->propfind_handler);
1470 
1471       ctx->num_active_propfinds++;
1472     }
1473   else
1474     SVN_ERR_MALFUNCTION();
1475 
1476   return SVN_NO_ERROR;
1477 }
1478 
1479 
1480 /** XML callbacks for our update-report response parsing */
1481 
1482 /* Conforms to svn_ra_serf__xml_opened_t  */
1483 static svn_error_t *
update_opened(svn_ra_serf__xml_estate_t * xes,void * baton,int entered_state,const svn_ra_serf__dav_props_t * tag,apr_pool_t * scratch_pool)1484 update_opened(svn_ra_serf__xml_estate_t *xes,
1485               void *baton,
1486               int entered_state,
1487               const svn_ra_serf__dav_props_t *tag,
1488               apr_pool_t *scratch_pool)
1489 {
1490   report_context_t *ctx = baton;
1491   apr_hash_t *attrs;
1492 
1493   switch (entered_state)
1494     {
1495       case UPDATE_REPORT:
1496         {
1497           const char *val;
1498 
1499           attrs = svn_ra_serf__xml_gather_since(xes, UPDATE_REPORT);
1500           val = svn_hash_gets(attrs, "inline-props");
1501 
1502           if (val && (strcmp(val, "true") == 0))
1503             ctx->add_props_included = TRUE;
1504 
1505           val = svn_hash_gets(attrs, "send-all");
1506 
1507           if (val && (strcmp(val, "true") == 0))
1508             {
1509               ctx->send_all_mode = TRUE;
1510 
1511               /* All properties are included in send-all mode. */
1512               ctx->add_props_included = TRUE;
1513             }
1514         }
1515         break;
1516 
1517       case OPEN_DIR:
1518       case ADD_DIR:
1519         {
1520           dir_baton_t *dir;
1521           const char *name;
1522           attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1523 
1524           name = svn_hash_gets(attrs, "name");
1525           if (!name)
1526             name = "";
1527 
1528           SVN_ERR(create_dir_baton(&dir, ctx, name, scratch_pool));
1529 
1530           if (entered_state == OPEN_DIR)
1531             {
1532               apr_int64_t base_rev;
1533 
1534               SVN_ERR(svn_cstring_atoi64(&base_rev,
1535                                          svn_hash_gets(attrs, "rev")));
1536               dir->base_rev = (svn_revnum_t)base_rev;
1537             }
1538           else
1539             {
1540               dir->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
1541 
1542               if (dir->copyfrom_path)
1543                 {
1544                   apr_int64_t copyfrom_rev;
1545                   const char *copyfrom_rev_str;
1546                   dir->copyfrom_path = svn_fspath__canonicalize(
1547                                                         dir->copyfrom_path,
1548                                                         dir->pool);
1549 
1550                   copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
1551 
1552                   if (!copyfrom_rev_str)
1553                     return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1554                                              NULL,
1555                                             _("Missing '%s' attribute"),
1556                                             "copyfrom-rev");
1557 
1558                   SVN_ERR(svn_cstring_atoi64(&copyfrom_rev, copyfrom_rev_str));
1559 
1560                   dir->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
1561                 }
1562 
1563               if (! ctx->add_props_included)
1564                 dir->fetch_props = TRUE;
1565             }
1566         }
1567         break;
1568       case OPEN_FILE:
1569       case ADD_FILE:
1570         {
1571           file_baton_t *file;
1572 
1573           attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1574 
1575           SVN_ERR(create_file_baton(&file, ctx, svn_hash_gets(attrs, "name"),
1576                                     scratch_pool));
1577 
1578           if (entered_state == OPEN_FILE)
1579             {
1580               apr_int64_t base_rev;
1581 
1582               SVN_ERR(svn_cstring_atoi64(&base_rev,
1583                                          svn_hash_gets(attrs, "rev")));
1584               file->base_rev = (svn_revnum_t)base_rev;
1585             }
1586           else
1587             {
1588               const char *sha1_checksum;
1589               file->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
1590 
1591               if (file->copyfrom_path)
1592                 {
1593                   apr_int64_t copyfrom_rev;
1594                   const char *copyfrom_rev_str;
1595 
1596                   file->copyfrom_path = svn_fspath__canonicalize(
1597                                                         file->copyfrom_path,
1598                                                         file->pool);
1599 
1600                   copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev");
1601 
1602                   if (!copyfrom_rev_str)
1603                     return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
1604                                              NULL,
1605                                             _("Missing '%s' attribute"),
1606                                             "copyfrom-rev");
1607 
1608                   SVN_ERR(svn_cstring_atoi64(&copyfrom_rev, copyfrom_rev_str));
1609 
1610                   file->copyfrom_rev = (svn_revnum_t)copyfrom_rev;
1611                 }
1612 
1613               sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
1614               if (sha1_checksum)
1615                 {
1616                   SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
1617                                                  svn_checksum_sha1,
1618                                                  sha1_checksum,
1619                                                  file->pool));
1620                 }
1621 
1622               /* If the server isn't in "send-all" mode, we should expect to
1623                  fetch contents for added files. */
1624               if (! ctx->send_all_mode)
1625                 file->fetch_file = TRUE;
1626 
1627               /* If the server isn't included properties for added items,
1628                  we'll need to fetch them ourselves. */
1629               if (! ctx->add_props_included)
1630                 file->fetch_props = TRUE;
1631             }
1632         }
1633         break;
1634 
1635       case TXDELTA:
1636         {
1637           file_baton_t *file = ctx->cur_file;
1638           const char *base_checksum;
1639 
1640           /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
1641              addition to <fetch-file>s and such) when *not* in
1642              "send-all" mode.  As a client, we're smart enough to know
1643              that's wrong, so we'll just ignore these tags. */
1644           if (! ctx->send_all_mode)
1645             break;
1646 
1647           file->fetch_file = FALSE;
1648 
1649           attrs = svn_ra_serf__xml_gather_since(xes, entered_state);
1650           base_checksum = svn_hash_gets(attrs, "base-checksum");
1651 
1652           if (base_checksum)
1653             SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
1654                                            svn_checksum_md5, base_checksum,
1655                                            file->pool));
1656 
1657           SVN_ERR(open_file_txdelta(ctx->cur_file, scratch_pool));
1658 
1659           if (ctx->cur_file->txdelta != svn_delta_noop_window_handler)
1660             {
1661               svn_stream_t *decoder;
1662 
1663               decoder = svn_txdelta_parse_svndiff(file->txdelta,
1664                                                   file->txdelta_baton,
1665                                                   TRUE /* error early close*/,
1666                                                   file->pool);
1667 
1668               file->txdelta_stream = svn_base64_decode(decoder, file->pool);
1669             }
1670         }
1671         break;
1672 
1673       case FETCH_PROPS:
1674         {
1675           /* Subversion <= 1.6 servers will return a fetch-props element on
1676              open-file and open-dir when non entry props were changed in
1677              !send-all mode. In turn we fetch the full set of properties
1678              and send all of those as *changes* to the editor. So these
1679              editors have to be aware that they receive-non property changes.
1680              (In case of incomplete directories they have to be aware anyway)
1681 
1682              In r1063337 this behavior was changed in mod_dav_svn to always
1683              send property changes inline in these cases. (See issue #3657)
1684 
1685              Note that before that change the property changes to the last_*
1686              entry props were already inlined via specific xml elements. */
1687           if (ctx->cur_file)
1688             ctx->cur_file->fetch_props = TRUE;
1689           else if (ctx->cur_dir)
1690             ctx->cur_dir->fetch_props = TRUE;
1691         }
1692         break;
1693     }
1694 
1695   return SVN_NO_ERROR;
1696 }
1697 
1698 
1699 
1700 /* Conforms to svn_ra_serf__xml_closed_t  */
1701 static svn_error_t *
update_closed(svn_ra_serf__xml_estate_t * xes,void * baton,int leaving_state,const svn_string_t * cdata,apr_hash_t * attrs,apr_pool_t * scratch_pool)1702 update_closed(svn_ra_serf__xml_estate_t *xes,
1703               void *baton,
1704               int leaving_state,
1705               const svn_string_t *cdata,
1706               apr_hash_t *attrs,
1707               apr_pool_t *scratch_pool)
1708 {
1709   report_context_t *ctx = baton;
1710 
1711   switch (leaving_state)
1712     {
1713       case UPDATE_REPORT:
1714         ctx->done = TRUE;
1715         break;
1716       case TARGET_REVISION:
1717         {
1718           const char *revstr = svn_hash_gets(attrs, "rev");
1719           apr_int64_t rev;
1720 
1721           SVN_ERR(svn_cstring_atoi64(&rev, revstr));
1722 
1723           SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
1724                                                    (svn_revnum_t)rev,
1725                                                    scratch_pool));
1726         }
1727         break;
1728 
1729       case CHECKED_IN_HREF:
1730         if (ctx->cur_file)
1731           ctx->cur_file->url = apr_pstrdup(ctx->cur_file->pool, cdata->data);
1732         else
1733           ctx->cur_dir->url = apr_pstrdup(ctx->cur_dir->pool, cdata->data);
1734         break;
1735 
1736       case SET_PROP:
1737       case REMOVE_PROP:
1738         {
1739           const char *name = svn_hash_gets(attrs, "name");
1740           const char *encoding;
1741           const svn_string_t *value;
1742 
1743           if (leaving_state == REMOVE_PROP)
1744             value = NULL;
1745           else if ((encoding = svn_hash_gets(attrs, "encoding")))
1746             {
1747               if (strcmp(encoding, "base64") != 0)
1748                 return svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING, NULL,
1749                                          _("Got unrecognized encoding '%s'"),
1750                                          encoding);
1751 
1752               value = svn_base64_decode_string(cdata, scratch_pool);
1753             }
1754           else
1755             value = cdata;
1756 
1757           if (ctx->cur_file)
1758             {
1759               file_baton_t *file = ctx->cur_file;
1760 
1761               if (value
1762                   || ctx->add_props_included
1763                   || SVN_IS_VALID_REVNUM(file->base_rev))
1764                 {
1765                   SVN_ERR(ensure_file_opened(file, scratch_pool));
1766 
1767                   SVN_ERR(ctx->editor->change_file_prop(file->file_baton,
1768                                                         name,
1769                                                         value,
1770                                                         scratch_pool));
1771                 }
1772               else
1773                 {
1774                   if (!file->remove_props)
1775                     file->remove_props = apr_hash_make(file->pool);
1776 
1777                   svn_hash_sets(file->remove_props,
1778                                 apr_pstrdup(file->pool, name),
1779                                 "");
1780                 }
1781             }
1782           else
1783             {
1784               dir_baton_t *dir = ctx->cur_dir;
1785 
1786               if (value
1787                   || ctx->add_props_included
1788                   || SVN_IS_VALID_REVNUM(dir->base_rev))
1789                 {
1790                   SVN_ERR(ensure_dir_opened(dir, scratch_pool));
1791 
1792                   SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton,
1793                                                        name,
1794                                                        value,
1795                                                        scratch_pool));
1796                 }
1797               else
1798                 {
1799                   if (!dir->remove_props)
1800                     dir->remove_props = apr_hash_make(dir->pool);
1801 
1802                   svn_hash_sets(dir->remove_props,
1803                                 apr_pstrdup(dir->pool, name),
1804                                 "");
1805                 }
1806             }
1807         }
1808         break;
1809 
1810       case OPEN_DIR:
1811       case ADD_DIR:
1812         {
1813           dir_baton_t *dir = ctx->cur_dir;
1814           ctx->cur_dir = ctx->cur_dir->parent_dir;
1815 
1816           if (dir->fetch_props && ! dir->url)
1817             {
1818               return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1819                                       _("The REPORT response did not "
1820                                         "include the requested checked-in "
1821                                         "value"));
1822             }
1823 
1824           if (!dir->fetch_props)
1825             {
1826               SVN_ERR(maybe_close_dir(dir));
1827               break; /* dir potentially no longer valid */
1828             }
1829           else
1830             {
1831               /* Otherwise, if the server is *not* in "send-all" mode, we
1832                  are at a point where we can queue up the PROPFIND request */
1833               SVN_ERR(fetch_for_dir(dir, scratch_pool));
1834             }
1835         }
1836         break;
1837 
1838       case OPEN_FILE:
1839       case ADD_FILE:
1840         {
1841           file_baton_t *file = ctx->cur_file;
1842 
1843           ctx->cur_file = NULL;
1844           /* go fetch info->name from DAV:checked-in */
1845 
1846           if ((file->fetch_file || file->fetch_props) && ! file->url)
1847             {
1848               return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1849                                       _("The REPORT response did not "
1850                                         "include the requested checked-in "
1851                                         "value"));
1852             }
1853 
1854           /* If the server is in "send-all" mode or didn't get further work,
1855              we can now close the file */
1856           if (! file->fetch_file && ! file->fetch_props)
1857             {
1858               SVN_ERR(close_file(file, scratch_pool));
1859               break; /* file is no longer valid */
1860             }
1861           else
1862             {
1863               /* Otherwise, if the server is *not* in "send-all" mode, we
1864                  should be at a point where we can queue up any auxiliary
1865                  content-fetching requests. */
1866               SVN_ERR(fetch_for_file(file, scratch_pool));
1867             }
1868         }
1869         break;
1870 
1871       case MD5_CHECKSUM:
1872         SVN_ERR(svn_checksum_parse_hex(&ctx->cur_file->final_md5_checksum,
1873                                        svn_checksum_md5,
1874                                        cdata->data,
1875                                        ctx->cur_file->pool));
1876         break;
1877 
1878       case FETCH_FILE:
1879         {
1880           file_baton_t *file = ctx->cur_file;
1881           const char *base_checksum = svn_hash_gets(attrs, "base-checksum");
1882           const char *sha1_checksum = svn_hash_gets(attrs, "sha1-checksum");
1883 
1884           if (base_checksum)
1885             SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum,
1886                                            svn_checksum_md5, base_checksum,
1887                                            file->pool));
1888 
1889           /* Property is duplicated between add-file and fetch-file */
1890           if (sha1_checksum && !file->final_sha1_checksum)
1891             SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum,
1892                                            svn_checksum_sha1,
1893                                            sha1_checksum,
1894                                            file->pool));
1895 
1896           /* Some 0.3x mod_dav_svn wrote both txdelta and fetch-file
1897              elements in send-all mode. (See neon for history) */
1898           if (! ctx->send_all_mode)
1899             file->fetch_file = TRUE;
1900         }
1901         break;
1902 
1903       case DELETE_ENTRY:
1904         {
1905           const char *name = svn_hash_gets(attrs, "name");
1906           const char *revstr;
1907           apr_int64_t delete_rev;
1908 
1909           SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
1910 
1911           revstr = svn_hash_gets(attrs, "rev");
1912 
1913           if (revstr)
1914             SVN_ERR(svn_cstring_atoi64(&delete_rev, revstr));
1915           else
1916             delete_rev = SVN_INVALID_REVNUM;
1917 
1918           SVN_ERR(ctx->editor->delete_entry(
1919                                     svn_relpath_join(ctx->cur_dir->relpath,
1920                                                      name,
1921                                                      scratch_pool),
1922                                     (svn_revnum_t)delete_rev,
1923                                     ctx->cur_dir->dir_baton,
1924                                     scratch_pool));
1925         }
1926         break;
1927 
1928       case ABSENT_DIR:
1929         {
1930           const char *name = svn_hash_gets(attrs, "name");
1931 
1932           SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
1933 
1934           SVN_ERR(ctx->editor->absent_directory(
1935                                     svn_relpath_join(ctx->cur_dir->relpath,
1936                                                      name, scratch_pool),
1937                                     ctx->cur_dir->dir_baton,
1938                                     scratch_pool));
1939         }
1940         break;
1941      case ABSENT_FILE:
1942         {
1943           const char *name = svn_hash_gets(attrs, "name");
1944 
1945           SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
1946 
1947           SVN_ERR(ctx->editor->absent_file(
1948                                     svn_relpath_join(ctx->cur_dir->relpath,
1949                                                      name, scratch_pool),
1950                                     ctx->cur_dir->dir_baton,
1951                                     scratch_pool));
1952         }
1953         break;
1954 
1955       case TXDELTA:
1956         {
1957           file_baton_t *file = ctx->cur_file;
1958 
1959           if (file->txdelta_stream)
1960             {
1961               SVN_ERR(svn_stream_close(file->txdelta_stream));
1962               file->txdelta_stream = NULL;
1963             }
1964         }
1965         break;
1966 
1967       case VERSION_NAME:
1968       case CREATIONDATE:
1969       case CREATOR_DISPLAYNAME:
1970         {
1971           /* Subversion <= 1.6 servers would return a fetch-props element on
1972              open-file and open-dir when non entry props were changed in
1973              !send-all mode. In turn we fetch the full set of properties and
1974              send those as *changes* to the editor. So these editors have to
1975              be aware that they receive non property changes.
1976              (In case of incomplete directories they have to be aware anyway)
1977 
1978              In that case the last_* entry props are posted as 3 specific xml
1979              elements, which we handle here.
1980 
1981              In r1063337 this behavior was changed in mod_dav_svn to always
1982              send property changes inline in these cases. (See issue #3657)
1983            */
1984 
1985           const char *propname;
1986 
1987           if (ctx->cur_file)
1988             SVN_ERR(ensure_file_opened(ctx->cur_file, scratch_pool));
1989           else if (ctx->cur_dir)
1990             SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool));
1991           else
1992             break;
1993 
1994           switch (leaving_state)
1995             {
1996               case VERSION_NAME:
1997                 propname = SVN_PROP_ENTRY_COMMITTED_REV;
1998                 break;
1999               case CREATIONDATE:
2000                 propname = SVN_PROP_ENTRY_COMMITTED_DATE;
2001                 break;
2002               case CREATOR_DISPLAYNAME:
2003                 propname = SVN_PROP_ENTRY_LAST_AUTHOR;
2004                 break;
2005               default:
2006                 SVN_ERR_MALFUNCTION(); /* Impossible to reach */
2007             }
2008 
2009           if (ctx->cur_file)
2010             SVN_ERR(ctx->editor->change_file_prop(ctx->cur_file->file_baton,
2011                                                   propname, cdata,
2012                                                   scratch_pool));
2013           else
2014             SVN_ERR(ctx->editor->change_dir_prop(ctx->cur_dir->dir_baton,
2015                                                   propname, cdata,
2016                                                   scratch_pool));
2017         }
2018         break;
2019     }
2020 
2021   return SVN_NO_ERROR;
2022 }
2023 
2024 
2025 /* Conforms to svn_ra_serf__xml_cdata_t  */
2026 static svn_error_t *
update_cdata(svn_ra_serf__xml_estate_t * xes,void * baton,int current_state,const char * data,apr_size_t len,apr_pool_t * scratch_pool)2027 update_cdata(svn_ra_serf__xml_estate_t *xes,
2028              void *baton,
2029              int current_state,
2030              const char *data,
2031              apr_size_t len,
2032              apr_pool_t *scratch_pool)
2033 {
2034   report_context_t *ctx = baton;
2035 
2036   if (current_state == TXDELTA && ctx->cur_file
2037       && ctx->cur_file->txdelta_stream)
2038     {
2039       SVN_ERR(svn_stream_write(ctx->cur_file->txdelta_stream, data, &len));
2040     }
2041 
2042   return SVN_NO_ERROR;
2043 }
2044 
2045 
2046 /** Editor callbacks given to callers to create request body */
2047 
2048 /* Helper to create simple xml tag without attributes. */
2049 static void
make_simple_xml_tag(svn_stringbuf_t ** buf_p,const char * tagname,const char * cdata,apr_pool_t * pool)2050 make_simple_xml_tag(svn_stringbuf_t **buf_p,
2051                     const char *tagname,
2052                     const char *cdata,
2053                     apr_pool_t *pool)
2054 {
2055   svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname,
2056                         SVN_VA_NULL);
2057   svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
2058   svn_xml_make_close_tag(buf_p, pool, tagname);
2059 }
2060 
2061 static svn_error_t *
set_path(void * report_baton,const char * path,svn_revnum_t revision,svn_depth_t depth,svn_boolean_t start_empty,const char * lock_token,apr_pool_t * pool)2062 set_path(void *report_baton,
2063          const char *path,
2064          svn_revnum_t revision,
2065          svn_depth_t depth,
2066          svn_boolean_t start_empty,
2067          const char *lock_token,
2068          apr_pool_t *pool)
2069 {
2070   report_context_t *report = report_baton;
2071   svn_stringbuf_t *buf = NULL;
2072 
2073   svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2074                         "rev", apr_ltoa(pool, revision),
2075                         "lock-token", lock_token,
2076                         "depth", svn_depth_to_word(depth),
2077                         "start-empty", start_empty ? "true" : NULL,
2078                         SVN_VA_NULL);
2079   svn_xml_escape_cdata_cstring(&buf, path, pool);
2080   svn_xml_make_close_tag(&buf, pool, "S:entry");
2081 
2082   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2083 
2084   return SVN_NO_ERROR;
2085 }
2086 
2087 static svn_error_t *
delete_path(void * report_baton,const char * path,apr_pool_t * pool)2088 delete_path(void *report_baton,
2089             const char *path,
2090             apr_pool_t *pool)
2091 {
2092   report_context_t *report = report_baton;
2093   svn_stringbuf_t *buf = NULL;
2094 
2095   make_simple_xml_tag(&buf, "S:missing", path, pool);
2096 
2097   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2098 
2099   return SVN_NO_ERROR;
2100 }
2101 
2102 static svn_error_t *
link_path(void * report_baton,const char * path,const char * url,svn_revnum_t revision,svn_depth_t depth,svn_boolean_t start_empty,const char * lock_token,apr_pool_t * pool)2103 link_path(void *report_baton,
2104           const char *path,
2105           const char *url,
2106           svn_revnum_t revision,
2107           svn_depth_t depth,
2108           svn_boolean_t start_empty,
2109           const char *lock_token,
2110           apr_pool_t *pool)
2111 {
2112   report_context_t *report = report_baton;
2113   const char *link, *report_target;
2114   apr_uri_t uri;
2115   apr_status_t status;
2116   svn_stringbuf_t *buf = NULL;
2117 
2118   /* We need to pass in the baseline relative path.
2119    *
2120    * TODO Confirm that it's on the same server?
2121    */
2122   status = apr_uri_parse(pool, url, &uri);
2123   if (status)
2124     {
2125       return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2126                                _("Unable to parse URL '%s'"), url);
2127     }
2128 
2129   SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess, pool));
2130   SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, pool));
2131 
2132   link = apr_pstrcat(pool, "/", link, SVN_VA_NULL);
2133 
2134   svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2135                         "rev", apr_ltoa(pool, revision),
2136                         "lock-token", lock_token,
2137                         "depth", svn_depth_to_word(depth),
2138                         "linkpath", link,
2139                         "start-empty", start_empty ? "true" : NULL,
2140                         SVN_VA_NULL);
2141   svn_xml_escape_cdata_cstring(&buf, path, pool);
2142   svn_xml_make_close_tag(&buf, pool, "S:entry");
2143 
2144   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2145 
2146   /* Store the switch roots to allow generating repos_relpaths from just
2147      the working copy paths. (Needed for HTTPv2) */
2148   path = apr_pstrdup(report->pool, path);
2149   link = apr_pstrdup(report->pool, link + 1);
2150   svn_hash_sets(report->switched_paths, path, link);
2151 
2152   if (!path[0] && report->update_target[0])
2153     {
2154       /* The update root is switched. Make sure we store it the way
2155          we expect it to find */
2156       svn_hash_sets(report->switched_paths, report->update_target, link);
2157     }
2158 
2159   return APR_SUCCESS;
2160 }
2161 
2162 /* Serf callback to setup update request headers. */
2163 static svn_error_t *
setup_update_report_headers(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)2164 setup_update_report_headers(serf_bucket_t *headers,
2165                             void *baton,
2166                             apr_pool_t *pool /* request pool */,
2167                             apr_pool_t *scratch_pool)
2168 {
2169   report_context_t *report = baton;
2170 
2171   svn_ra_serf__setup_svndiff_accept_encoding(headers, report->sess);
2172 
2173   return SVN_NO_ERROR;
2174 }
2175 
2176 /* Baton for update_delay_handler */
2177 typedef struct update_delay_baton_t
2178 {
2179   report_context_t *report;
2180   svn_spillbuf_t *spillbuf;
2181   svn_ra_serf__response_handler_t inner_handler;
2182   void *inner_handler_baton;
2183 } update_delay_baton_t;
2184 
2185 /* Helper for update_delay_handler() and process_pending() to
2186    call UDB->INNER_HANDLER with buffer pointed by DATA. */
2187 static svn_error_t *
process_buffer(update_delay_baton_t * udb,serf_request_t * request,const void * data,apr_size_t len,svn_boolean_t at_eof,serf_bucket_alloc_t * alloc,apr_pool_t * pool)2188 process_buffer(update_delay_baton_t *udb,
2189                serf_request_t *request,
2190                const void *data,
2191                apr_size_t len,
2192                svn_boolean_t at_eof,
2193                serf_bucket_alloc_t *alloc,
2194                apr_pool_t *pool)
2195 {
2196   serf_bucket_t *tmp_bucket;
2197   svn_error_t *err;
2198 
2199   /* ### This code (and the eagain bucket code) can probably be
2200       ### simplified by using a bit of aggregate bucket magic.
2201       ### See mail from Ivan to dev@s.a.o. */
2202   if (at_eof)
2203   {
2204       tmp_bucket = serf_bucket_simple_create(data, len, NULL, NULL,
2205                                              alloc);
2206   }
2207   else
2208   {
2209       tmp_bucket = svn_ra_serf__create_bucket_with_eagain(data, len,
2210                                                           alloc);
2211   }
2212 
2213   /* If not at EOF create a bucket that finishes with EAGAIN, otherwise
2214       use a standard bucket with default EOF handling */
2215   err = udb->inner_handler(request, tmp_bucket,
2216                            udb->inner_handler_baton, pool);
2217 
2218   /* And free the bucket explicitly to avoid growing request allocator
2219      storage (in a loop) */
2220   serf_bucket_destroy(tmp_bucket);
2221 
2222   return svn_error_trace(err);
2223 }
2224 
2225 
2226 /* Delaying wrapping reponse handler, to avoid creating too many
2227    requests to deliver efficiently */
2228 static svn_error_t *
update_delay_handler(serf_request_t * request,serf_bucket_t * response,void * handler_baton,apr_pool_t * scratch_pool)2229 update_delay_handler(serf_request_t *request,
2230                      serf_bucket_t *response,
2231                      void *handler_baton,
2232                      apr_pool_t *scratch_pool)
2233 {
2234   update_delay_baton_t *udb = handler_baton;
2235   apr_status_t status;
2236   apr_pool_t *iterpool = NULL;
2237 
2238   if (! udb->spillbuf)
2239     {
2240       if (udb->report->send_all_mode)
2241         {
2242           /* Easy out... We only have one request, so avoid everything and just
2243              call the inner handler.
2244 
2245              We will always get in the loop (below) on the first chunk, as only
2246              the server can get us in true send-all mode */
2247 
2248           return svn_error_trace(udb->inner_handler(request, response,
2249                                                     udb->inner_handler_baton,
2250                                                     scratch_pool));
2251         }
2252 
2253       while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
2254                  < REQUEST_COUNT_TO_RESUME)
2255         {
2256           const char *data;
2257           apr_size_t len;
2258           svn_boolean_t at_eof = FALSE;
2259           svn_error_t *err;
2260 
2261           status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
2262           if (SERF_BUCKET_READ_ERROR(status))
2263             return svn_ra_serf__wrap_err(status, NULL);
2264           else if (APR_STATUS_IS_EOF(status))
2265             udb->report->report_received = at_eof = TRUE;
2266 
2267           if (!iterpool)
2268             iterpool = svn_pool_create(scratch_pool);
2269           else
2270             svn_pool_clear(iterpool);
2271 
2272           if (len == 0 && !at_eof)
2273             return svn_ra_serf__wrap_err(status, NULL);
2274 
2275           err = process_buffer(udb, request, data, len, at_eof,
2276                                serf_request_get_alloc(request),
2277                                iterpool);
2278 
2279           if (err && SERF_BUCKET_READ_ERROR(err->apr_err))
2280             return svn_error_trace(err);
2281           else if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
2282             {
2283               svn_error_clear(err); /* Throttling is working ok */
2284             }
2285           else if (err && (APR_STATUS_IS_EOF(err->apr_err)))
2286             {
2287               svn_pool_destroy(iterpool);
2288               return svn_error_trace(err); /* No buffering was necessary */
2289             }
2290           else
2291             {
2292               /* SERF_ERROR_WAIT_CONN should be impossible? */
2293               return svn_error_trace(err);
2294             }
2295         }
2296 
2297       /* Let's start using the spill infrastructure */
2298       udb->spillbuf = svn_spillbuf__create(SPILLBUF_BLOCKSIZE,
2299                                            SPILLBUF_MAXBUFFSIZE,
2300                                            udb->report->pool);
2301     }
2302 
2303   /* Read everything we can to a spillbuffer */
2304   do
2305     {
2306       const char *data;
2307       apr_size_t len;
2308 
2309       /* ### What blocksize should we pass? */
2310       status = serf_bucket_read(response, 8*PARSE_CHUNK_SIZE, &data, &len);
2311 
2312       if (!SERF_BUCKET_READ_ERROR(status))
2313         SVN_ERR(svn_spillbuf__write(udb->spillbuf, data, len, scratch_pool));
2314     }
2315   while (status == APR_SUCCESS);
2316 
2317   if (APR_STATUS_IS_EOF(status))
2318     udb->report->report_received = TRUE;
2319 
2320   /* We handle feeding the data from the main context loop, which will be right
2321      after processing the pending data */
2322 
2323   if (status)
2324     return svn_ra_serf__wrap_err(status, NULL);
2325   else
2326     return SVN_NO_ERROR;
2327 }
2328 
2329 /* Process pending data from the update report, if any */
2330 static svn_error_t *
process_pending(update_delay_baton_t * udb,apr_pool_t * scratch_pool)2331 process_pending(update_delay_baton_t *udb,
2332                 apr_pool_t *scratch_pool)
2333 {
2334   apr_pool_t *iterpool = NULL;
2335   serf_bucket_alloc_t *alloc = NULL;
2336 
2337   while ((udb->report->num_active_fetches + udb->report->num_active_propfinds)
2338             < REQUEST_COUNT_TO_RESUME)
2339     {
2340       const char *data;
2341       apr_size_t len;
2342       svn_boolean_t at_eof;
2343       svn_error_t *err;
2344 
2345       if (!iterpool)
2346         {
2347           iterpool = svn_pool_create(scratch_pool);
2348           alloc = serf_bucket_allocator_create(scratch_pool, NULL, NULL);
2349         }
2350       else
2351         svn_pool_clear(iterpool);
2352 
2353       SVN_ERR(svn_spillbuf__read(&data, &len, udb->spillbuf, iterpool));
2354 
2355       if (data == NULL && !udb->report->report_received)
2356         break;
2357       else if (data == NULL)
2358         at_eof = TRUE;
2359       else
2360         at_eof = FALSE;
2361 
2362       err = process_buffer(udb, NULL /* allowed? */, data, len,
2363                            at_eof, alloc, iterpool);
2364 
2365       if (err && APR_STATUS_IS_EAGAIN(err->apr_err))
2366         {
2367           svn_error_clear(err); /* Throttling is working */
2368         }
2369       else if (err && APR_STATUS_IS_EOF(err->apr_err))
2370         {
2371           svn_error_clear(err);
2372 
2373           svn_pool_destroy(iterpool);
2374           udb->spillbuf = NULL;
2375           return SVN_NO_ERROR;
2376         }
2377       else if (err)
2378         return svn_error_trace(err);
2379     }
2380 
2381   if (iterpool)
2382     svn_pool_destroy(iterpool);
2383 
2384   return SVN_NO_ERROR;
2385 }
2386 
2387 /* Process the 'update' editor report */
2388 static svn_error_t *
process_editor_report(report_context_t * ctx,svn_ra_serf__handler_t * handler,apr_pool_t * scratch_pool)2389 process_editor_report(report_context_t *ctx,
2390                       svn_ra_serf__handler_t *handler,
2391                       apr_pool_t *scratch_pool)
2392 {
2393   svn_ra_serf__session_t *sess = ctx->sess;
2394   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2395   apr_interval_time_t waittime_left = sess->timeout;
2396   update_delay_baton_t *ud;
2397 
2398   /* Now wrap the response handler with delay support to avoid sending
2399      out too many requests at once */
2400   ud = apr_pcalloc(scratch_pool, sizeof(*ud));
2401   ud->report = ctx;
2402 
2403   ud->inner_handler = handler->response_handler;
2404   ud->inner_handler_baton = handler->response_baton;
2405 
2406   handler->response_handler = update_delay_handler;
2407   handler->response_baton = ud;
2408 
2409   /* Open the first extra connection. */
2410   SVN_ERR(open_connection_if_needed(sess, 0));
2411 
2412   sess->cur_conn = 1;
2413 
2414   /* Note that we may have no active GET or PROPFIND requests, yet the
2415      processing has not been completed. This could be from a delay on the
2416      network or because we've spooled the entire response into our "pending"
2417      content of the XML parser. The DONE flag will get set when all the
2418      XML content has been received *and* parsed.  */
2419   while (!handler->done
2420          || ctx->num_active_fetches
2421          || ctx->num_active_propfinds
2422          || !ctx->done)
2423     {
2424       svn_error_t *err;
2425       int i;
2426 
2427       svn_pool_clear(iterpool);
2428 
2429       err = svn_ra_serf__context_run(sess, &waittime_left, iterpool);
2430 
2431       if (handler->done && handler->server_error)
2432         {
2433           svn_error_clear(err);
2434           err = svn_ra_serf__server_error_create(handler, iterpool);
2435 
2436           SVN_ERR_ASSERT(err != NULL);
2437         }
2438 
2439       SVN_ERR(err);
2440 
2441       /* If there is pending REPORT data, process it now. */
2442       if (ud->spillbuf)
2443         SVN_ERR(process_pending(ud, iterpool));
2444 
2445       /* Debugging purposes only! */
2446       for (i = 0; i < sess->num_conns; i++)
2447         {
2448           serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
2449         }
2450     }
2451 
2452   svn_pool_clear(iterpool);
2453 
2454   /* If we got a complete report, close the edit.  Otherwise, abort it. */
2455   if (ctx->done)
2456     SVN_ERR(ctx->editor->close_edit(ctx->editor_baton, iterpool));
2457   else
2458     return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2459                             _("Missing update-report close tag"));
2460 
2461   svn_pool_destroy(iterpool);
2462   return SVN_NO_ERROR;
2463 }
2464 
2465 static svn_error_t *
finish_report(void * report_baton,apr_pool_t * pool)2466 finish_report(void *report_baton,
2467               apr_pool_t *pool)
2468 {
2469   report_context_t *report = report_baton;
2470   svn_ra_serf__session_t *sess = report->sess;
2471   svn_ra_serf__handler_t *handler;
2472   svn_ra_serf__xml_context_t *xmlctx;
2473   const char *report_target;
2474   svn_stringbuf_t *buf = NULL;
2475   apr_pool_t *scratch_pool = svn_pool_create(pool);
2476   svn_error_t *err;
2477 
2478   svn_xml_make_close_tag(&buf, scratch_pool, "S:update-report");
2479   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2480   SVN_ERR(svn_stream_close(report->body_template));
2481 
2482   SVN_ERR(svn_ra_serf__report_resource(&report_target, sess,  scratch_pool));
2483 
2484   xmlctx = svn_ra_serf__xml_context_create(update_ttable,
2485                                            update_opened, update_closed,
2486                                            update_cdata,
2487                                            report,
2488                                            scratch_pool);
2489   handler = svn_ra_serf__create_expat_handler(sess, xmlctx, NULL,
2490                                               scratch_pool);
2491 
2492   svn_ra_serf__request_body_get_delegate(&handler->body_delegate,
2493                                          &handler->body_delegate_baton,
2494                                          report->body);
2495   handler->method = "REPORT";
2496   handler->path = report_target;
2497   handler->body_type = "text/xml";
2498   handler->custom_accept_encoding = TRUE;
2499   handler->header_delegate = setup_update_report_headers;
2500   handler->header_delegate_baton = report;
2501 
2502   svn_ra_serf__request_create(handler);
2503 
2504   err = process_editor_report(report, handler, scratch_pool);
2505 
2506   if (err)
2507     {
2508       err = svn_error_trace(err);
2509       err = svn_error_compose_create(
2510                 err,
2511                 svn_error_trace(
2512                     report->editor->abort_edit(report->editor_baton,
2513                                                scratch_pool)));
2514     }
2515 
2516   svn_pool_destroy(scratch_pool);
2517 
2518   return svn_error_trace(err);
2519 }
2520 
2521 
2522 static svn_error_t *
abort_report(void * report_baton,apr_pool_t * pool)2523 abort_report(void *report_baton,
2524              apr_pool_t *pool)
2525 {
2526 #if 0
2527   report_context_t *report = report_baton;
2528 #endif
2529 
2530   /* Should we perform some cleanup here? */
2531 
2532   return SVN_NO_ERROR;
2533 }
2534 
2535 static const svn_ra_reporter3_t ra_serf_reporter = {
2536   set_path,
2537   delete_path,
2538   link_path,
2539   finish_report,
2540   abort_report
2541 };
2542 
2543 
2544 /** RA function implementations and body */
2545 
2546 static svn_error_t *
make_update_reporter(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,svn_revnum_t revision,const char * src_path,const char * dest_path,const char * update_target,svn_depth_t depth,svn_boolean_t ignore_ancestry,svn_boolean_t text_deltas,svn_boolean_t send_copyfrom_args,const svn_delta_editor_t * update_editor,void * update_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2547 make_update_reporter(svn_ra_session_t *ra_session,
2548                      const svn_ra_reporter3_t **reporter,
2549                      void **report_baton,
2550                      svn_revnum_t revision,
2551                      const char *src_path,
2552                      const char *dest_path,
2553                      const char *update_target,
2554                      svn_depth_t depth,
2555                      svn_boolean_t ignore_ancestry,
2556                      svn_boolean_t text_deltas,
2557                      svn_boolean_t send_copyfrom_args,
2558                      const svn_delta_editor_t *update_editor,
2559                      void *update_baton,
2560                      apr_pool_t *result_pool,
2561                      apr_pool_t *scratch_pool)
2562 {
2563   report_context_t *report;
2564   const svn_delta_editor_t *filter_editor;
2565   void *filter_baton;
2566   svn_boolean_t has_target = *update_target != '\0';
2567   svn_boolean_t server_supports_depth;
2568   svn_ra_serf__session_t *sess = ra_session->priv;
2569   svn_stringbuf_t *buf = NULL;
2570   svn_boolean_t use_bulk_updates;
2571 
2572   SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
2573                                       SVN_RA_CAPABILITY_DEPTH, scratch_pool));
2574   /* We can skip the depth filtering when the user requested
2575      depth_files or depth_infinity because the server will
2576      transmit the right stuff anyway. */
2577   if ((depth != svn_depth_files)
2578       && (depth != svn_depth_infinity)
2579       && ! server_supports_depth)
2580     {
2581       SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
2582                                             &filter_baton,
2583                                             update_editor,
2584                                             update_baton,
2585                                             depth, has_target,
2586                                             result_pool));
2587       update_editor = filter_editor;
2588       update_baton = filter_baton;
2589     }
2590 
2591   report = apr_pcalloc(result_pool, sizeof(*report));
2592   report->pool = result_pool;
2593   report->sess = sess;
2594   report->target_rev = revision;
2595   report->ignore_ancestry = ignore_ancestry;
2596   report->send_copyfrom_args = send_copyfrom_args;
2597   report->text_deltas = text_deltas;
2598   report->switched_paths = apr_hash_make(report->pool);
2599 
2600   report->source = src_path;
2601   report->destination = dest_path;
2602   report->update_target = update_target;
2603 
2604   report->editor = update_editor;
2605   report->editor_baton = update_baton;
2606   report->done = FALSE;
2607 
2608   *reporter = &ra_serf_reporter;
2609   *report_baton = report;
2610 
2611   report->body =
2612     svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
2613                                      report->pool);
2614   report->body_template = svn_ra_serf__request_body_get_stream(report->body);
2615 
2616   if (sess->bulk_updates == svn_tristate_true)
2617     {
2618       /* User would like to use bulk updates. */
2619       use_bulk_updates = TRUE;
2620     }
2621   else if (sess->bulk_updates == svn_tristate_false)
2622     {
2623       /* User doesn't want bulk updates. */
2624       use_bulk_updates = FALSE;
2625     }
2626   else
2627     {
2628       /* User doesn't have any preferences on bulk updates. Decide on server
2629          preferences and capabilities. */
2630       if (sess->server_allows_bulk)
2631         {
2632           if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
2633             {
2634               /* Server doesn't want bulk updates */
2635               use_bulk_updates = FALSE;
2636             }
2637           else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
2638             {
2639               /* Server prefers bulk updates, and we respect that */
2640               use_bulk_updates = TRUE;
2641             }
2642           else
2643             {
2644               /* Server allows bulk updates, but doesn't dictate its use. Do
2645                  whatever is the default. */
2646               use_bulk_updates = FALSE;
2647             }
2648         }
2649       else
2650         {
2651           /* Pre-1.8 server didn't send the bulk_updates header. Check if server
2652              supports inlining properties in update editor report. */
2653           if (sess->supports_inline_props)
2654             {
2655               /* NOTE: both inlined properties and server->allows_bulk_update
2656                  (flag SVN_DAV_ALLOW_BULK_UPDATES) were added in 1.8.0, so
2657                  this code is never reached with a released version of
2658                  mod_dav_svn.
2659 
2660                  Basically by default a 1.8.0 client connecting to a 1.7.x or
2661                  older server will always use bulk updates. */
2662 
2663               /* Inline props supported: do not use bulk updates. */
2664               use_bulk_updates = FALSE;
2665             }
2666           else
2667             {
2668               /* Inline props are not supported: use bulk updates to avoid
2669                * PROPFINDs for every added node. */
2670               use_bulk_updates = TRUE;
2671             }
2672         }
2673     }
2674 
2675   if (use_bulk_updates)
2676     {
2677       svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
2678                             "S:update-report",
2679                             "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
2680                             SVN_VA_NULL);
2681     }
2682   else
2683     {
2684       svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
2685                             "S:update-report",
2686                             "xmlns:S", SVN_XML_NAMESPACE,
2687                             SVN_VA_NULL);
2688       /* Subversion 1.8+ servers can be told to send properties for newly
2689          added items inline even when doing a skelta response. */
2690       make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
2691     }
2692 
2693   make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
2694 
2695   if (SVN_IS_VALID_REVNUM(report->target_rev))
2696     {
2697       make_simple_xml_tag(&buf, "S:target-revision",
2698                           apr_ltoa(scratch_pool, report->target_rev),
2699                           scratch_pool);
2700     }
2701 
2702   if (report->destination && *report->destination)
2703     {
2704       make_simple_xml_tag(&buf, "S:dst-path", report->destination,
2705                           scratch_pool);
2706     }
2707 
2708   if (report->update_target && *report->update_target)
2709     {
2710       make_simple_xml_tag(&buf, "S:update-target", report->update_target,
2711                           scratch_pool);
2712     }
2713 
2714   if (report->ignore_ancestry)
2715     {
2716       make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
2717     }
2718 
2719   if (report->send_copyfrom_args)
2720     {
2721       make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
2722     }
2723 
2724   /* Old servers know "recursive" but not "depth"; help them DTRT. */
2725   if (depth == svn_depth_files || depth == svn_depth_empty)
2726     {
2727       make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
2728     }
2729 
2730   /* When in 'send-all' mode, mod_dav_svn will assume that it should
2731      calculate and transmit real text-deltas (instead of empty windows
2732      that merely indicate "text is changed") unless it finds this
2733      element.
2734 
2735      NOTE: Do NOT count on servers actually obeying this, as some exist
2736      which obey send-all, but do not check for this directive at all!
2737 
2738      NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
2739      override our request and send text-deltas. */
2740   if (! text_deltas)
2741     {
2742       make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
2743     }
2744 
2745   make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
2746 
2747   SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len));
2748 
2749   return SVN_NO_ERROR;
2750 }
2751 
2752 svn_error_t *
svn_ra_serf__do_update(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,svn_revnum_t revision_to_update_to,const char * update_target,svn_depth_t depth,svn_boolean_t send_copyfrom_args,svn_boolean_t ignore_ancestry,const svn_delta_editor_t * update_editor,void * update_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2753 svn_ra_serf__do_update(svn_ra_session_t *ra_session,
2754                        const svn_ra_reporter3_t **reporter,
2755                        void **report_baton,
2756                        svn_revnum_t revision_to_update_to,
2757                        const char *update_target,
2758                        svn_depth_t depth,
2759                        svn_boolean_t send_copyfrom_args,
2760                        svn_boolean_t ignore_ancestry,
2761                        const svn_delta_editor_t *update_editor,
2762                        void *update_baton,
2763                        apr_pool_t *result_pool,
2764                        apr_pool_t *scratch_pool)
2765 {
2766   svn_ra_serf__session_t *session = ra_session->priv;
2767 
2768   SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
2769                                revision_to_update_to,
2770                                session->session_url.path, NULL, update_target,
2771                                depth, ignore_ancestry, TRUE /* text_deltas */,
2772                                send_copyfrom_args,
2773                                update_editor, update_baton,
2774                                result_pool, scratch_pool));
2775   return SVN_NO_ERROR;
2776 }
2777 
2778 svn_error_t *
svn_ra_serf__do_diff(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,svn_revnum_t revision,const char * diff_target,svn_depth_t depth,svn_boolean_t ignore_ancestry,svn_boolean_t text_deltas,const char * versus_url,const svn_delta_editor_t * diff_editor,void * diff_baton,apr_pool_t * pool)2779 svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
2780                      const svn_ra_reporter3_t **reporter,
2781                      void **report_baton,
2782                      svn_revnum_t revision,
2783                      const char *diff_target,
2784                      svn_depth_t depth,
2785                      svn_boolean_t ignore_ancestry,
2786                      svn_boolean_t text_deltas,
2787                      const char *versus_url,
2788                      const svn_delta_editor_t *diff_editor,
2789                      void *diff_baton,
2790                      apr_pool_t *pool)
2791 {
2792   svn_ra_serf__session_t *session = ra_session->priv;
2793   apr_pool_t *scratch_pool = svn_pool_create(pool);
2794 
2795   SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
2796                                revision,
2797                                session->session_url.path, versus_url, diff_target,
2798                                depth, ignore_ancestry, text_deltas,
2799                                FALSE /* send_copyfrom */,
2800                                diff_editor, diff_baton,
2801                                pool, scratch_pool));
2802   svn_pool_destroy(scratch_pool);
2803   return SVN_NO_ERROR;
2804 }
2805 
2806 svn_error_t *
svn_ra_serf__do_status(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,const char * status_target,svn_revnum_t revision,svn_depth_t depth,const svn_delta_editor_t * status_editor,void * status_baton,apr_pool_t * pool)2807 svn_ra_serf__do_status(svn_ra_session_t *ra_session,
2808                        const svn_ra_reporter3_t **reporter,
2809                        void **report_baton,
2810                        const char *status_target,
2811                        svn_revnum_t revision,
2812                        svn_depth_t depth,
2813                        const svn_delta_editor_t *status_editor,
2814                        void *status_baton,
2815                        apr_pool_t *pool)
2816 {
2817   svn_ra_serf__session_t *session = ra_session->priv;
2818   apr_pool_t *scratch_pool = svn_pool_create(pool);
2819 
2820   SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
2821                                revision,
2822                                session->session_url.path, NULL, status_target,
2823                                depth, FALSE, FALSE, FALSE,
2824                                status_editor, status_baton,
2825                                pool, scratch_pool));
2826   svn_pool_destroy(scratch_pool);
2827   return SVN_NO_ERROR;
2828 }
2829 
2830 svn_error_t *
svn_ra_serf__do_switch(svn_ra_session_t * ra_session,const svn_ra_reporter3_t ** reporter,void ** report_baton,svn_revnum_t revision_to_switch_to,const char * switch_target,svn_depth_t depth,const char * switch_url,svn_boolean_t send_copyfrom_args,svn_boolean_t ignore_ancestry,const svn_delta_editor_t * switch_editor,void * switch_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2831 svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
2832                        const svn_ra_reporter3_t **reporter,
2833                        void **report_baton,
2834                        svn_revnum_t revision_to_switch_to,
2835                        const char *switch_target,
2836                        svn_depth_t depth,
2837                        const char *switch_url,
2838                        svn_boolean_t send_copyfrom_args,
2839                        svn_boolean_t ignore_ancestry,
2840                        const svn_delta_editor_t *switch_editor,
2841                        void *switch_baton,
2842                        apr_pool_t *result_pool,
2843                        apr_pool_t *scratch_pool)
2844 {
2845   svn_ra_serf__session_t *session = ra_session->priv;
2846 
2847   return make_update_reporter(ra_session, reporter, report_baton,
2848                               revision_to_switch_to,
2849                               session->session_url.path,
2850                               switch_url, switch_target,
2851                               depth,
2852                               ignore_ancestry,
2853                               TRUE /* text_deltas */,
2854                               send_copyfrom_args,
2855                               switch_editor, switch_baton,
2856                               result_pool, scratch_pool);
2857 }
2858