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