update.c revision 269847
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 * This enum represents the current state of our XML parsing for a REPORT.
55 *
56 * A little explanation of how the parsing works.  Every time we see
57 * an open-directory tag, we enter the OPEN_DIR state.  Likewise, for
58 * add-directory, open-file, etc.  When we see the closing variant of the
59 * open-directory tag, we'll 'pop' out of that state.
60 *
61 * Each state has a pool associated with it that can have temporary
62 * allocations that will live as long as the tag is opened.  Once
63 * the tag is 'closed', the pool will be reused.
64 */
65typedef enum report_state_e {
66    NONE = 0,
67    INITIAL = 0,
68    UPDATE_REPORT,
69    TARGET_REVISION,
70    OPEN_DIR,
71    ADD_DIR,
72    ABSENT_DIR,
73    OPEN_FILE,
74    ADD_FILE,
75    ABSENT_FILE,
76    PROP,
77    IGNORE_PROP_NAME,
78    NEED_PROP_NAME,
79    TXDELTA
80} report_state_e;
81
82
83/* While we process the REPORT response, we will queue up GET and PROPFIND
84   requests. For a very large checkout, it is very easy to queue requests
85   faster than they are resolved. Thus, we need to pause the XML processing
86   (which queues more requests) to avoid queueing too many, with their
87   attendant memory costs. When the queue count drops low enough, we will
88   resume XML processing.
89
90   Note that we don't want the count to drop to zero. We have multiple
91   connections that we want to keep busy. These are also heuristic numbers
92   since network and parsing behavior (ie. it doesn't pause immediately)
93   can make the measurements quite imprecise.
94
95   We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and
96   NUM_ACTIVE_PROPFINDS in the report_context_t structure.  */
97#define REQUEST_COUNT_TO_PAUSE 50
98#define REQUEST_COUNT_TO_RESUME 40
99
100
101/* Forward-declare our report context. */
102typedef struct report_context_t report_context_t;
103
104/*
105 * This structure represents the information for a directory.
106 */
107typedef struct report_dir_t
108{
109  /* Our parent directory.
110   *
111   * This value is NULL when we are the root.
112   */
113  struct report_dir_t *parent_dir;
114
115  apr_pool_t *pool;
116
117  /* Pointer back to our original report context. */
118  report_context_t *report_context;
119
120  /* Our name sans any parents. */
121  const char *base_name;
122
123  /* the expanded directory name (including all parent names) */
124  const char *name;
125
126  /* the canonical url for this directory after updating. (received) */
127  const char *url;
128
129  /* The original repos_relpath of this url (from the working copy)
130     or NULL if the repos_relpath can be calculated from the edit root. */
131  const char *repos_relpath;
132
133  /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */
134  svn_revnum_t base_rev;
135
136  /* controlling dir baton - this is only created in ensure_dir_opened() */
137  void *dir_baton;
138  apr_pool_t *dir_baton_pool;
139
140  /* How many references to this directory do we still have open? */
141  apr_size_t ref_count;
142
143  /* Namespace list allocated out of this ->pool. */
144  svn_ra_serf__ns_t *ns_list;
145
146  /* hashtable for all of the properties (shared within a dir) */
147  apr_hash_t *props;
148
149  /* hashtable for all to-be-removed properties (shared within a dir) */
150  apr_hash_t *removed_props;
151
152  /* The propfind request for our current directory */
153  svn_ra_serf__handler_t *propfind_handler;
154
155  /* Has the server told us to fetch the dir props? */
156  svn_boolean_t fetch_props;
157
158  /* Have we closed the directory tag (meaning no more additions)? */
159  svn_boolean_t tag_closed;
160
161  /* The children of this directory  */
162  struct report_dir_t *children;
163
164  /* The next sibling of this directory */
165  struct report_dir_t *sibling;
166} report_dir_t;
167
168/*
169 * This structure represents the information for a file.
170 *
171 * A directory may have a report_info_t associated with it as well.
172 *
173 * This structure is created as we parse the REPORT response and
174 * once the element is completed, we create a report_fetch_t structure
175 * to give to serf to retrieve this file.
176 */
177typedef struct report_info_t
178{
179  apr_pool_t *pool;
180
181  /* The enclosing directory.
182   *
183   * If this structure refers to a directory, the dir it points to will be
184   * itself.
185   */
186  report_dir_t *dir;
187
188  /* Our name sans any directory info. */
189  const char *base_name;
190
191  /* the expanded file name (including all parent directory names) */
192  const char *name;
193
194  /* the canonical url for this file. */
195  const char *url;
196
197  /* lock token, if we had one to start off with. */
198  const char *lock_token;
199
200  /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */
201  svn_revnum_t base_rev;
202
203  /* our delta base, if present (NULL if we're adding the file) */
204  const char *delta_base;
205
206  /* Path of original item if add with history */
207  const char *copyfrom_path;
208
209  /* Revision of original item if add with history */
210  svn_revnum_t copyfrom_rev;
211
212  /* The propfind request for our current file (if present) */
213  svn_ra_serf__handler_t *propfind_handler;
214
215  /* Has the server told us to fetch the file props? */
216  svn_boolean_t fetch_props;
217
218  /* Has the server told us to go fetch - only valid if we had it already */
219  svn_boolean_t fetch_file;
220
221  /* The properties for this file */
222  apr_hash_t *props;
223
224  /* pool passed to update->add_file, etc. */
225  apr_pool_t *editor_pool;
226
227  /* controlling file_baton and textdelta handler */
228  void *file_baton;
229  const char *base_checksum;
230  const char *final_sha1_checksum;
231  svn_txdelta_window_handler_t textdelta;
232  void *textdelta_baton;
233  svn_stream_t *svndiff_decoder;
234  svn_stream_t *base64_decoder;
235
236  /* Checksum for close_file */
237  const char *final_checksum;
238
239  /* Stream containing file contents already cached in the working
240     copy (which may be used to avoid a GET request for the same). */
241  svn_stream_t *cached_contents;
242
243  /* temporary property for this file which is currently being parsed
244   * It will eventually be stored in our parent directory's property hash.
245   */
246  const char *prop_ns;
247  const char *prop_name;
248  svn_stringbuf_t *prop_value;
249  const char *prop_encoding;
250} report_info_t;
251
252/*
253 * This structure represents a single request to GET (fetch) a file with
254 * its associated Serf session/connection.
255 */
256typedef struct report_fetch_t {
257
258  /* The handler representing this particular fetch.  */
259  svn_ra_serf__handler_t *handler;
260
261  /* The session we should use to fetch the file. */
262  svn_ra_serf__session_t *sess;
263
264  /* The connection we should use to fetch file. */
265  svn_ra_serf__connection_t *conn;
266
267  /* Stores the information for the file we want to fetch. */
268  report_info_t *info;
269
270  /* Have we read our response headers yet? */
271  svn_boolean_t read_headers;
272
273  /* This flag is set when our response is aborted before we reach the
274   * end and we decide to requeue this request.
275   */
276  svn_boolean_t aborted_read;
277  apr_off_t aborted_read_size;
278
279  /* This is the amount of data that we have read so far. */
280  apr_off_t read_size;
281
282  /* If we're receiving an svndiff, this will be non-NULL. */
283  svn_stream_t *delta_stream;
284
285  /* If we're writing this file to a stream, this will be non-NULL. */
286  svn_stream_t *target_stream;
287
288  /* Are we done fetching this file? */
289  svn_boolean_t done;
290
291  /* Discard the rest of the content? */
292  svn_boolean_t discard;
293
294  svn_ra_serf__list_t **done_list;
295  svn_ra_serf__list_t done_item;
296
297} report_fetch_t;
298
299/*
300 * The master structure for a REPORT request and response.
301 */
302struct report_context_t {
303  apr_pool_t *pool;
304
305  svn_ra_serf__session_t *sess;
306  svn_ra_serf__connection_t *conn;
307
308  /* Source path and destination path */
309  const char *source;
310  const char *destination;
311
312  /* Our update target. */
313  const char *update_target;
314
315  /* What is the target revision that we want for this REPORT? */
316  svn_revnum_t target_rev;
317
318  /* Have we been asked to ignore ancestry or textdeltas? */
319  svn_boolean_t ignore_ancestry;
320  svn_boolean_t text_deltas;
321
322  /* Do we want the server to send copyfrom args or not? */
323  svn_boolean_t send_copyfrom_args;
324
325  /* Is the server sending everything in one response? */
326  svn_boolean_t send_all_mode;
327
328  /* Is the server including properties inline for newly added
329     files/dirs? */
330  svn_boolean_t add_props_included;
331
332  /* Path -> const char *repos_relpath mapping */
333  apr_hash_t *switched_paths;
334
335  /* Boolean indicating whether "" is switched.
336     (This indicates that the we are updating a single file) */
337  svn_boolean_t root_is_switched;
338
339  /* Our master update editor and baton. */
340  const svn_delta_editor_t *update_editor;
341  void *update_baton;
342
343  /* The file holding request body for the REPORT.
344   *
345   * ### todo: It will be better for performance to store small
346   * request bodies (like 4k) in memory and bigger bodies on disk.
347   */
348  apr_file_t *body_file;
349
350  /* root directory object */
351  report_dir_t *root_dir;
352
353  /* number of pending GET requests */
354  unsigned int num_active_fetches;
355
356  /* completed fetches (contains report_fetch_t) */
357  svn_ra_serf__list_t *done_fetches;
358
359  /* number of pending PROPFIND requests */
360  unsigned int num_active_propfinds;
361
362  /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */
363  svn_ra_serf__list_t *done_propfinds;
364  svn_ra_serf__list_t *done_dir_propfinds;
365
366  /* list of outstanding prop changes (contains report_dir_t) */
367  svn_ra_serf__list_t *active_dir_propfinds;
368
369  /* list of files that only have prop changes (contains report_info_t) */
370  svn_ra_serf__list_t *file_propchanges_only;
371
372  /* The path to the REPORT request */
373  const char *path;
374
375  /* Are we done parsing the REPORT response? */
376  svn_boolean_t done;
377
378  /* Did we receive all data from the network? */
379  svn_boolean_t report_received;
380
381  /* Did we get a complete (non-truncated) report? */
382  svn_boolean_t report_completed;
383
384  /* The XML parser context for the REPORT response.  */
385  svn_ra_serf__xml_parser_t *parser_ctx;
386
387  /* Did we close the root directory? */
388  svn_boolean_t closed_root;
389};
390
391
392#ifdef NOT_USED_YET
393
394#define D_ "DAV:"
395#define S_ SVN_XML_NAMESPACE
396static const svn_ra_serf__xml_transition_t update_ttable[] = {
397  { INITIAL, S_, "update-report", UPDATE_REPORT,
398    FALSE, { NULL }, FALSE },
399
400  { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
401    FALSE, { "rev", NULL }, TRUE },
402
403  { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
404    FALSE, { "rev", NULL }, TRUE },
405
406  { OPEN_DIR, S_, "open-directory", OPEN_DIR,
407    FALSE, { "rev", "name", NULL }, TRUE },
408
409  { OPEN_DIR, S_, "add-directory", ADD_DIR,
410    FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
411
412  { ADD_DIR, S_, "add-directory", ADD_DIR,
413    FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
414
415  { OPEN_DIR, S_, "open-file", OPEN_FILE,
416    FALSE, { "rev", "name", NULL }, TRUE },
417
418  { OPEN_DIR, S_, "add-file", ADD_FILE,
419    FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
420
421  { ADD_DIR, S_, "add-file", ADD_FILE,
422    FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
423
424  { OPEN_DIR, S_, "delete-entry", OPEN_FILE,
425    FALSE, { "?rev", "name", NULL }, TRUE },
426
427  { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
428    FALSE, { "name", NULL }, TRUE },
429
430  { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
431    FALSE, { "name", NULL }, TRUE },
432
433  { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
434    FALSE, { "name", NULL }, TRUE },
435
436  { ADD_DIR, S_, "absent-file", ABSENT_FILE,
437    FALSE, { "name", NULL }, TRUE },
438
439  { 0 }
440};
441
442
443
444/* Conforms to svn_ra_serf__xml_opened_t  */
445static svn_error_t *
446update_opened(svn_ra_serf__xml_estate_t *xes,
447              void *baton,
448              int entered_state,
449              const svn_ra_serf__dav_props_t *tag,
450              apr_pool_t *scratch_pool)
451{
452  report_context_t *ctx = baton;
453
454  return SVN_NO_ERROR;
455}
456
457
458
459/* Conforms to svn_ra_serf__xml_closed_t  */
460static svn_error_t *
461update_closed(svn_ra_serf__xml_estate_t *xes,
462              void *baton,
463              int leaving_state,
464              const svn_string_t *cdata,
465              apr_hash_t *attrs,
466              apr_pool_t *scratch_pool)
467{
468  report_context_t *ctx = baton;
469
470  if (leaving_state == TARGET_REVISION)
471    {
472      const char *rev = svn_hash_gets(attrs, "rev");
473
474      SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
475                                                      SVN_STR_TO_REV(rev),
476                                                      ctx->sess->pool));
477    }
478
479  return SVN_NO_ERROR;
480}
481
482
483/* Conforms to svn_ra_serf__xml_cdata_t  */
484static svn_error_t *
485update_cdata(svn_ra_serf__xml_estate_t *xes,
486             void *baton,
487             int current_state,
488             const char *data,
489             apr_size_t len,
490             apr_pool_t *scratch_pool)
491{
492  report_context_t *ctx = baton;
493
494  return SVN_NO_ERROR;
495}
496
497#endif /* NOT_USED_YET */
498
499
500/* Returns best connection for fetching files/properties. */
501static svn_ra_serf__connection_t *
502get_best_connection(report_context_t *ctx)
503{
504  svn_ra_serf__connection_t *conn;
505  int first_conn = 1;
506
507  /* Skip the first connection if the REPORT response hasn't been completely
508     received yet or if we're being told to limit our connections to
509     2 (because this could be an attempt to ensure that we do all our
510     auxiliary GETs/PROPFINDs on a single connection).
511
512     ### FIXME: This latter requirement (max_connections > 2) is
513     ### really just a hack to work around the fact that some update
514     ### editor implementations (such as svnrdump's dump editor)
515     ### simply can't handle the way ra_serf violates the editor v1
516     ### drive ordering requirements.
517     ###
518     ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
519  */
520  if (ctx->report_received && (ctx->sess->max_connections > 2))
521    first_conn = 0;
522
523  /* Currently, we just cycle connections.  In the future we could
524     store the number of pending requests on each connection, or
525     perform other heuristics, to achieve better connection usage.
526     (As an optimization, if there's only one available auxiliary
527     connection to use, don't bother doing all the cur_conn math --
528     just return that one connection.)  */
529  if (ctx->sess->num_conns - first_conn == 1)
530    {
531      conn = ctx->sess->conns[first_conn];
532    }
533  else
534    {
535      conn = ctx->sess->conns[ctx->sess->cur_conn];
536      ctx->sess->cur_conn++;
537      if (ctx->sess->cur_conn >= ctx->sess->num_conns)
538        ctx->sess->cur_conn = first_conn;
539    }
540  return conn;
541}
542
543
544/** Report state management helper **/
545
546static report_info_t *
547push_state(svn_ra_serf__xml_parser_t *parser,
548           report_context_t *ctx,
549           report_state_e state)
550{
551  report_info_t *info;
552  apr_pool_t *info_parent_pool;
553
554  svn_ra_serf__xml_push_state(parser, state);
555
556  info = parser->state->private;
557
558  /* Our private pool needs to be disjoint from the state pool. */
559  if (!info)
560    {
561      info_parent_pool = ctx->pool;
562    }
563  else
564    {
565      info_parent_pool = info->pool;
566    }
567
568  if (state == OPEN_DIR || state == ADD_DIR)
569    {
570      report_info_t *new_info;
571
572      new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
573      new_info->pool = svn_pool_create(info_parent_pool);
574      new_info->lock_token = NULL;
575      new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
576
577      new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir));
578      new_info->dir->pool = new_info->pool;
579
580      /* Create the root property tree. */
581      new_info->dir->props = apr_hash_make(new_info->pool);
582      new_info->props = new_info->dir->props;
583      new_info->dir->removed_props = apr_hash_make(new_info->pool);
584
585      new_info->dir->report_context = ctx;
586
587      if (info)
588        {
589          info->dir->ref_count++;
590
591          new_info->dir->parent_dir = info->dir;
592
593          /* Point our ns_list at our parents to try to reuse it. */
594          new_info->dir->ns_list = info->dir->ns_list;
595
596          /* Add ourselves to our parent's list */
597          new_info->dir->sibling = info->dir->children;
598          info->dir->children = new_info->dir;
599        }
600      else
601        {
602          /* Allow us to be found later. */
603          ctx->root_dir = new_info->dir;
604        }
605
606      parser->state->private = new_info;
607    }
608  else if (state == OPEN_FILE || state == ADD_FILE)
609    {
610      report_info_t *new_info;
611
612      new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
613      new_info->pool = svn_pool_create(info_parent_pool);
614      new_info->file_baton = NULL;
615      new_info->lock_token = NULL;
616      new_info->fetch_file = FALSE;
617      new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
618
619      /* Point at our parent's directory state. */
620      new_info->dir = info->dir;
621      info->dir->ref_count++;
622
623      new_info->props = apr_hash_make(new_info->pool);
624
625      parser->state->private = new_info;
626    }
627
628  return parser->state->private;
629}
630
631
632/** Wrappers around our various property walkers **/
633
634static svn_error_t *
635set_file_props(void *baton,
636               const char *ns,
637               const char *name,
638               const svn_string_t *val,
639               apr_pool_t *scratch_pool)
640{
641  report_info_t *info = baton;
642  const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
643  const char *prop_name;
644
645  prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
646  if (prop_name != NULL)
647    return svn_error_trace(editor->change_file_prop(info->file_baton,
648                                                    prop_name,
649                                                    val,
650                                                    scratch_pool));
651  return SVN_NO_ERROR;
652}
653
654
655static svn_error_t *
656set_dir_props(void *baton,
657              const char *ns,
658              const char *name,
659              const svn_string_t *val,
660              apr_pool_t *scratch_pool)
661{
662  report_dir_t *dir = baton;
663  const svn_delta_editor_t *editor = dir->report_context->update_editor;
664  const char *prop_name;
665
666  prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
667  if (prop_name != NULL)
668    return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
669                                                   prop_name,
670                                                   val,
671                                                   scratch_pool));
672  return SVN_NO_ERROR;
673}
674
675
676static svn_error_t *
677remove_file_props(void *baton,
678                  const char *ns,
679                  const char *name,
680                  const svn_string_t *val,
681                  apr_pool_t *scratch_pool)
682{
683  report_info_t *info = baton;
684  const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
685  const char *prop_name;
686
687  prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
688  if (prop_name != NULL)
689    return svn_error_trace(editor->change_file_prop(info->file_baton,
690                                                    prop_name,
691                                                    NULL,
692                                                    scratch_pool));
693  return SVN_NO_ERROR;
694}
695
696
697static svn_error_t *
698remove_dir_props(void *baton,
699                 const char *ns,
700                 const char *name,
701                 const svn_string_t *val,
702                 apr_pool_t *scratch_pool)
703{
704  report_dir_t *dir = baton;
705  const svn_delta_editor_t *editor = dir->report_context->update_editor;
706  const char *prop_name;
707
708  prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
709  if (prop_name != NULL)
710    return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
711                                                   prop_name,
712                                                   NULL,
713                                                   scratch_pool));
714  return SVN_NO_ERROR;
715}
716
717
718/** Helpers to open and close directories */
719
720static svn_error_t*
721ensure_dir_opened(report_dir_t *dir)
722{
723  report_context_t *ctx = dir->report_context;
724
725  /* if we're already open, return now */
726  if (dir->dir_baton)
727    {
728      return SVN_NO_ERROR;
729    }
730
731  if (dir->base_name[0] == '\0')
732    {
733      dir->dir_baton_pool = svn_pool_create(dir->pool);
734
735      if (ctx->destination
736          && ctx->sess->wc_callbacks->invalidate_wc_props)
737        {
738          SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
739                      ctx->sess->wc_callback_baton,
740                      ctx->update_target,
741                      SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
742        }
743
744      SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev,
745                                            dir->dir_baton_pool,
746                                            &dir->dir_baton));
747    }
748  else
749    {
750      SVN_ERR(ensure_dir_opened(dir->parent_dir));
751
752      dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool);
753
754      if (SVN_IS_VALID_REVNUM(dir->base_rev))
755        {
756          SVN_ERR(ctx->update_editor->open_directory(dir->name,
757                                                     dir->parent_dir->dir_baton,
758                                                     dir->base_rev,
759                                                     dir->dir_baton_pool,
760                                                     &dir->dir_baton));
761        }
762      else
763        {
764          SVN_ERR(ctx->update_editor->add_directory(dir->name,
765                                                    dir->parent_dir->dir_baton,
766                                                    NULL, SVN_INVALID_REVNUM,
767                                                    dir->dir_baton_pool,
768                                                    &dir->dir_baton));
769        }
770    }
771
772  return SVN_NO_ERROR;
773}
774
775static svn_error_t *
776close_dir(report_dir_t *dir)
777{
778  report_dir_t *prev;
779  report_dir_t *sibling;
780
781  /* ### is there a better pool... this is tossed at end-of-func  */
782  apr_pool_t *scratch_pool = dir->dir_baton_pool;
783
784  SVN_ERR_ASSERT(! dir->ref_count);
785
786  SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name,
787                                      dir->base_rev,
788                                      set_dir_props, dir,
789                                      scratch_pool));
790
791  SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name,
792                                      dir->base_rev, remove_dir_props, dir,
793                                      scratch_pool));
794
795  if (dir->fetch_props)
796    {
797      SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url,
798                                          dir->report_context->target_rev,
799                                          set_dir_props, dir,
800                                          scratch_pool));
801    }
802
803  SVN_ERR(dir->report_context->update_editor->close_directory(
804            dir->dir_baton, scratch_pool));
805
806  /* remove us from our parent's children list */
807  if (dir->parent_dir)
808    {
809      prev = NULL;
810      sibling = dir->parent_dir->children;
811
812      while (sibling != dir)
813        {
814          prev = sibling;
815          sibling = sibling->sibling;
816          if (!sibling)
817            SVN_ERR_MALFUNCTION();
818        }
819
820      if (!prev)
821        {
822          dir->parent_dir->children = dir->sibling;
823        }
824      else
825        {
826          prev->sibling = dir->sibling;
827        }
828    }
829
830  svn_pool_destroy(dir->dir_baton_pool);
831  svn_pool_destroy(dir->pool);
832
833  return SVN_NO_ERROR;
834}
835
836static svn_error_t *close_all_dirs(report_dir_t *dir)
837{
838  while (dir->children)
839    {
840      SVN_ERR(close_all_dirs(dir->children));
841      dir->ref_count--;
842    }
843
844  SVN_ERR_ASSERT(! dir->ref_count);
845
846  SVN_ERR(ensure_dir_opened(dir));
847
848  return close_dir(dir);
849}
850
851
852/** Routines called when we are fetching a file */
853
854/* This function works around a bug in some older versions of
855 * mod_dav_svn in that it will not send remove-prop in the update
856 * report when a lock property disappears when send-all is false.
857 *
858 * Therefore, we'll try to look at our properties and see if there's
859 * an active lock.  If not, then we'll assume there isn't a lock
860 * anymore.
861 */
862static void
863check_lock(report_info_t *info)
864{
865  const char *lock_val;
866
867  lock_val = svn_ra_serf__get_ver_prop(info->props, info->url,
868                                       info->dir->report_context->target_rev,
869                                       "DAV:", "lockdiscovery");
870
871  if (lock_val)
872    {
873      char *new_lock;
874      new_lock = apr_pstrdup(info->editor_pool, lock_val);
875      apr_collapse_spaces(new_lock, new_lock);
876      lock_val = new_lock;
877    }
878
879  if (!lock_val || lock_val[0] == '\0')
880    {
881      svn_string_t *str;
882
883      str = svn_string_ncreate("", 1, info->editor_pool);
884
885      svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name,
886                                info->base_rev, "DAV:", "lock-token",
887                                str, info->dir->pool);
888    }
889}
890
891static svn_error_t *
892headers_fetch(serf_bucket_t *headers,
893              void *baton,
894              apr_pool_t *pool)
895{
896  report_fetch_t *fetch_ctx = baton;
897
898  /* note that we have old VC URL */
899  if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) &&
900      fetch_ctx->info->delta_base)
901    {
902      serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
903                               fetch_ctx->info->delta_base);
904      serf_bucket_headers_setn(headers, "Accept-Encoding",
905                               "svndiff1;q=0.9,svndiff;q=0.8");
906    }
907  else if (fetch_ctx->sess->using_compression)
908    {
909      serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
910    }
911
912  return SVN_NO_ERROR;
913}
914
915static svn_error_t *
916cancel_fetch(serf_request_t *request,
917             serf_bucket_t *response,
918             int status_code,
919             void *baton)
920{
921  report_fetch_t *fetch_ctx = baton;
922
923  /* Uh-oh.  Our connection died on us.
924   *
925   * The core ra_serf layer will requeue our request - we just need to note
926   * that we got cut off in the middle of our song.
927   */
928  if (!response)
929    {
930      /* If we already started the fetch and opened the file handle, we need
931       * to hold subsequent read() ops until we get back to where we were
932       * before the close and we can then resume the textdelta() calls.
933       */
934      if (fetch_ctx->read_headers)
935        {
936          if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
937            {
938              fetch_ctx->aborted_read = TRUE;
939              fetch_ctx->aborted_read_size = fetch_ctx->read_size;
940            }
941          fetch_ctx->read_size = 0;
942        }
943
944      return SVN_NO_ERROR;
945    }
946
947  /* We have no idea what went wrong. */
948  SVN_ERR_MALFUNCTION();
949}
950
951static svn_error_t *
952error_fetch(serf_request_t *request,
953            report_fetch_t *fetch_ctx,
954            svn_error_t *err)
955{
956  fetch_ctx->done = TRUE;
957
958  fetch_ctx->done_item.data = fetch_ctx;
959  fetch_ctx->done_item.next = *fetch_ctx->done_list;
960  *fetch_ctx->done_list = &fetch_ctx->done_item;
961
962  /* Discard the rest of this request
963     (This makes sure it doesn't error when the request is aborted later) */
964  serf_request_set_handler(request,
965                           svn_ra_serf__response_discard_handler, NULL);
966
967  /* Some errors would be handled by serf; make sure they really make
968     the update fail by wrapping it in a different error. */
969  if (!SERF_BUCKET_READ_ERROR(err->apr_err))
970    return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
971
972  return err;
973}
974
975/* Wield the editor referenced by INFO to open (or add) the file
976   file also associated with INFO, setting properties on the file and
977   calling the editor's apply_textdelta() function on it if necessary
978   (or if FORCE_APPLY_TEXTDELTA is set).
979
980   Callers will probably want to also see the function that serves
981   the opposite purpose of this one, close_updated_file().  */
982static svn_error_t *
983open_updated_file(report_info_t *info,
984                  svn_boolean_t force_apply_textdelta,
985                  apr_pool_t *scratch_pool)
986{
987  report_context_t *ctx = info->dir->report_context;
988  const svn_delta_editor_t *update_editor = ctx->update_editor;
989
990  /* Ensure our parent is open. */
991  SVN_ERR(ensure_dir_opened(info->dir));
992  info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
993
994  /* Expand our full name now if we haven't done so yet. */
995  if (!info->name)
996    {
997      info->name = svn_relpath_join(info->dir->name, info->base_name,
998                                    info->editor_pool);
999    }
1000
1001  /* Open (or add) the file. */
1002  if (SVN_IS_VALID_REVNUM(info->base_rev))
1003    {
1004      SVN_ERR(update_editor->open_file(info->name,
1005                                       info->dir->dir_baton,
1006                                       info->base_rev,
1007                                       info->editor_pool,
1008                                       &info->file_baton));
1009    }
1010  else
1011    {
1012      SVN_ERR(update_editor->add_file(info->name,
1013                                      info->dir->dir_baton,
1014                                      info->copyfrom_path,
1015                                      info->copyfrom_rev,
1016                                      info->editor_pool,
1017                                      &info->file_baton));
1018    }
1019
1020  /* Check for lock information. */
1021  if (info->lock_token)
1022    check_lock(info);
1023
1024  /* Get (maybe) a textdelta window handler for transmitting file
1025     content changes. */
1026  if (info->fetch_file || force_apply_textdelta)
1027    {
1028      SVN_ERR(update_editor->apply_textdelta(info->file_baton,
1029                                             info->base_checksum,
1030                                             info->editor_pool,
1031                                             &info->textdelta,
1032                                             &info->textdelta_baton));
1033    }
1034
1035  return SVN_NO_ERROR;
1036}
1037
1038/* Close the file associated with INFO->file_baton, and cleanup other
1039   bits of that structure managed by open_updated_file(). */
1040static svn_error_t *
1041close_updated_file(report_info_t *info,
1042                   apr_pool_t *scratch_pool)
1043{
1044  report_context_t *ctx = info->dir->report_context;
1045
1046  /* Set all of the properties we received */
1047  SVN_ERR(svn_ra_serf__walk_all_props(info->props,
1048                                      info->base_name,
1049                                      info->base_rev,
1050                                      set_file_props, info,
1051                                      scratch_pool));
1052  SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
1053                                      info->base_name,
1054                                      info->base_rev,
1055                                      remove_file_props, info,
1056                                      scratch_pool));
1057  if (info->fetch_props)
1058    {
1059      SVN_ERR(svn_ra_serf__walk_all_props(info->props,
1060                                          info->url,
1061                                          ctx->target_rev,
1062                                          set_file_props, info,
1063                                          scratch_pool));
1064    }
1065
1066  /* Close the file via the editor. */
1067  SVN_ERR(info->dir->report_context->update_editor->close_file(
1068            info->file_baton, info->final_checksum, scratch_pool));
1069
1070  /* We're done with our editor pool. */
1071  svn_pool_destroy(info->editor_pool);
1072
1073  return SVN_NO_ERROR;
1074}
1075
1076/* Implements svn_ra_serf__response_handler_t */
1077static svn_error_t *
1078handle_fetch(serf_request_t *request,
1079             serf_bucket_t *response,
1080             void *handler_baton,
1081             apr_pool_t *pool)
1082{
1083  const char *data;
1084  apr_size_t len;
1085  apr_status_t status;
1086  report_fetch_t *fetch_ctx = handler_baton;
1087  svn_error_t *err;
1088
1089  /* ### new field. make sure we didn't miss some initialization.  */
1090  SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1091
1092  if (!fetch_ctx->read_headers)
1093    {
1094      serf_bucket_t *hdrs;
1095      const char *val;
1096      report_info_t *info;
1097
1098      hdrs = serf_bucket_response_get_headers(response);
1099      val = serf_bucket_headers_get(hdrs, "Content-Type");
1100      info = fetch_ctx->info;
1101
1102      if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
1103        {
1104          fetch_ctx->delta_stream =
1105              svn_txdelta_parse_svndiff(info->textdelta,
1106                                        info->textdelta_baton,
1107                                        TRUE, info->editor_pool);
1108
1109          /* Validate the delta base claimed by the server matches
1110             what we asked for! */
1111          val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
1112          if (val && (strcmp(val, info->delta_base) != 0))
1113            {
1114              err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1115                                      _("GET request returned unexpected "
1116                                        "delta base: %s"), val);
1117              return error_fetch(request, fetch_ctx, err);
1118            }
1119        }
1120      else
1121        {
1122          fetch_ctx->delta_stream = NULL;
1123        }
1124
1125      fetch_ctx->read_headers = TRUE;
1126    }
1127
1128  /* If the error code wasn't 200, something went wrong. Don't use the returned
1129     data as its probably an error message. Just bail out instead. */
1130  if (fetch_ctx->handler->sline.code != 200)
1131    {
1132      err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1133                              _("GET request failed: %d %s"),
1134                              fetch_ctx->handler->sline.code,
1135                              fetch_ctx->handler->sline.reason);
1136      return error_fetch(request, fetch_ctx, err);
1137    }
1138
1139  while (1)
1140    {
1141      svn_txdelta_window_t delta_window = { 0 };
1142      svn_txdelta_op_t delta_op;
1143      svn_string_t window_data;
1144
1145      status = serf_bucket_read(response, 8000, &data, &len);
1146      if (SERF_BUCKET_READ_ERROR(status))
1147        {
1148          return svn_ra_serf__wrap_err(status, NULL);
1149        }
1150
1151      fetch_ctx->read_size += len;
1152
1153      if (fetch_ctx->aborted_read)
1154        {
1155          apr_off_t skip;
1156          /* We haven't caught up to where we were before. */
1157          if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1158            {
1159              /* Eek.  What did the file shrink or something? */
1160              if (APR_STATUS_IS_EOF(status))
1161                {
1162                  SVN_ERR_MALFUNCTION();
1163                }
1164
1165              /* Skip on to the next iteration of this loop. */
1166              if (APR_STATUS_IS_EAGAIN(status))
1167                {
1168                  return svn_ra_serf__wrap_err(status, NULL);
1169                }
1170              continue;
1171            }
1172
1173          /* Woo-hoo.  We're back. */
1174          fetch_ctx->aborted_read = FALSE;
1175
1176          /* Update data and len to just provide the new data. */
1177          skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
1178          data += skip;
1179          len -= skip;
1180        }
1181
1182      if (fetch_ctx->delta_stream)
1183        {
1184          err = svn_stream_write(fetch_ctx->delta_stream, data, &len);
1185          if (err)
1186            {
1187              return error_fetch(request, fetch_ctx, err);
1188            }
1189        }
1190      /* otherwise, manually construct the text delta window. */
1191      else if (len)
1192        {
1193          window_data.data = data;
1194          window_data.len = len;
1195
1196          delta_op.action_code = svn_txdelta_new;
1197          delta_op.offset = 0;
1198          delta_op.length = len;
1199
1200          delta_window.tview_len = len;
1201          delta_window.num_ops = 1;
1202          delta_window.ops = &delta_op;
1203          delta_window.new_data = &window_data;
1204
1205          /* write to the file located in the info. */
1206          err = fetch_ctx->info->textdelta(&delta_window,
1207                                           fetch_ctx->info->textdelta_baton);
1208          if (err)
1209            {
1210              return error_fetch(request, fetch_ctx, err);
1211            }
1212        }
1213
1214      if (APR_STATUS_IS_EOF(status))
1215        {
1216          report_info_t *info = fetch_ctx->info;
1217
1218          if (fetch_ctx->delta_stream)
1219            err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream));
1220          else
1221            err = svn_error_trace(info->textdelta(NULL,
1222                                                  info->textdelta_baton));
1223          if (err)
1224            {
1225              return error_fetch(request, fetch_ctx, err);
1226            }
1227
1228          err = close_updated_file(info, info->pool);
1229          if (err)
1230            {
1231              return svn_error_trace(error_fetch(request, fetch_ctx, err));
1232            }
1233
1234          fetch_ctx->done = TRUE;
1235
1236          fetch_ctx->done_item.data = fetch_ctx;
1237          fetch_ctx->done_item.next = *fetch_ctx->done_list;
1238          *fetch_ctx->done_list = &fetch_ctx->done_item;
1239
1240          /* We're done with our pool. */
1241          svn_pool_destroy(info->pool);
1242
1243          if (status)
1244            return svn_ra_serf__wrap_err(status, NULL);
1245        }
1246      if (APR_STATUS_IS_EAGAIN(status))
1247        {
1248          return svn_ra_serf__wrap_err(status, NULL);
1249        }
1250    }
1251  /* not reached */
1252}
1253
1254/* Implements svn_ra_serf__response_handler_t */
1255static svn_error_t *
1256handle_stream(serf_request_t *request,
1257              serf_bucket_t *response,
1258              void *handler_baton,
1259              apr_pool_t *pool)
1260{
1261  report_fetch_t *fetch_ctx = handler_baton;
1262  svn_error_t *err;
1263  apr_status_t status;
1264
1265  /* ### new field. make sure we didn't miss some initialization.  */
1266  SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
1267
1268  err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline,
1269                                     fetch_ctx->info->name,
1270                                     fetch_ctx->handler->location);
1271  if (err)
1272    {
1273      fetch_ctx->handler->done = TRUE;
1274
1275      err = svn_error_compose_create(
1276                  err,
1277                  svn_ra_serf__handle_discard_body(request, response, NULL, pool));
1278
1279      return svn_error_trace(err);
1280    }
1281
1282  while (1)
1283    {
1284      const char *data;
1285      apr_size_t len;
1286
1287      status = serf_bucket_read(response, 8000, &data, &len);
1288      if (SERF_BUCKET_READ_ERROR(status))
1289        {
1290          return svn_ra_serf__wrap_err(status, NULL);
1291        }
1292
1293      fetch_ctx->read_size += len;
1294
1295      if (fetch_ctx->aborted_read)
1296        {
1297          /* We haven't caught up to where we were before. */
1298          if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
1299            {
1300              /* Eek.  What did the file shrink or something? */
1301              if (APR_STATUS_IS_EOF(status))
1302                {
1303                  SVN_ERR_MALFUNCTION();
1304                }
1305
1306              /* Skip on to the next iteration of this loop. */
1307              if (APR_STATUS_IS_EAGAIN(status))
1308                {
1309                  return svn_ra_serf__wrap_err(status, NULL);
1310                }
1311              continue;
1312            }
1313
1314          /* Woo-hoo.  We're back. */
1315          fetch_ctx->aborted_read = FALSE;
1316
1317          /* Increment data and len by the difference. */
1318          data += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1319          len += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
1320        }
1321
1322      if (len)
1323        {
1324          apr_size_t written_len;
1325
1326          written_len = len;
1327
1328          SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data,
1329                                   &written_len));
1330        }
1331
1332      if (APR_STATUS_IS_EOF(status))
1333        {
1334          fetch_ctx->done = TRUE;
1335        }
1336
1337      if (status)
1338        {
1339          return svn_ra_serf__wrap_err(status, NULL);
1340        }
1341    }
1342  /* not reached */
1343}
1344
1345/* Close the directory represented by DIR -- and any suitable parents
1346   thereof -- if we are able to do so.  This is the case whenever:
1347
1348     - there are no remaining open items within the directory, and
1349     - the directory's XML close tag has been processed (so we know
1350       there are no more children to worry about in the future), and
1351     - either:
1352         - we aren't fetching properties for this directory, or
1353         - we've already finished fetching those properties.
1354*/
1355static svn_error_t *
1356maybe_close_dir_chain(report_dir_t *dir)
1357{
1358  report_dir_t *cur_dir = dir;
1359
1360  SVN_ERR(ensure_dir_opened(cur_dir));
1361
1362  while (cur_dir
1363         && !cur_dir->ref_count
1364         && cur_dir->tag_closed
1365         && (!cur_dir->fetch_props || cur_dir->propfind_handler->done))
1366    {
1367      report_dir_t *parent = cur_dir->parent_dir;
1368      report_context_t *report_context = cur_dir->report_context;
1369      svn_boolean_t propfind_in_done_list = FALSE;
1370      svn_ra_serf__list_t *done_list;
1371
1372      /* Make sure there are no references to this dir in the
1373         active_dir_propfinds list.  If there are, don't close the
1374         directory -- which would delete the pool from which the
1375         relevant active_dir_propfinds list item is allocated -- and
1376         of course don't crawl upward to check the parents for
1377         a closure opportunity, either.  */
1378      done_list = report_context->active_dir_propfinds;
1379      while (done_list)
1380        {
1381          if (done_list->data == cur_dir)
1382            {
1383              propfind_in_done_list = TRUE;
1384              break;
1385            }
1386          done_list = done_list->next;
1387        }
1388      if (propfind_in_done_list)
1389        break;
1390
1391      SVN_ERR(close_dir(cur_dir));
1392      if (parent)
1393        {
1394          parent->ref_count--;
1395        }
1396      else
1397        {
1398          report_context->closed_root = TRUE;
1399        }
1400      cur_dir = parent;
1401    }
1402
1403  return SVN_NO_ERROR;
1404}
1405
1406/* Open the file associated with INFO for editing, pass along any
1407   propchanges we've recorded for it, and then close the file. */
1408static svn_error_t *
1409handle_propchange_only(report_info_t *info,
1410                       apr_pool_t *scratch_pool)
1411{
1412  SVN_ERR(open_updated_file(info, FALSE, scratch_pool));
1413  SVN_ERR(close_updated_file(info, scratch_pool));
1414
1415  /* We're done with our pool. */
1416  svn_pool_destroy(info->pool);
1417
1418  info->dir->ref_count--;
1419
1420  /* See if the parent directory of this file (and perhaps even
1421     parents of that) can be closed now.  */
1422  SVN_ERR(maybe_close_dir_chain(info->dir));
1423
1424  return SVN_NO_ERROR;
1425}
1426
1427/* "Fetch" a file whose contents were made available via the
1428   get_wc_contents() callback (as opposed to requiring a GET to the
1429   server), and feed the information through the associated update
1430   editor.  In editor-speak, this will add/open the file, transmit any
1431   property changes, handle the contents, and then close the file.  */
1432static svn_error_t *
1433handle_local_content(report_info_t *info,
1434                     apr_pool_t *scratch_pool)
1435{
1436  SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta,
1437                                  info->textdelta_baton, NULL, scratch_pool));
1438  SVN_ERR(svn_stream_close(info->cached_contents));
1439  info->cached_contents = NULL;
1440  SVN_ERR(close_updated_file(info, scratch_pool));
1441
1442  /* We're done with our pool. */
1443  svn_pool_destroy(info->pool);
1444
1445  info->dir->ref_count--;
1446
1447  /* See if the parent directory of this fetched item (and
1448     perhaps even parents of that) can be closed now. */
1449  SVN_ERR(maybe_close_dir_chain(info->dir));
1450
1451  return SVN_NO_ERROR;
1452}
1453
1454/* --------------------------------------------------------- */
1455
1456static svn_error_t *
1457fetch_file(report_context_t *ctx, report_info_t *info)
1458{
1459  svn_ra_serf__connection_t *conn;
1460  svn_ra_serf__handler_t *handler;
1461
1462  /* What connection should we go on? */
1463  conn = get_best_connection(ctx);
1464
1465  /* If needed, create the PROPFIND to retrieve the file's properties. */
1466  info->propfind_handler = NULL;
1467  if (info->fetch_props)
1468    {
1469      SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props,
1470                                         ctx->sess, conn, info->url,
1471                                         ctx->target_rev, "0", all_props,
1472                                         &ctx->done_propfinds,
1473                                         info->dir->pool));
1474      SVN_ERR_ASSERT(info->propfind_handler);
1475
1476      /* Create a serf request for the PROPFIND.  */
1477      svn_ra_serf__request_create(info->propfind_handler);
1478
1479      ctx->num_active_propfinds++;
1480    }
1481
1482  /* If we've been asked to fetch the file or it's an add, do so.
1483   * Otherwise, handle the case where only the properties changed.
1484   */
1485  if (info->fetch_file && ctx->text_deltas)
1486    {
1487      svn_stream_t *contents = NULL;
1488
1489      /* Open the file for editing. */
1490      SVN_ERR(open_updated_file(info, FALSE, info->pool));
1491
1492      if (info->textdelta == svn_delta_noop_window_handler)
1493        {
1494          /* There is nobody looking for an actual stream.
1495
1496             Just report an empty stream instead of fetching
1497             to be ingored data */
1498          info->cached_contents = svn_stream_empty(info->pool);
1499        }
1500      else if (ctx->sess->wc_callbacks->get_wc_contents
1501               && info->final_sha1_checksum)
1502        {
1503          svn_error_t *err = NULL;
1504          svn_checksum_t *checksum = NULL;
1505
1506          /* Parse the optional SHA1 checksum (1.7+) */
1507          err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
1508                                       info->final_sha1_checksum,
1509                                       info->pool);
1510
1511          /* Okay so far?  Let's try to get a stream on some readily
1512             available matching content. */
1513          if (!err && checksum)
1514            {
1515              err = ctx->sess->wc_callbacks->get_wc_contents(
1516                        ctx->sess->wc_callback_baton, &contents,
1517                        checksum, info->pool);
1518
1519              if (! err)
1520                info->cached_contents = contents;
1521            }
1522
1523          if (err)
1524            {
1525              /* Meh.  Maybe we'll care one day why we're in an
1526                 errorful state, but this codepath is optional.  */
1527              svn_error_clear(err);
1528            }
1529        }
1530
1531      /* If the working copy can provide cached contents for this
1532         file, we don't have to fetch them from the server. */
1533      if (info->cached_contents)
1534        {
1535          /* If we'll be doing a PROPFIND for this file... */
1536          if (info->propfind_handler)
1537            {
1538              /* ... then we'll just leave ourselves a little "todo"
1539                 about that fact (and we'll deal with the file content
1540                 stuff later, after we've handled that PROPFIND
1541                 response. */
1542              svn_ra_serf__list_t *list_item;
1543
1544              list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
1545              list_item->data = info;
1546              list_item->next = ctx->file_propchanges_only;
1547              ctx->file_propchanges_only = list_item;
1548            }
1549          else
1550            {
1551              /* Otherwise, if we've no PROPFIND to do, we might as
1552                 well take care of those locally accessible file
1553                 contents now. */
1554              SVN_ERR(handle_local_content(info, info->pool));
1555            }
1556        }
1557      else
1558        {
1559          /* Otherwise, we use a GET request for the file's contents. */
1560          report_fetch_t *fetch_ctx;
1561
1562          fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
1563          fetch_ctx->info = info;
1564          fetch_ctx->done_list = &ctx->done_fetches;
1565          fetch_ctx->sess = ctx->sess;
1566          fetch_ctx->conn = conn;
1567
1568          handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
1569
1570          handler->handler_pool = info->dir->pool;
1571          handler->method = "GET";
1572          handler->path = fetch_ctx->info->url;
1573
1574          handler->conn = conn;
1575          handler->session = ctx->sess;
1576
1577          handler->custom_accept_encoding = TRUE;
1578          handler->header_delegate = headers_fetch;
1579          handler->header_delegate_baton = fetch_ctx;
1580
1581          handler->response_handler = handle_fetch;
1582          handler->response_baton = fetch_ctx;
1583
1584          handler->response_error = cancel_fetch;
1585          handler->response_error_baton = fetch_ctx;
1586
1587          fetch_ctx->handler = handler;
1588
1589          svn_ra_serf__request_create(handler);
1590
1591          ctx->num_active_fetches++;
1592        }
1593    }
1594  else if (info->propfind_handler)
1595    {
1596      svn_ra_serf__list_t *list_item;
1597
1598      list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
1599      list_item->data = info;
1600      list_item->next = ctx->file_propchanges_only;
1601      ctx->file_propchanges_only = list_item;
1602    }
1603  else
1604    {
1605      /* No propfind or GET request.  Just handle the prop changes now. */
1606      SVN_ERR(handle_propchange_only(info, info->pool));
1607    }
1608
1609  if (ctx->num_active_fetches + ctx->num_active_propfinds
1610      > REQUEST_COUNT_TO_PAUSE)
1611    ctx->parser_ctx->paused = TRUE;
1612
1613  return SVN_NO_ERROR;
1614}
1615
1616
1617/** XML callbacks for our update-report response parsing */
1618
1619static svn_error_t *
1620start_report(svn_ra_serf__xml_parser_t *parser,
1621             svn_ra_serf__dav_props_t name,
1622             const char **attrs,
1623             apr_pool_t *scratch_pool)
1624{
1625  report_context_t *ctx = parser->user_data;
1626  report_state_e state;
1627
1628  state = parser->state->current_state;
1629
1630  if (state == NONE && strcmp(name.name, "update-report") == 0)
1631    {
1632      const char *val;
1633
1634      val = svn_xml_get_attr_value("inline-props", attrs);
1635      if (val && (strcmp(val, "true") == 0))
1636        ctx->add_props_included = TRUE;
1637
1638      val = svn_xml_get_attr_value("send-all", attrs);
1639      if (val && (strcmp(val, "true") == 0))
1640        {
1641          ctx->send_all_mode = TRUE;
1642
1643          /* All properties are included in send-all mode. */
1644          ctx->add_props_included = TRUE;
1645        }
1646    }
1647  else if (state == NONE && strcmp(name.name, "target-revision") == 0)
1648    {
1649      const char *rev;
1650
1651      rev = svn_xml_get_attr_value("rev", attrs);
1652
1653      if (!rev)
1654        {
1655          return svn_error_create(
1656            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1657            _("Missing revision attr in target-revision element"));
1658        }
1659
1660      SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
1661                                                      SVN_STR_TO_REV(rev),
1662                                                      ctx->sess->pool));
1663    }
1664  else if (state == NONE && strcmp(name.name, "open-directory") == 0)
1665    {
1666      const char *rev;
1667      report_info_t *info;
1668
1669      rev = svn_xml_get_attr_value("rev", attrs);
1670
1671      if (!rev)
1672        {
1673          return svn_error_create(
1674            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1675            _("Missing revision attr in open-directory element"));
1676        }
1677
1678      info = push_state(parser, ctx, OPEN_DIR);
1679
1680      info->base_rev = SVN_STR_TO_REV(rev);
1681      info->dir->base_rev = info->base_rev;
1682      info->fetch_props = TRUE;
1683
1684      info->dir->base_name = "";
1685      info->dir->name = "";
1686
1687      info->base_name = info->dir->base_name;
1688      info->name = info->dir->name;
1689
1690      info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, "");
1691
1692      if (!info->dir->repos_relpath)
1693        SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath,
1694                                               ctx->sess->session_url.path,
1695                                               ctx->sess, ctx->conn,
1696                                               info->dir->pool));
1697    }
1698  else if (state == NONE)
1699    {
1700      /* do nothing as we haven't seen our valid start tag yet. */
1701    }
1702  else if ((state == OPEN_DIR || state == ADD_DIR) &&
1703           strcmp(name.name, "open-directory") == 0)
1704    {
1705      const char *rev, *dirname;
1706      report_dir_t *dir;
1707      report_info_t *info;
1708
1709      rev = svn_xml_get_attr_value("rev", attrs);
1710
1711      if (!rev)
1712        {
1713          return svn_error_create(
1714            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1715            _("Missing revision attr in open-directory element"));
1716        }
1717
1718      dirname = svn_xml_get_attr_value("name", attrs);
1719
1720      if (!dirname)
1721        {
1722          return svn_error_create(
1723            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1724            _("Missing name attr in open-directory element"));
1725        }
1726
1727      info = push_state(parser, ctx, OPEN_DIR);
1728
1729      dir = info->dir;
1730
1731      info->base_rev = SVN_STR_TO_REV(rev);
1732      dir->base_rev = info->base_rev;
1733
1734      info->fetch_props = FALSE;
1735
1736      dir->base_name = apr_pstrdup(dir->pool, dirname);
1737      info->base_name = dir->base_name;
1738
1739      /* Expand our name. */
1740      dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
1741                                   dir->pool);
1742      info->name = dir->name;
1743
1744      dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name);
1745
1746      if (!dir->repos_relpath)
1747        dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
1748                                               dir->base_name, dir->pool);
1749    }
1750  else if ((state == OPEN_DIR || state == ADD_DIR) &&
1751           strcmp(name.name, "add-directory") == 0)
1752    {
1753      const char *dir_name, *cf, *cr;
1754      report_dir_t *dir;
1755      report_info_t *info;
1756
1757      dir_name = svn_xml_get_attr_value("name", attrs);
1758      if (!dir_name)
1759        {
1760          return svn_error_create(
1761            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1762            _("Missing name attr in add-directory element"));
1763        }
1764      cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1765      cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1766
1767      info = push_state(parser, ctx, ADD_DIR);
1768
1769      dir = info->dir;
1770
1771      dir->base_name = apr_pstrdup(dir->pool, dir_name);
1772      info->base_name = dir->base_name;
1773
1774      /* Expand our name. */
1775      dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
1776                                   dir->pool);
1777      info->name = dir->name;
1778
1779      info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1780      info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
1781
1782      /* Mark that we don't have a base. */
1783      info->base_rev = SVN_INVALID_REVNUM;
1784      dir->base_rev = info->base_rev;
1785
1786      /* If the server isn't included properties for added items,
1787         we'll need to fetch them ourselves. */
1788      if (! ctx->add_props_included)
1789        dir->fetch_props = TRUE;
1790
1791      dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
1792                                            dir->base_name, dir->pool);
1793    }
1794  else if ((state == OPEN_DIR || state == ADD_DIR) &&
1795           strcmp(name.name, "open-file") == 0)
1796    {
1797      const char *file_name, *rev;
1798      report_info_t *info;
1799
1800      file_name = svn_xml_get_attr_value("name", attrs);
1801
1802      if (!file_name)
1803        {
1804          return svn_error_create(
1805            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1806            _("Missing name attr in open-file element"));
1807        }
1808
1809      rev = svn_xml_get_attr_value("rev", attrs);
1810
1811      if (!rev)
1812        {
1813          return svn_error_create(
1814            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1815            _("Missing revision attr in open-file element"));
1816        }
1817
1818      info = push_state(parser, ctx, OPEN_FILE);
1819
1820      info->base_rev = SVN_STR_TO_REV(rev);
1821      info->fetch_props = FALSE;
1822
1823      info->base_name = apr_pstrdup(info->pool, file_name);
1824      info->name = NULL;
1825    }
1826  else if ((state == OPEN_DIR || state == ADD_DIR) &&
1827           strcmp(name.name, "add-file") == 0)
1828    {
1829      const char *file_name, *cf, *cr;
1830      report_info_t *info;
1831
1832      file_name = svn_xml_get_attr_value("name", attrs);
1833      cf = svn_xml_get_attr_value("copyfrom-path", attrs);
1834      cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
1835
1836      if (!file_name)
1837        {
1838          return svn_error_create(
1839            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1840            _("Missing name attr in add-file element"));
1841        }
1842
1843      info = push_state(parser, ctx, ADD_FILE);
1844
1845      info->base_rev = SVN_INVALID_REVNUM;
1846
1847      /* If the server isn't in "send-all" mode, we should expect to
1848         fetch contents for added files. */
1849      if (! ctx->send_all_mode)
1850        info->fetch_file = TRUE;
1851
1852      /* If the server isn't included properties for added items,
1853         we'll need to fetch them ourselves. */
1854      if (! ctx->add_props_included)
1855        info->fetch_props = TRUE;
1856
1857      info->base_name = apr_pstrdup(info->pool, file_name);
1858      info->name = NULL;
1859
1860      info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
1861      info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
1862
1863      info->final_sha1_checksum =
1864        svn_xml_get_attr_value("sha1-checksum", attrs);
1865      if (info->final_sha1_checksum)
1866        info->final_sha1_checksum = apr_pstrdup(info->pool,
1867                                                info->final_sha1_checksum);
1868    }
1869  else if ((state == OPEN_DIR || state == ADD_DIR) &&
1870           strcmp(name.name, "delete-entry") == 0)
1871    {
1872      const char *file_name;
1873      const char *rev_str;
1874      report_info_t *info;
1875      apr_pool_t *tmppool;
1876      const char *full_path;
1877      svn_revnum_t delete_rev = SVN_INVALID_REVNUM;
1878
1879      file_name = svn_xml_get_attr_value("name", attrs);
1880
1881      if (!file_name)
1882        {
1883          return svn_error_create(
1884            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1885            _("Missing name attr in delete-entry element"));
1886        }
1887
1888      rev_str = svn_xml_get_attr_value("rev", attrs);
1889      if (rev_str) /* Not available on older repositories! */
1890        delete_rev = SVN_STR_TO_REV(rev_str);
1891
1892      info = parser->state->private;
1893
1894      SVN_ERR(ensure_dir_opened(info->dir));
1895
1896      tmppool = svn_pool_create(info->dir->dir_baton_pool);
1897
1898      full_path = svn_relpath_join(info->dir->name, file_name, tmppool);
1899
1900      SVN_ERR(ctx->update_editor->delete_entry(full_path,
1901                                               delete_rev,
1902                                               info->dir->dir_baton,
1903                                               tmppool));
1904
1905      svn_pool_destroy(tmppool);
1906    }
1907  else if ((state == OPEN_DIR || state == ADD_DIR) &&
1908           strcmp(name.name, "absent-directory") == 0)
1909    {
1910      const char *file_name;
1911      report_info_t *info;
1912
1913      file_name = svn_xml_get_attr_value("name", attrs);
1914
1915      if (!file_name)
1916        {
1917          return svn_error_create(
1918            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1919            _("Missing name attr in absent-directory element"));
1920        }
1921
1922      info = parser->state->private;
1923
1924      SVN_ERR(ensure_dir_opened(info->dir));
1925
1926      SVN_ERR(ctx->update_editor->absent_directory(
1927                                        svn_relpath_join(info->name, file_name,
1928                                                         info->dir->pool),
1929                                        info->dir->dir_baton,
1930                                        info->dir->pool));
1931    }
1932  else if ((state == OPEN_DIR || state == ADD_DIR) &&
1933           strcmp(name.name, "absent-file") == 0)
1934    {
1935      const char *file_name;
1936      report_info_t *info;
1937
1938      file_name = svn_xml_get_attr_value("name", attrs);
1939
1940      if (!file_name)
1941        {
1942          return svn_error_create(
1943            SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1944            _("Missing name attr in absent-file element"));
1945        }
1946
1947      info = parser->state->private;
1948
1949      SVN_ERR(ensure_dir_opened(info->dir));
1950
1951      SVN_ERR(ctx->update_editor->absent_file(
1952                                        svn_relpath_join(info->name, file_name,
1953                                                         info->dir->pool),
1954                                        info->dir->dir_baton,
1955                                        info->dir->pool));
1956    }
1957  else if (state == OPEN_DIR || state == ADD_DIR)
1958    {
1959      report_info_t *info;
1960
1961      if (strcmp(name.name, "checked-in") == 0)
1962        {
1963          info = push_state(parser, ctx, IGNORE_PROP_NAME);
1964          info->prop_ns = name.namespace;
1965          info->prop_name = apr_pstrdup(parser->state->pool, name.name);
1966          info->prop_encoding = NULL;
1967          svn_stringbuf_setempty(info->prop_value);
1968        }
1969      else if (strcmp(name.name, "set-prop") == 0 ||
1970               strcmp(name.name, "remove-prop") == 0)
1971        {
1972          const char *full_prop_name;
1973          const char *colon;
1974
1975          info = push_state(parser, ctx, PROP);
1976
1977          full_prop_name = svn_xml_get_attr_value("name", attrs);
1978          if (!full_prop_name)
1979            {
1980              return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1981                                       _("Missing name attr in %s element"),
1982                                       name.name);
1983            }
1984
1985          colon = strchr(full_prop_name, ':');
1986
1987          if (colon)
1988            colon++;
1989          else
1990            colon = full_prop_name;
1991
1992          info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
1993                                         colon - full_prop_name);
1994          info->prop_name = apr_pstrdup(parser->state->pool, colon);
1995          info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
1996          svn_stringbuf_setempty(info->prop_value);
1997        }
1998      else if (strcmp(name.name, "prop") == 0)
1999        {
2000          /* need to fetch it. */
2001          push_state(parser, ctx, NEED_PROP_NAME);
2002        }
2003      else if (strcmp(name.name, "fetch-props") == 0)
2004        {
2005          info = parser->state->private;
2006
2007          info->dir->fetch_props = TRUE;
2008        }
2009      else
2010        {
2011          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2012                                   _("Unknown tag '%s' while at state %d"),
2013                                   name.name, state);
2014        }
2015
2016    }
2017  else if (state == OPEN_FILE || state == ADD_FILE)
2018    {
2019      report_info_t *info;
2020
2021      if (strcmp(name.name, "checked-in") == 0)
2022        {
2023          info = push_state(parser, ctx, IGNORE_PROP_NAME);
2024          info->prop_ns = name.namespace;
2025          info->prop_name = apr_pstrdup(parser->state->pool, name.name);
2026          info->prop_encoding = NULL;
2027          svn_stringbuf_setempty(info->prop_value);
2028        }
2029      else if (strcmp(name.name, "prop") == 0)
2030        {
2031          /* need to fetch it. */
2032          push_state(parser, ctx, NEED_PROP_NAME);
2033        }
2034      else if (strcmp(name.name, "fetch-props") == 0)
2035        {
2036          info = parser->state->private;
2037
2038          info->fetch_props = TRUE;
2039        }
2040      else if (strcmp(name.name, "fetch-file") == 0)
2041        {
2042          info = parser->state->private;
2043          info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs);
2044
2045          if (info->base_checksum)
2046            info->base_checksum = apr_pstrdup(info->pool, info->base_checksum);
2047
2048          info->final_sha1_checksum =
2049            svn_xml_get_attr_value("sha1-checksum", attrs);
2050          if (info->final_sha1_checksum)
2051            info->final_sha1_checksum = apr_pstrdup(info->pool,
2052                                                    info->final_sha1_checksum);
2053
2054          info->fetch_file = TRUE;
2055        }
2056      else if (strcmp(name.name, "set-prop") == 0 ||
2057               strcmp(name.name, "remove-prop") == 0)
2058        {
2059          const char *full_prop_name;
2060          const char *colon;
2061
2062          info = push_state(parser, ctx, PROP);
2063
2064          full_prop_name = svn_xml_get_attr_value("name", attrs);
2065          if (!full_prop_name)
2066            {
2067              return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2068                                       _("Missing name attr in %s element"),
2069                                       name.name);
2070            }
2071          colon = strchr(full_prop_name, ':');
2072
2073          if (colon)
2074            colon++;
2075          else
2076            colon = full_prop_name;
2077
2078          info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
2079                                         colon - full_prop_name);
2080          info->prop_name = apr_pstrdup(parser->state->pool, colon);
2081          info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2082          svn_stringbuf_setempty(info->prop_value);
2083        }
2084      else if (strcmp(name.name, "txdelta") == 0)
2085        {
2086          /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
2087             addition to <fetch-file>s and such) when *not* in
2088             "send-all" mode.  As a client, we're smart enough to know
2089             that's wrong, so we'll just ignore these tags. */
2090          if (ctx->send_all_mode)
2091            {
2092              const svn_delta_editor_t *update_editor = ctx->update_editor;
2093
2094              info = push_state(parser, ctx, TXDELTA);
2095
2096              if (! info->file_baton)
2097                {
2098                  SVN_ERR(open_updated_file(info, FALSE, info->pool));
2099                }
2100
2101              info->base_checksum = svn_xml_get_attr_value("base-checksum",
2102                                                           attrs);
2103              SVN_ERR(update_editor->apply_textdelta(info->file_baton,
2104                                                     info->base_checksum,
2105                                                     info->editor_pool,
2106                                                     &info->textdelta,
2107                                                     &info->textdelta_baton));
2108              info->svndiff_decoder = svn_txdelta_parse_svndiff(
2109                                          info->textdelta,
2110                                          info->textdelta_baton,
2111                                          TRUE, info->pool);
2112              info->base64_decoder = svn_base64_decode(info->svndiff_decoder,
2113                                                       info->pool);
2114            }
2115        }
2116      else
2117        {
2118          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2119                                   _("Unknown tag '%s' while at state %d"),
2120                                   name.name, state);
2121        }
2122    }
2123  else if (state == IGNORE_PROP_NAME)
2124    {
2125      report_info_t *info = push_state(parser, ctx, PROP);
2126      info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2127    }
2128  else if (state == NEED_PROP_NAME)
2129    {
2130      report_info_t *info;
2131
2132      info = push_state(parser, ctx, PROP);
2133
2134      info->prop_ns = name.namespace;
2135      info->prop_name = apr_pstrdup(parser->state->pool, name.name);
2136      info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
2137      svn_stringbuf_setempty(info->prop_value);
2138    }
2139
2140  return SVN_NO_ERROR;
2141}
2142
2143static svn_error_t *
2144end_report(svn_ra_serf__xml_parser_t *parser,
2145           svn_ra_serf__dav_props_t name,
2146           apr_pool_t *scratch_pool)
2147{
2148  report_context_t *ctx = parser->user_data;
2149  report_state_e state;
2150
2151  state = parser->state->current_state;
2152
2153  if (state == NONE)
2154    {
2155      if (strcmp(name.name, "update-report") == 0)
2156        {
2157          ctx->report_completed = TRUE;
2158        }
2159      else
2160        {
2161          /* nothing to close yet. */
2162          return SVN_NO_ERROR;
2163        }
2164    }
2165
2166  if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
2167       (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0))))
2168    {
2169      const char *checked_in_url;
2170      report_info_t *info = parser->state->private;
2171
2172      /* We've now closed this directory; note it. */
2173      info->dir->tag_closed = TRUE;
2174
2175      /* go fetch info->file_name from DAV:checked-in */
2176      checked_in_url =
2177          svn_ra_serf__get_ver_prop(info->dir->props, info->base_name,
2178                                    info->base_rev, "DAV:", "checked-in");
2179
2180      /* If we were expecting to have the properties and we aren't able to
2181       * get it, bail.
2182       */
2183      if (!checked_in_url &&
2184          (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
2185        {
2186          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2187                                  _("The REPORT or PROPFIND response did not "
2188                                    "include the requested checked-in value"));
2189        }
2190
2191      info->dir->url = checked_in_url;
2192
2193      /* At this point, we should have the checked-in href.
2194       * If needed, create the PROPFIND to retrieve the dir's properties.
2195       */
2196      if (info->dir->fetch_props)
2197        {
2198          svn_ra_serf__list_t *list_item;
2199
2200          SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler,
2201                                             info->dir->props, ctx->sess,
2202                                             get_best_connection(ctx),
2203                                             info->dir->url,
2204                                             ctx->target_rev, "0",
2205                                             all_props,
2206                                             &ctx->done_dir_propfinds,
2207                                             info->dir->pool));
2208          SVN_ERR_ASSERT(info->dir->propfind_handler);
2209
2210          /* Create a serf request for the PROPFIND.  */
2211          svn_ra_serf__request_create(info->dir->propfind_handler);
2212
2213          ctx->num_active_propfinds++;
2214
2215          list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
2216          list_item->data = info->dir;
2217          list_item->next = ctx->active_dir_propfinds;
2218          ctx->active_dir_propfinds = list_item;
2219
2220          if (ctx->num_active_fetches + ctx->num_active_propfinds
2221              > REQUEST_COUNT_TO_PAUSE)
2222            ctx->parser_ctx->paused = TRUE;
2223        }
2224      else
2225        {
2226          info->dir->propfind_handler = NULL;
2227        }
2228
2229      /* See if this directory (and perhaps even parents of that) can
2230         be closed now.  This is likely to be the case only if we
2231         didn't need to contact the server for supplemental
2232         information required to handle any of this directory's
2233         children.  */
2234      SVN_ERR(maybe_close_dir_chain(info->dir));
2235      svn_ra_serf__xml_pop_state(parser);
2236    }
2237  else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
2238    {
2239      report_info_t *info = parser->state->private;
2240
2241      /* Expand our full name now if we haven't done so yet. */
2242      if (!info->name)
2243        {
2244          info->name = svn_relpath_join(info->dir->name, info->base_name,
2245                                        info->pool);
2246        }
2247
2248      if (info->lock_token && !info->fetch_props)
2249        info->fetch_props = TRUE;
2250
2251      /* If possible, we'd like to fetch only a delta against a
2252       * version of the file we already have in our working copy,
2253       * rather than fetching a fulltext.
2254       *
2255       * In HTTP v2, we can simply construct the URL we need given the
2256       * repos_relpath and base revision number.
2257       */
2258      if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
2259        {
2260          const char *repos_relpath;
2261
2262          /* If this file is switched vs the editor root we should provide
2263             its real url instead of the one calculated from the session root.
2264           */
2265          repos_relpath = svn_hash_gets(ctx->switched_paths, info->name);
2266
2267          if (!repos_relpath)
2268            {
2269              if (ctx->root_is_switched)
2270                {
2271                  /* We are updating a direct target (most likely a file)
2272                     that is switched vs its parent url */
2273                  SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool)
2274                                    == '\0');
2275
2276                  repos_relpath = svn_hash_gets(ctx->switched_paths, "");
2277                }
2278              else
2279                repos_relpath = svn_relpath_join(info->dir->repos_relpath,
2280                                                 info->base_name, info->pool);
2281            }
2282
2283          info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s",
2284                                          ctx->sess->rev_root_stub,
2285                                          info->base_rev,
2286                                          svn_path_uri_encode(repos_relpath,
2287                                                              info->pool));
2288        }
2289      else if (ctx->sess->wc_callbacks->get_wc_prop)
2290        {
2291          /* If we have a WC, we might be able to dive all the way into the WC
2292           * to get the previous URL so we can do a differential GET with the
2293           * base URL.
2294           */
2295          const svn_string_t *value = NULL;
2296          SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
2297            ctx->sess->wc_callback_baton, info->name,
2298            SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool));
2299
2300          info->delta_base = value ? value->data : NULL;
2301        }
2302
2303      /* go fetch info->name from DAV:checked-in */
2304      info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
2305                                            info->base_rev, "DAV:", "checked-in");
2306      if (!info->url)
2307        {
2308          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2309                                  _("The REPORT or PROPFIND response did not "
2310                                    "include the requested checked-in value"));
2311        }
2312
2313      /* If the server is in "send-all" mode, we might have opened the
2314         file when we started seeing content for it.  If we didn't get
2315         any content for it, we still need to open the file.  But in
2316         any case, we can then immediately close it.  */
2317      if (ctx->send_all_mode)
2318        {
2319          if (! info->file_baton)
2320            {
2321              SVN_ERR(open_updated_file(info, FALSE, info->pool));
2322            }
2323          SVN_ERR(close_updated_file(info, info->pool));
2324          info->dir->ref_count--;
2325        }
2326      /* Otherwise, if the server is *not* in "send-all" mode, we
2327         should be at a point where we can queue up any auxiliary
2328         content-fetching requests.  */
2329      else
2330        {
2331          SVN_ERR(fetch_file(ctx, info));
2332        }
2333
2334      svn_ra_serf__xml_pop_state(parser);
2335    }
2336  else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
2337    {
2338      report_info_t *info = parser->state->private;
2339
2340      /* go fetch info->name from DAV:checked-in */
2341      info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
2342                                            info->base_rev, "DAV:", "checked-in");
2343      if (!info->url)
2344        {
2345          return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2346                                  _("The REPORT or PROPFIND response did not "
2347                                    "include the requested checked-in value"));
2348        }
2349
2350      /* If the server is in "send-all" mode, we might have opened the
2351         file when we started seeing content for it.  If we didn't get
2352         any content for it, we still need to open the file.  But in
2353         any case, we can then immediately close it.  */
2354      if (ctx->send_all_mode)
2355        {
2356          if (! info->file_baton)
2357            {
2358              SVN_ERR(open_updated_file(info, FALSE, info->pool));
2359            }
2360          SVN_ERR(close_updated_file(info, info->pool));
2361          info->dir->ref_count--;
2362        }
2363      /* Otherwise, if the server is *not* in "send-all" mode, we
2364         should be at a point where we can queue up any auxiliary
2365         content-fetching requests.  */
2366      else
2367        {
2368          SVN_ERR(fetch_file(ctx, info));
2369        }
2370
2371      svn_ra_serf__xml_pop_state(parser);
2372    }
2373  else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0)
2374    {
2375      report_info_t *info = parser->state->private;
2376
2377      /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2378         <fetch-file>s and such) when *not* in "send-all" mode.  As a
2379         client, we're smart enough to know that's wrong, so when not
2380         in "receiving-all" mode, we'll ignore these tags. */
2381      if (ctx->send_all_mode)
2382        {
2383          SVN_ERR(svn_stream_close(info->base64_decoder));
2384        }
2385
2386      svn_ra_serf__xml_pop_state(parser);
2387    }
2388  else if (state == PROP)
2389    {
2390      /* We need to move the prop_ns, prop_name, and prop_value into the
2391       * same lifetime as the dir->pool.
2392       */
2393      svn_ra_serf__ns_t *ns, *ns_name_match;
2394      svn_boolean_t found = FALSE;
2395      report_info_t *info;
2396      report_dir_t *dir;
2397      apr_hash_t *props;
2398      const svn_string_t *set_val_str;
2399      apr_pool_t *pool;
2400
2401      info = parser->state->private;
2402      dir = info->dir;
2403
2404      /* We're going to be slightly tricky.  We don't care what the ->url
2405       * field is here at this point.  So, we're going to stick a single
2406       * copy of the property name inside of the ->url field.
2407       */
2408      ns_name_match = NULL;
2409      for (ns = dir->ns_list; ns; ns = ns->next)
2410        {
2411          if (strcmp(ns->namespace, info->prop_ns) == 0)
2412            {
2413              ns_name_match = ns;
2414              if (strcmp(ns->url, info->prop_name) == 0)
2415                {
2416                  found = TRUE;
2417                  break;
2418                }
2419            }
2420        }
2421
2422      if (!found)
2423        {
2424          ns = apr_palloc(dir->pool, sizeof(*ns));
2425          if (!ns_name_match)
2426            {
2427              ns->namespace = apr_pstrdup(dir->pool, info->prop_ns);
2428            }
2429          else
2430            {
2431              ns->namespace = ns_name_match->namespace;
2432            }
2433          ns->url = apr_pstrdup(dir->pool, info->prop_name);
2434
2435          ns->next = dir->ns_list;
2436          dir->ns_list = ns;
2437        }
2438
2439      if (strcmp(name.name, "remove-prop") != 0)
2440        {
2441          props = info->props;
2442          pool = info->pool;
2443        }
2444      else
2445        {
2446          props = dir->removed_props;
2447          pool = dir->pool;
2448          svn_stringbuf_setempty(info->prop_value);
2449        }
2450
2451      if (info->prop_encoding)
2452        {
2453          if (strcmp(info->prop_encoding, "base64") == 0)
2454            {
2455              svn_string_t tmp;
2456
2457              /* Don't use morph_info_string cuz we need prop_value to
2458                 remain usable.  */
2459              tmp.data = info->prop_value->data;
2460              tmp.len = info->prop_value->len;
2461
2462              set_val_str = svn_base64_decode_string(&tmp, pool);
2463            }
2464          else
2465            {
2466              return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
2467                                       NULL,
2468                                       _("Got unrecognized encoding '%s'"),
2469                                       info->prop_encoding);
2470            }
2471        }
2472      else
2473        {
2474          set_val_str = svn_string_create_from_buf(info->prop_value, pool);
2475        }
2476
2477      svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
2478                                ns->namespace, ns->url, set_val_str, pool);
2479
2480      /* Advance handling:  if we spotted the md5-checksum property on
2481         the wire, remember it's value. */
2482      if (strcmp(ns->url, "md5-checksum") == 0
2483          && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0)
2484        info->final_checksum = apr_pstrdup(info->pool, set_val_str->data);
2485
2486      svn_ra_serf__xml_pop_state(parser);
2487    }
2488  else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
2489    {
2490      svn_ra_serf__xml_pop_state(parser);
2491    }
2492
2493  return SVN_NO_ERROR;
2494}
2495
2496static svn_error_t *
2497cdata_report(svn_ra_serf__xml_parser_t *parser,
2498             const char *data,
2499             apr_size_t len,
2500             apr_pool_t *scratch_pool)
2501{
2502  report_context_t *ctx = parser->user_data;
2503
2504  UNUSED_CTX(ctx);
2505
2506  if (parser->state->current_state == PROP)
2507    {
2508      report_info_t *info = parser->state->private;
2509
2510      svn_stringbuf_appendbytes(info->prop_value, data, len);
2511    }
2512  else if (parser->state->current_state == TXDELTA)
2513    {
2514      /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2515         <fetch-file>s and such) when *not* in "send-all" mode.  As a
2516         client, we're smart enough to know that's wrong, so when not
2517         in "receiving-all" mode, we'll ignore these tags. */
2518      if (ctx->send_all_mode)
2519        {
2520          apr_size_t nlen = len;
2521          report_info_t *info = parser->state->private;
2522
2523          SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen));
2524          if (nlen != len)
2525            {
2526              /* Short write without associated error?  "Can't happen." */
2527              return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
2528                                       _("Error writing to '%s': unexpected EOF"),
2529                                       info->name);
2530            }
2531        }
2532    }
2533
2534  return SVN_NO_ERROR;
2535}
2536
2537
2538/** Editor callbacks given to callers to create request body */
2539
2540/* Helper to create simple xml tag without attributes. */
2541static void
2542make_simple_xml_tag(svn_stringbuf_t **buf_p,
2543                    const char *tagname,
2544                    const char *cdata,
2545                    apr_pool_t *pool)
2546{
2547  svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL);
2548  svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
2549  svn_xml_make_close_tag(buf_p, pool, tagname);
2550}
2551
2552static svn_error_t *
2553set_path(void *report_baton,
2554         const char *path,
2555         svn_revnum_t revision,
2556         svn_depth_t depth,
2557         svn_boolean_t start_empty,
2558         const char *lock_token,
2559         apr_pool_t *pool)
2560{
2561  report_context_t *report = report_baton;
2562  svn_stringbuf_t *buf = NULL;
2563
2564  svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2565                        "rev", apr_ltoa(pool, revision),
2566                        "lock-token", lock_token,
2567                        "depth", svn_depth_to_word(depth),
2568                        "start-empty", start_empty ? "true" : NULL,
2569                        NULL);
2570  svn_xml_escape_cdata_cstring(&buf, path, pool);
2571  svn_xml_make_close_tag(&buf, pool, "S:entry");
2572
2573  SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2574                                 NULL, pool));
2575
2576  return SVN_NO_ERROR;
2577}
2578
2579static svn_error_t *
2580delete_path(void *report_baton,
2581            const char *path,
2582            apr_pool_t *pool)
2583{
2584  report_context_t *report = report_baton;
2585  svn_stringbuf_t *buf = NULL;
2586
2587  make_simple_xml_tag(&buf, "S:missing", path, pool);
2588
2589  SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2590                                 NULL, pool));
2591
2592  return SVN_NO_ERROR;
2593}
2594
2595static svn_error_t *
2596link_path(void *report_baton,
2597          const char *path,
2598          const char *url,
2599          svn_revnum_t revision,
2600          svn_depth_t depth,
2601          svn_boolean_t start_empty,
2602          const char *lock_token,
2603          apr_pool_t *pool)
2604{
2605  report_context_t *report = report_baton;
2606  const char *link, *report_target;
2607  apr_uri_t uri;
2608  apr_status_t status;
2609  svn_stringbuf_t *buf = NULL;
2610
2611  /* We need to pass in the baseline relative path.
2612   *
2613   * TODO Confirm that it's on the same server?
2614   */
2615  status = apr_uri_parse(pool, url, &uri);
2616  if (status)
2617    {
2618      return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
2619                               _("Unable to parse URL '%s'"), url);
2620    }
2621
2622  SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess,
2623                                       NULL, pool));
2624  SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess,
2625                                         NULL, pool));
2626
2627  link = apr_pstrcat(pool, "/", link, (char *)NULL);
2628
2629  svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
2630                        "rev", apr_ltoa(pool, revision),
2631                        "lock-token", lock_token,
2632                        "depth", svn_depth_to_word(depth),
2633                        "linkpath", link,
2634                        "start-empty", start_empty ? "true" : NULL,
2635                        NULL);
2636  svn_xml_escape_cdata_cstring(&buf, path, pool);
2637  svn_xml_make_close_tag(&buf, pool, "S:entry");
2638
2639  SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2640                                 NULL, pool));
2641
2642  /* Store the switch roots to allow generating repos_relpaths from just
2643     the working copy paths. (Needed for HTTPv2) */
2644  path = apr_pstrdup(report->pool, path);
2645  svn_hash_sets(report->switched_paths,
2646                path, apr_pstrdup(report->pool, link + 1));
2647
2648  if (!*path)
2649    report->root_is_switched = TRUE;
2650
2651  return APR_SUCCESS;
2652}
2653
2654/** Minimum nr. of outstanding requests needed before a new connection is
2655 *  opened. */
2656#define REQS_PER_CONN 8
2657
2658/** This function creates a new connection for this serf session, but only
2659 * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
2660 * only one main connection open.
2661 */
2662static svn_error_t *
2663open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
2664{
2665  /* For each REQS_PER_CONN outstanding requests open a new connection, with
2666   * a minimum of 1 extra connection. */
2667  if (sess->num_conns == 1 ||
2668      ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
2669    {
2670      int cur = sess->num_conns;
2671      apr_status_t status;
2672
2673      sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
2674      sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
2675                                                                 NULL, NULL);
2676      sess->conns[cur]->last_status_code = -1;
2677      sess->conns[cur]->session = sess;
2678      status = serf_connection_create2(&sess->conns[cur]->conn,
2679                                       sess->context,
2680                                       sess->session_url,
2681                                       svn_ra_serf__conn_setup,
2682                                       sess->conns[cur],
2683                                       svn_ra_serf__conn_closed,
2684                                       sess->conns[cur],
2685                                       sess->pool);
2686      if (status)
2687        return svn_ra_serf__wrap_err(status, NULL);
2688
2689      sess->num_conns++;
2690    }
2691
2692  return SVN_NO_ERROR;
2693}
2694
2695/* Serf callback to create update request body bucket. */
2696static svn_error_t *
2697create_update_report_body(serf_bucket_t **body_bkt,
2698                          void *baton,
2699                          serf_bucket_alloc_t *alloc,
2700                          apr_pool_t *pool)
2701{
2702  report_context_t *report = baton;
2703  apr_off_t offset;
2704
2705  offset = 0;
2706  apr_file_seek(report->body_file, APR_SET, &offset);
2707
2708  *body_bkt = serf_bucket_file_create(report->body_file, alloc);
2709
2710  return SVN_NO_ERROR;
2711}
2712
2713/* Serf callback to setup update request headers. */
2714static svn_error_t *
2715setup_update_report_headers(serf_bucket_t *headers,
2716                            void *baton,
2717                            apr_pool_t *pool)
2718{
2719  report_context_t *report = baton;
2720
2721  if (report->sess->using_compression)
2722    {
2723      serf_bucket_headers_setn(headers, "Accept-Encoding",
2724                               "gzip,svndiff1;q=0.9,svndiff;q=0.8");
2725    }
2726  else
2727    {
2728      serf_bucket_headers_setn(headers, "Accept-Encoding",
2729                               "svndiff1;q=0.9,svndiff;q=0.8");
2730    }
2731
2732  return SVN_NO_ERROR;
2733}
2734
2735static svn_error_t *
2736finish_report(void *report_baton,
2737              apr_pool_t *pool)
2738{
2739  report_context_t *report = report_baton;
2740  svn_ra_serf__session_t *sess = report->sess;
2741  svn_ra_serf__handler_t *handler;
2742  svn_ra_serf__xml_parser_t *parser_ctx;
2743  const char *report_target;
2744  svn_stringbuf_t *buf = NULL;
2745  apr_pool_t *iterpool = svn_pool_create(pool);
2746  svn_error_t *err;
2747  apr_interval_time_t waittime_left = sess->timeout;
2748
2749  svn_xml_make_close_tag(&buf, iterpool, "S:update-report");
2750  SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
2751                                 NULL, iterpool));
2752
2753  /* We need to flush the file, make it unbuffered (so that it can be
2754   * zero-copied via mmap), and reset the position before attempting to
2755   * deliver the file.
2756   *
2757   * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
2758   * and zero-copy the PUT body.  However, on older APR versions, we can't
2759   * check the buffer status; but serf will fall through and create a file
2760   * bucket for us on the buffered svndiff handle.
2761   */
2762  apr_file_flush(report->body_file);
2763#if APR_VERSION_AT_LEAST(1, 3, 0)
2764  apr_file_buffer_set(report->body_file, NULL, 0);
2765#endif
2766
2767  SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool));
2768
2769  /* create and deliver request */
2770  report->path = report_target;
2771
2772  handler = apr_pcalloc(pool, sizeof(*handler));
2773
2774  handler->handler_pool = pool;
2775  handler->method = "REPORT";
2776  handler->path = report->path;
2777  handler->body_delegate = create_update_report_body;
2778  handler->body_delegate_baton = report;
2779  handler->body_type = "text/xml";
2780  handler->custom_accept_encoding = TRUE;
2781  handler->header_delegate = setup_update_report_headers;
2782  handler->header_delegate_baton = report;
2783  handler->conn = sess->conns[0];
2784  handler->session = sess;
2785
2786  parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
2787
2788  parser_ctx->pool = pool;
2789  parser_ctx->response_type = "update-report";
2790  parser_ctx->user_data = report;
2791  parser_ctx->start = start_report;
2792  parser_ctx->end = end_report;
2793  parser_ctx->cdata = cdata_report;
2794  parser_ctx->done = &report->done;
2795
2796  handler->response_handler = svn_ra_serf__handle_xml_parser;
2797  handler->response_baton = parser_ctx;
2798
2799  report->parser_ctx = parser_ctx;
2800
2801  svn_ra_serf__request_create(handler);
2802
2803  /* Open the first extra connection. */
2804  SVN_ERR(open_connection_if_needed(sess, 0));
2805
2806  sess->cur_conn = 1;
2807
2808  /* Note that we may have no active GET or PROPFIND requests, yet the
2809     processing has not been completed. This could be from a delay on the
2810     network or because we've spooled the entire response into our "pending"
2811     content of the XML parser. The DONE flag will get set when all the
2812     XML content has been received *and* parsed.  */
2813  while (!report->done
2814         || report->num_active_fetches
2815         || report->num_active_propfinds)
2816    {
2817      apr_pool_t *iterpool_inner;
2818      svn_ra_serf__list_t *done_list;
2819      int i;
2820      apr_status_t status;
2821
2822      /* Note: this throws out the old ITERPOOL_INNER.  */
2823      svn_pool_clear(iterpool);
2824
2825      if (sess->cancel_func)
2826        SVN_ERR(sess->cancel_func(sess->cancel_baton));
2827
2828      /* We need to be careful between the outer and inner ITERPOOLs,
2829         and what items are allocated within.  */
2830      iterpool_inner = svn_pool_create(iterpool);
2831
2832      status = serf_context_run(sess->context,
2833                                SVN_RA_SERF__CONTEXT_RUN_DURATION,
2834                                iterpool_inner);
2835
2836      err = sess->pending_error;
2837      sess->pending_error = SVN_NO_ERROR;
2838
2839      if (!err && handler->done && handler->server_error)
2840        {
2841          err = handler->server_error->error;
2842        }
2843
2844      /* If the context duration timeout is up, we'll subtract that
2845         duration from the total time alloted for such things.  If
2846         there's no time left, we fail with a message indicating that
2847         the connection timed out.  */
2848      if (APR_STATUS_IS_TIMEUP(status))
2849        {
2850          svn_error_clear(err);
2851          err = SVN_NO_ERROR;
2852          status = 0;
2853
2854          if (sess->timeout)
2855            {
2856              if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
2857                {
2858                  waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
2859                }
2860              else
2861                {
2862                  return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
2863                                          _("Connection timed out"));
2864                }
2865            }
2866        }
2867      else
2868        {
2869          waittime_left = sess->timeout;
2870        }
2871
2872      if (status && handler->sline.code != 200)
2873        {
2874          return svn_error_trace(
2875                    svn_error_compose_create(
2876                        svn_ra_serf__error_on_status(handler->sline,
2877                                                     handler->path,
2878                                                     handler->location),
2879                        err));
2880        }
2881      SVN_ERR(err);
2882      if (status)
2883        {
2884          return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT"));
2885        }
2886
2887      /* Open extra connections if we have enough requests to send. */
2888      if (sess->num_conns < sess->max_connections)
2889        SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches +
2890                                          report->num_active_propfinds));
2891
2892      /* Prune completed file PROPFINDs. */
2893      done_list = report->done_propfinds;
2894      while (done_list)
2895        {
2896          svn_ra_serf__list_t *next_done = done_list->next;
2897
2898          svn_pool_clear(iterpool_inner);
2899
2900          report->num_active_propfinds--;
2901
2902          /* If we have some files that we won't be fetching the content
2903           * for, ensure that we update the file with any altered props.
2904           */
2905          if (report->file_propchanges_only)
2906            {
2907              svn_ra_serf__list_t *cur, *prev;
2908
2909              prev = NULL;
2910              cur = report->file_propchanges_only;
2911
2912              while (cur)
2913                {
2914                  report_info_t *item = cur->data;
2915
2916                  if (item->propfind_handler == done_list->data)
2917                    {
2918                      break;
2919                    }
2920
2921                  prev = cur;
2922                  cur = cur->next;
2923                }
2924
2925              /* If we found a match, set the new props and remove this
2926               * propchange from our list.
2927               */
2928              if (cur)
2929                {
2930                  report_info_t *info = cur->data;
2931
2932                  if (!prev)
2933                    {
2934                      report->file_propchanges_only = cur->next;
2935                    }
2936                  else
2937                    {
2938                      prev->next = cur->next;
2939                    }
2940
2941                  /* If we've got cached file content for this file,
2942                     take care of the locally collected properties and
2943                     file content at once.  Otherwise, just deal with
2944                     the collected properties.
2945
2946                     NOTE:  These functions below could delete
2947                     info->dir->pool (via maybe_close_dir_chain()),
2948                     from which is allocated the list item in
2949                     report->file_propchanges_only.
2950                  */
2951                  if (info->cached_contents)
2952                    {
2953                      SVN_ERR(handle_local_content(info, iterpool_inner));
2954                    }
2955                  else
2956                    {
2957                      SVN_ERR(handle_propchange_only(info, iterpool_inner));
2958                    }
2959                }
2960            }
2961
2962          done_list = next_done;
2963        }
2964      report->done_propfinds = NULL;
2965
2966      /* Prune completed fetches from our list. */
2967      done_list = report->done_fetches;
2968      while (done_list)
2969        {
2970          report_fetch_t *done_fetch = done_list->data;
2971          svn_ra_serf__list_t *next_done = done_list->next;
2972          report_dir_t *cur_dir;
2973
2974          /* Decrease the refcount in the parent directory of the file
2975             whose fetch has completed. */
2976          cur_dir = done_fetch->info->dir;
2977          cur_dir->ref_count--;
2978
2979          /* Decrement our active fetch count. */
2980          report->num_active_fetches--;
2981
2982          /* See if the parent directory of this fetched item (and
2983             perhaps even parents of that) can be closed now.
2984
2985             NOTE:  This could delete cur_dir->pool, from which is
2986             allocated the list item in report->done_fetches.
2987          */
2988          SVN_ERR(maybe_close_dir_chain(cur_dir));
2989
2990          done_list = next_done;
2991        }
2992      report->done_fetches = NULL;
2993
2994      /* Prune completed directory PROPFINDs. */
2995      done_list = report->done_dir_propfinds;
2996      while (done_list)
2997        {
2998          svn_ra_serf__list_t *next_done = done_list->next;
2999
3000          report->num_active_propfinds--;
3001
3002          if (report->active_dir_propfinds)
3003            {
3004              svn_ra_serf__list_t *cur, *prev;
3005
3006              prev = NULL;
3007              cur = report->active_dir_propfinds;
3008
3009              while (cur)
3010                {
3011                  report_dir_t *item = cur->data;
3012
3013                  if (item->propfind_handler == done_list->data)
3014                    {
3015                      break;
3016                    }
3017
3018                  prev = cur;
3019                  cur = cur->next;
3020                }
3021              SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */
3022
3023              /* If we found a match, set the new props and remove this
3024               * propchange from our list.
3025               */
3026              if (cur)
3027                {
3028                  report_dir_t *cur_dir = cur->data;
3029
3030                  if (!prev)
3031                    {
3032                      report->active_dir_propfinds = cur->next;
3033                    }
3034                  else
3035                    {
3036                      prev->next = cur->next;
3037                    }
3038
3039                  /* See if this directory (and perhaps even parents of that)
3040                     can be closed now.
3041
3042                     NOTE:  This could delete cur_dir->pool, from which is
3043                     allocated the list item in report->active_dir_propfinds.
3044                  */
3045                  SVN_ERR(maybe_close_dir_chain(cur_dir));
3046                }
3047            }
3048
3049          done_list = next_done;
3050        }
3051      report->done_dir_propfinds = NULL;
3052
3053      /* If the parser is paused, and the number of active requests has
3054         dropped far enough, then resume parsing.  */
3055      if (parser_ctx->paused
3056          && (report->num_active_fetches + report->num_active_propfinds
3057              < REQUEST_COUNT_TO_RESUME))
3058        parser_ctx->paused = FALSE;
3059
3060      /* If we have not paused the parser and it looks like data MAY be
3061         present (we can't know for sure because of the private structure),
3062         then go process the pending content.  */
3063      if (!parser_ctx->paused && parser_ctx->pending != NULL)
3064        SVN_ERR(svn_ra_serf__process_pending(parser_ctx,
3065                                             &report->report_received,
3066                                             iterpool_inner));
3067
3068      /* Debugging purposes only! */
3069      for (i = 0; i < sess->num_conns; i++)
3070        {
3071          serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
3072        }
3073    }
3074
3075  /* If we got a complete report, close the edit.  Otherwise, abort it. */
3076  if (report->report_completed)
3077    {
3078      /* Ensure that we opened and closed our root dir and that we closed
3079       * all of our children. */
3080      if (!report->closed_root && report->root_dir != NULL)
3081        {
3082          SVN_ERR(close_all_dirs(report->root_dir));
3083        }
3084
3085      err = report->update_editor->close_edit(report->update_baton, iterpool);
3086    }
3087  else
3088    {
3089      /* Tell the editor that something failed */
3090      err = report->update_editor->abort_edit(report->update_baton, iterpool);
3091
3092      err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
3093                             _("Missing update-report close tag"));
3094    }
3095
3096  svn_pool_destroy(iterpool);
3097  return svn_error_trace(err);
3098}
3099
3100
3101static svn_error_t *
3102abort_report(void *report_baton,
3103             apr_pool_t *pool)
3104{
3105#if 0
3106  report_context_t *report = report_baton;
3107#endif
3108
3109  /* Should we perform some cleanup here? */
3110
3111  return SVN_NO_ERROR;
3112}
3113
3114static const svn_ra_reporter3_t ra_serf_reporter = {
3115  set_path,
3116  delete_path,
3117  link_path,
3118  finish_report,
3119  abort_report
3120};
3121
3122
3123/** RA function implementations and body */
3124
3125static svn_error_t *
3126make_update_reporter(svn_ra_session_t *ra_session,
3127                     const svn_ra_reporter3_t **reporter,
3128                     void **report_baton,
3129                     svn_revnum_t revision,
3130                     const char *src_path,
3131                     const char *dest_path,
3132                     const char *update_target,
3133                     svn_depth_t depth,
3134                     svn_boolean_t ignore_ancestry,
3135                     svn_boolean_t text_deltas,
3136                     svn_boolean_t send_copyfrom_args,
3137                     const svn_delta_editor_t *update_editor,
3138                     void *update_baton,
3139                     apr_pool_t *result_pool,
3140                     apr_pool_t *scratch_pool)
3141{
3142  report_context_t *report;
3143  const svn_delta_editor_t *filter_editor;
3144  void *filter_baton;
3145  svn_boolean_t has_target = *update_target != '\0';
3146  svn_boolean_t server_supports_depth;
3147  svn_ra_serf__session_t *sess = ra_session->priv;
3148  svn_stringbuf_t *buf = NULL;
3149  svn_boolean_t use_bulk_updates;
3150
3151  SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
3152                                      SVN_RA_CAPABILITY_DEPTH, scratch_pool));
3153  /* We can skip the depth filtering when the user requested
3154     depth_files or depth_infinity because the server will
3155     transmit the right stuff anyway. */
3156  if ((depth != svn_depth_files)
3157      && (depth != svn_depth_infinity)
3158      && ! server_supports_depth)
3159    {
3160      SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
3161                                            &filter_baton,
3162                                            update_editor,
3163                                            update_baton,
3164                                            depth, has_target,
3165                                            sess->pool));
3166      update_editor = filter_editor;
3167      update_baton = filter_baton;
3168    }
3169
3170  report = apr_pcalloc(result_pool, sizeof(*report));
3171  report->pool = result_pool;
3172  report->sess = sess;
3173  report->conn = report->sess->conns[0];
3174  report->target_rev = revision;
3175  report->ignore_ancestry = ignore_ancestry;
3176  report->send_copyfrom_args = send_copyfrom_args;
3177  report->text_deltas = text_deltas;
3178  report->switched_paths = apr_hash_make(report->pool);
3179
3180  report->source = src_path;
3181  report->destination = dest_path;
3182  report->update_target = update_target;
3183
3184  report->update_editor = update_editor;
3185  report->update_baton = update_baton;
3186  report->done = FALSE;
3187
3188  *reporter = &ra_serf_reporter;
3189  *report_baton = report;
3190
3191  SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL,
3192                                   svn_io_file_del_on_pool_cleanup,
3193                                   report->pool, scratch_pool));
3194
3195  if (sess->bulk_updates == svn_tristate_true)
3196    {
3197      /* User would like to use bulk updates. */
3198      use_bulk_updates = TRUE;
3199    }
3200  else if (sess->bulk_updates == svn_tristate_false)
3201    {
3202      /* User doesn't want bulk updates. */
3203      use_bulk_updates = FALSE;
3204    }
3205  else
3206    {
3207      /* User doesn't have any preferences on bulk updates. Decide on server
3208         preferences and capabilities. */
3209      if (sess->server_allows_bulk)
3210        {
3211          if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
3212            {
3213              /* Server doesn't want bulk updates */
3214              use_bulk_updates = FALSE;
3215            }
3216          else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
3217            {
3218              /* Server prefers bulk updates, and we respect that */
3219              use_bulk_updates = TRUE;
3220            }
3221          else
3222            {
3223              /* Server allows bulk updates, but doesn't dictate its use. Do
3224                 whatever is the default. */
3225              use_bulk_updates = FALSE;
3226            }
3227        }
3228      else
3229        {
3230          /* Pre-1.8 server didn't send the bulk_updates header. Check if server
3231             supports inlining properties in update editor report. */
3232          if (sess->supports_inline_props)
3233            {
3234              /* Inline props supported: do not use bulk updates. */
3235              use_bulk_updates = FALSE;
3236            }
3237          else
3238            {
3239              /* Inline props are not supported: use bulk updates to avoid
3240               * PROPFINDs for every added node. */
3241              use_bulk_updates = TRUE;
3242            }
3243        }
3244    }
3245
3246  if (use_bulk_updates)
3247    {
3248      svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
3249                            "S:update-report",
3250                            "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
3251                            NULL);
3252    }
3253  else
3254    {
3255      svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
3256                            "S:update-report",
3257                            "xmlns:S", SVN_XML_NAMESPACE,
3258                            NULL);
3259      /* Subversion 1.8+ servers can be told to send properties for newly
3260         added items inline even when doing a skelta response. */
3261      make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
3262    }
3263
3264  make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
3265
3266  if (SVN_IS_VALID_REVNUM(report->target_rev))
3267    {
3268      make_simple_xml_tag(&buf, "S:target-revision",
3269                          apr_ltoa(scratch_pool, report->target_rev),
3270                          scratch_pool);
3271    }
3272
3273  if (report->destination && *report->destination)
3274    {
3275      make_simple_xml_tag(&buf, "S:dst-path", report->destination,
3276                          scratch_pool);
3277    }
3278
3279  if (report->update_target && *report->update_target)
3280    {
3281      make_simple_xml_tag(&buf, "S:update-target", report->update_target,
3282                          scratch_pool);
3283    }
3284
3285  if (report->ignore_ancestry)
3286    {
3287      make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
3288    }
3289
3290  if (report->send_copyfrom_args)
3291    {
3292      make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
3293    }
3294
3295  /* Old servers know "recursive" but not "depth"; help them DTRT. */
3296  if (depth == svn_depth_files || depth == svn_depth_empty)
3297    {
3298      make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
3299    }
3300
3301  /* When in 'send-all' mode, mod_dav_svn will assume that it should
3302     calculate and transmit real text-deltas (instead of empty windows
3303     that merely indicate "text is changed") unless it finds this
3304     element.
3305
3306     NOTE: Do NOT count on servers actually obeying this, as some exist
3307     which obey send-all, but do not check for this directive at all!
3308
3309     NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
3310     override our request and send text-deltas. */
3311  if (! text_deltas)
3312    {
3313      make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
3314    }
3315
3316  make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
3317
3318  SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
3319                                 NULL, scratch_pool));
3320
3321  return SVN_NO_ERROR;
3322}
3323
3324svn_error_t *
3325svn_ra_serf__do_update(svn_ra_session_t *ra_session,
3326                       const svn_ra_reporter3_t **reporter,
3327                       void **report_baton,
3328                       svn_revnum_t revision_to_update_to,
3329                       const char *update_target,
3330                       svn_depth_t depth,
3331                       svn_boolean_t send_copyfrom_args,
3332                       svn_boolean_t ignore_ancestry,
3333                       const svn_delta_editor_t *update_editor,
3334                       void *update_baton,
3335                       apr_pool_t *result_pool,
3336                       apr_pool_t *scratch_pool)
3337{
3338  svn_ra_serf__session_t *session = ra_session->priv;
3339
3340  SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3341                               revision_to_update_to,
3342                               session->session_url.path, NULL, update_target,
3343                               depth, ignore_ancestry, TRUE /* text_deltas */,
3344                               send_copyfrom_args,
3345                               update_editor, update_baton,
3346                               result_pool, scratch_pool));
3347  return SVN_NO_ERROR;
3348}
3349
3350svn_error_t *
3351svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
3352                     const svn_ra_reporter3_t **reporter,
3353                     void **report_baton,
3354                     svn_revnum_t revision,
3355                     const char *diff_target,
3356                     svn_depth_t depth,
3357                     svn_boolean_t ignore_ancestry,
3358                     svn_boolean_t text_deltas,
3359                     const char *versus_url,
3360                     const svn_delta_editor_t *diff_editor,
3361                     void *diff_baton,
3362                     apr_pool_t *pool)
3363{
3364  svn_ra_serf__session_t *session = ra_session->priv;
3365  apr_pool_t *scratch_pool = svn_pool_create(pool);
3366
3367  SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3368                               revision,
3369                               session->session_url.path, versus_url, diff_target,
3370                               depth, ignore_ancestry, text_deltas, FALSE,
3371                               diff_editor, diff_baton,
3372                               pool, scratch_pool));
3373  svn_pool_destroy(scratch_pool);
3374  return SVN_NO_ERROR;
3375}
3376
3377svn_error_t *
3378svn_ra_serf__do_status(svn_ra_session_t *ra_session,
3379                       const svn_ra_reporter3_t **reporter,
3380                       void **report_baton,
3381                       const char *status_target,
3382                       svn_revnum_t revision,
3383                       svn_depth_t depth,
3384                       const svn_delta_editor_t *status_editor,
3385                       void *status_baton,
3386                       apr_pool_t *pool)
3387{
3388  svn_ra_serf__session_t *session = ra_session->priv;
3389  apr_pool_t *scratch_pool = svn_pool_create(pool);
3390
3391  SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
3392                               revision,
3393                               session->session_url.path, NULL, status_target,
3394                               depth, FALSE, FALSE, FALSE,
3395                               status_editor, status_baton,
3396                               pool, scratch_pool));
3397  svn_pool_destroy(scratch_pool);
3398  return SVN_NO_ERROR;
3399}
3400
3401svn_error_t *
3402svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
3403                       const svn_ra_reporter3_t **reporter,
3404                       void **report_baton,
3405                       svn_revnum_t revision_to_switch_to,
3406                       const char *switch_target,
3407                       svn_depth_t depth,
3408                       const char *switch_url,
3409                       svn_boolean_t send_copyfrom_args,
3410                       svn_boolean_t ignore_ancestry,
3411                       const svn_delta_editor_t *switch_editor,
3412                       void *switch_baton,
3413                       apr_pool_t *result_pool,
3414                       apr_pool_t *scratch_pool)
3415{
3416  svn_ra_serf__session_t *session = ra_session->priv;
3417
3418  return make_update_reporter(ra_session, reporter, report_baton,
3419                              revision_to_switch_to,
3420                              session->session_url.path,
3421                              switch_url, switch_target,
3422                              depth,
3423                              ignore_ancestry,
3424                              TRUE /* text_deltas */,
3425                              send_copyfrom_args,
3426                              switch_editor, switch_baton,
3427                              result_pool, scratch_pool);
3428}
3429
3430/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
3431 * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
3432 * present in PROPS.
3433 *
3434 * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
3435 *
3436 * Performs all temporary allocations in POOL.
3437 */
3438static svn_error_t *
3439try_get_wc_contents(svn_boolean_t *found_p,
3440                    svn_ra_serf__session_t *session,
3441                    apr_hash_t *props,
3442                    svn_stream_t *dst_stream,
3443                    apr_pool_t *pool)
3444{
3445  apr_hash_t *svn_props;
3446  const char *sha1_checksum_prop;
3447  svn_checksum_t *checksum;
3448  svn_stream_t *wc_stream;
3449  svn_error_t *err;
3450
3451  /* No contents found by default. */
3452  *found_p = FALSE;
3453
3454  if (!session->wc_callbacks->get_wc_contents)
3455    {
3456      /* No callback, nothing to do. */
3457      return SVN_NO_ERROR;
3458    }
3459
3460
3461  svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
3462  if (!svn_props)
3463    {
3464      /* No properties -- therefore no checksum property -- in response. */
3465      return SVN_NO_ERROR;
3466    }
3467
3468  sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum");
3469  if (sha1_checksum_prop == NULL)
3470    {
3471      /* No checksum property in response. */
3472      return SVN_NO_ERROR;
3473    }
3474
3475  SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
3476                                 sha1_checksum_prop, pool));
3477
3478  err = session->wc_callbacks->get_wc_contents(
3479          session->wc_callback_baton, &wc_stream, checksum, pool);
3480
3481  if (err)
3482    {
3483      svn_error_clear(err);
3484
3485      /* Ignore errors for now. */
3486      return SVN_NO_ERROR;
3487    }
3488
3489  if (wc_stream)
3490    {
3491        SVN_ERR(svn_stream_copy3(wc_stream,
3492                                 svn_stream_disown(dst_stream, pool),
3493                                 NULL, NULL, pool));
3494      *found_p = TRUE;
3495    }
3496
3497  return SVN_NO_ERROR;
3498}
3499
3500svn_error_t *
3501svn_ra_serf__get_file(svn_ra_session_t *ra_session,
3502                      const char *path,
3503                      svn_revnum_t revision,
3504                      svn_stream_t *stream,
3505                      svn_revnum_t *fetched_rev,
3506                      apr_hash_t **props,
3507                      apr_pool_t *pool)
3508{
3509  svn_ra_serf__session_t *session = ra_session->priv;
3510  svn_ra_serf__connection_t *conn;
3511  const char *fetch_url;
3512  apr_hash_t *fetch_props;
3513  svn_node_kind_t res_kind;
3514  const svn_ra_serf__dav_props_t *which_props;
3515
3516  /* What connection should we go on? */
3517  conn = session->conns[session->cur_conn];
3518
3519  /* Fetch properties. */
3520
3521  fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
3522
3523  /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
3524   *
3525   * Otherwise, we need to get the baseline version for this particular
3526   * revision and then fetch that file.
3527   */
3528  if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
3529    {
3530      SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
3531                                          session, conn,
3532                                          fetch_url, revision,
3533                                          pool, pool));
3534      revision = SVN_INVALID_REVNUM;
3535    }
3536  /* REVISION is always SVN_INVALID_REVNUM  */
3537  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
3538
3539  if (props)
3540    {
3541      which_props = all_props;
3542    }
3543  else if (stream && session->wc_callbacks->get_wc_contents)
3544    {
3545      which_props = type_and_checksum_props;
3546    }
3547  else
3548    {
3549      which_props = check_path_props;
3550    }
3551
3552  SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url,
3553                                        SVN_INVALID_REVNUM,
3554                                        which_props,
3555                                        pool, pool));
3556
3557  /* Verify that resource type is not collection. */
3558  SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props));
3559  if (res_kind != svn_node_file)
3560    {
3561      return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
3562                              _("Can't get text contents of a directory"));
3563    }
3564
3565  /* TODO Filter out all of our props into a usable format. */
3566  if (props)
3567    {
3568      /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
3569         ### put them into POOL, so we're okay.  */
3570      SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props,
3571                                         pool, pool));
3572    }
3573
3574  if (stream)
3575    {
3576      svn_boolean_t found;
3577      SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool));
3578
3579      /* No contents found in the WC, let's fetch from server. */
3580      if (!found)
3581        {
3582          report_fetch_t *stream_ctx;
3583          svn_ra_serf__handler_t *handler;
3584
3585          /* Create the fetch context. */
3586          stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
3587          stream_ctx->target_stream = stream;
3588          stream_ctx->sess = session;
3589          stream_ctx->conn = conn;
3590          stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
3591          stream_ctx->info->name = fetch_url;
3592
3593          handler = apr_pcalloc(pool, sizeof(*handler));
3594
3595          handler->handler_pool = pool;
3596          handler->method = "GET";
3597          handler->path = fetch_url;
3598          handler->conn = conn;
3599          handler->session = session;
3600
3601          handler->custom_accept_encoding = TRUE;
3602          handler->header_delegate = headers_fetch;
3603          handler->header_delegate_baton = stream_ctx;
3604
3605          handler->response_handler = handle_stream;
3606          handler->response_baton = stream_ctx;
3607
3608          handler->response_error = cancel_fetch;
3609          handler->response_error_baton = stream_ctx;
3610
3611          stream_ctx->handler = handler;
3612
3613          svn_ra_serf__request_create(handler);
3614
3615          SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
3616        }
3617    }
3618
3619  return SVN_NO_ERROR;
3620}
3621