]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_ra_serf/update.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_ra_serf / update.c
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 \f
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 \f
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  */
65 typedef 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. */
102 typedef struct report_context_t report_context_t;
103
104 /*
105  * This structure represents the information for a directory.
106  */
107 typedef 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  */
177 typedef 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  */
256 typedef 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  */
302 struct 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
399 static 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  */
448 static svn_error_t *
449 update_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  */
463 static svn_error_t *
464 update_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  */
487 static svn_error_t *
488 update_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. */
504 static svn_ra_serf__connection_t *
505 get_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 \f
547 /** Report state management helper **/
548
549 static report_info_t *
550 push_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 \f
635 /** Wrappers around our various property walkers **/
636
637 static svn_error_t *
638 set_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
658 static svn_error_t *
659 set_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
679 static svn_error_t *
680 remove_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
700 static svn_error_t *
701 remove_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 \f
721 /** Helpers to open and close directories */
722
723 static svn_error_t*
724 ensure_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
778 static svn_error_t *
779 close_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
839 static 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 \f
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  */
865 static void
866 check_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
894 static svn_error_t *
895 headers_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
918 static svn_error_t *
919 cancel_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
954 static svn_error_t *
955 error_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().  */
985 static svn_error_t *
986 open_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(). */
1043 static svn_error_t *
1044 close_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 */
1080 static svn_error_t *
1081 handle_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 */
1258 static svn_error_t *
1259 handle_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 */
1358 static svn_error_t *
1359 maybe_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. */
1411 static svn_error_t *
1412 handle_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.  */
1435 static svn_error_t *
1436 handle_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
1459 static svn_error_t *
1460 fetch_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 \f
1620 /** XML callbacks for our update-report response parsing */
1621
1622 static svn_error_t *
1623 start_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
2146 static svn_error_t *
2147 end_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
2501 static svn_error_t *
2502 cdata_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 \f
2543 /** Editor callbacks given to callers to create request body */
2544
2545 /* Helper to create simple xml tag without attributes. */
2546 static void
2547 make_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
2557 static svn_error_t *
2558 set_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
2591 static svn_error_t *
2592 delete_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
2607 static svn_error_t *
2608 link_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  */
2680 static svn_error_t *
2681 open_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. */
2714 static svn_error_t *
2715 create_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. */
2732 static svn_error_t *
2733 setup_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
2753 static svn_error_t *
2754 finish_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
3119 static svn_error_t *
3120 abort_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
3132 static 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 \f
3141 /** RA function implementations and body */
3142
3143 static svn_error_t *
3144 make_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
3343 svn_error_t *
3344 svn_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
3369 svn_error_t *
3370 svn_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
3396 svn_error_t *
3397 svn_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
3420 svn_error_t *
3421 svn_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  */
3457 static svn_error_t *
3458 try_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
3519 svn_error_t *
3520 svn_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 }