Followup to r29625: fix getopt tests.
[svn.git] / subversion / libsvn_ra_neon / fetch.c
blob84cd9cb7846604743f16418e6c67fd5cef77883b
1 /*
2 * fetch.c : routines for fetching updates and checkouts
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <assert.h>
22 #include <stdlib.h> /* for free() */
24 #define APR_WANT_STRFUNC
25 #include <apr_want.h> /* for strcmp() */
27 #include <apr_pools.h>
28 #include <apr_tables.h>
29 #include <apr_strings.h>
30 #include <apr_md5.h>
31 #include <apr_xml.h>
33 #include <ne_basic.h>
35 #include "svn_error.h"
36 #include "svn_pools.h"
37 #include "svn_delta.h"
38 #include "svn_io.h"
39 #include "svn_md5.h"
40 #include "svn_base64.h"
41 #include "svn_ra.h"
42 #include "../libsvn_ra/ra_loader.h"
43 #include "svn_path.h"
44 #include "svn_xml.h"
45 #include "svn_dav.h"
46 #include "svn_time.h"
47 #include "svn_props.h"
49 #include "private/svn_dav_protocol.h"
50 #include "svn_private_config.h"
52 #include "ra_neon.h"
55 typedef struct {
56 /* the information for this subdir. if rsrc==NULL, then this is a sentinel
57 record in fetch_ctx_t.subdirs to close the directory implied by the
58 parent_baton member. */
59 svn_ra_neon__resource_t *rsrc;
61 /* the directory containing this subdirectory. */
62 void *parent_baton;
64 } subdir_t;
66 typedef struct {
67 apr_pool_t *pool;
69 /* these two are the handler that the editor gave us */
70 svn_txdelta_window_handler_t handler;
71 void *handler_baton;
73 /* if we're receiving an svndiff, this is a parser which places the
74 resulting windows into the above handler/baton. */
75 svn_stream_t *stream;
77 } file_read_ctx_t;
79 typedef struct {
80 svn_boolean_t do_checksum; /* only accumulate checksum if set */
81 apr_md5_ctx_t md5_context; /* accumulating checksum of file contents */
82 svn_stream_t *stream; /* stream to write file contents to */
83 } file_write_ctx_t;
85 typedef struct {
86 svn_ra_neon__request_t *req; /* Used to propagate errors out of the reader */
87 int checked_type; /* have we processed ctype yet? */
89 void *subctx;
90 } custom_get_ctx_t;
92 #define POP_SUBDIR(sds) (APR_ARRAY_IDX((sds), --(sds)->nelts, subdir_t *))
93 #define PUSH_SUBDIR(sds,s) (APR_ARRAY_PUSH((sds), subdir_t *) = (s))
95 typedef svn_error_t * (*prop_setter_t)(void *baton,
96 const char *name,
97 const svn_string_t *value,
98 apr_pool_t *pool);
100 typedef struct {
101 /* The baton returned by the editor's open_root/open_dir */
102 void *baton;
104 /* Should we fetch properties for this directory when the close tag
105 is found? */
106 svn_boolean_t fetch_props;
108 /* The version resource URL for this directory. */
109 const char *vsn_url;
111 /* A buffer which stores the relative directory name. We also use this
112 for temporary construction of relative file names. */
113 svn_stringbuf_t *pathbuf;
115 /* If a directory, this may contain a hash of prophashes returned
116 from doing a depth 1 PROPFIND. */
117 apr_hash_t *children;
119 /* A subpool. It's about memory. Ya dig? */
120 apr_pool_t *pool;
122 } dir_item_t;
124 typedef struct {
125 svn_ra_neon__session_t *ras;
127 apr_file_t *tmpfile;
129 /* The pool of the report baton; used for things that must live during the
130 whole editing operation. */
131 apr_pool_t *pool;
132 /* Pool initialized when the report_baton is created, and meant for
133 quick scratchwork. This is like a loop pool, but since the loop
134 that drives ra_neon callbacks is in the wrong scope for us to use
135 the normal loop pool idiom, we must resort to this. Always clear
136 this pool right after using it; only YOU can prevent forest fires. */
137 apr_pool_t *scratch_pool;
139 svn_boolean_t fetch_content;
140 svn_boolean_t fetch_props;
142 const svn_delta_editor_t *editor;
143 void *edit_baton;
145 /* Stack of directory batons/vsn_urls. */
146 apr_array_header_t *dirs;
148 #define TOP_DIR(rb) (APR_ARRAY_IDX((rb)->dirs, (rb)->dirs->nelts - 1, \
149 dir_item_t))
150 #define DIR_DEPTH(rb) ((rb)->dirs->nelts)
151 #define PUSH_BATON(rb,b) (APR_ARRAY_PUSH((rb)->dirs, void *) = (b))
153 /* These items are only valid inside add- and open-file tags! */
154 void *file_baton;
155 apr_pool_t *file_pool;
156 const char *result_checksum; /* hex md5 digest of result; may be null */
158 svn_stringbuf_t *namestr;
159 svn_stringbuf_t *cpathstr;
160 svn_stringbuf_t *href;
162 /* Empty string means no encoding, "base64" means base64. */
163 svn_stringbuf_t *encoding;
165 /* These are used when receiving an inline txdelta, and null at all
166 other times. */
167 svn_txdelta_window_handler_t whandler;
168 void *whandler_baton;
169 svn_stream_t *svndiff_decoder;
170 svn_stream_t *base64_decoder;
172 /* A generic accumulator for elements that have small bits of cdata,
173 like md5_checksum, href, etc. Uh, or where our own API gives us
174 no choice about holding them in memory, as with prop values, ahem.
175 This is always the empty stringbuf when not in use. */
176 svn_stringbuf_t *cdata_accum;
178 /* Are we inside a resource element? */
179 svn_boolean_t in_resource;
180 /* Valid if in_resource is true. */
181 svn_stringbuf_t *current_wcprop_path;
182 svn_boolean_t is_switch;
184 /* Named target, or NULL if none. For example, in 'svn up wc/foo',
185 this is "wc/foo", but in 'svn up' it is "".
187 The target helps us determine whether a response received from
188 the server should be acted on. Take 'svn up wc/foo': the server
189 may send back a new vsn-rsrc-url wcprop for 'wc' (because the
190 report had to be anchored there just in case the update deletes
191 wc/foo). While this is correct behavior for the server, the
192 client should ignore the new wcprop, because the client knows
193 it's not really updating the top level directory. */
194 const char *target;
196 /* Whether the server should try to send copyfrom arguments. */
197 svn_boolean_t send_copyfrom_args;
199 /* Use an intermediate tmpfile for the REPORT response. */
200 svn_boolean_t spool_response;
202 /* A modern server will understand our "send-all" attribute on the
203 update report request, and will put a "send-all" attribute on
204 its response. If we see that attribute, we set this to true,
205 otherwise, it stays false (i.e., it's not a modern server). */
206 svn_boolean_t receiving_all;
208 } report_baton_t;
210 static const svn_ra_neon__xml_elm_t report_elements[] =
212 { SVN_XML_NAMESPACE, "update-report", ELEM_update_report, 0 },
213 { SVN_XML_NAMESPACE, "resource-walk", ELEM_resource_walk, 0 },
214 { SVN_XML_NAMESPACE, "resource", ELEM_resource, 0 },
215 { SVN_XML_NAMESPACE, "target-revision", ELEM_target_revision, 0 },
216 { SVN_XML_NAMESPACE, "open-directory", ELEM_open_directory, 0 },
217 { SVN_XML_NAMESPACE, "add-directory", ELEM_add_directory, 0 },
218 { SVN_XML_NAMESPACE, "absent-directory", ELEM_absent_directory, 0 },
219 { SVN_XML_NAMESPACE, "open-file", ELEM_open_file, 0 },
220 { SVN_XML_NAMESPACE, "add-file", ELEM_add_file, 0 },
221 { SVN_XML_NAMESPACE, "txdelta", ELEM_txdelta, 0 },
222 { SVN_XML_NAMESPACE, "absent-file", ELEM_absent_file, 0 },
223 { SVN_XML_NAMESPACE, "delete-entry", ELEM_delete_entry, 0 },
224 { SVN_XML_NAMESPACE, "fetch-props", ELEM_fetch_props, 0 },
225 { SVN_XML_NAMESPACE, "set-prop", ELEM_set_prop, 0 },
226 { SVN_XML_NAMESPACE, "remove-prop", ELEM_remove_prop, 0 },
227 { SVN_XML_NAMESPACE, "fetch-file", ELEM_fetch_file, 0 },
228 { SVN_XML_NAMESPACE, "prop", ELEM_SVN_prop, 0 },
229 { SVN_DAV_PROP_NS_DAV, "repository-uuid",
230 ELEM_repository_uuid, SVN_RA_NEON__XML_CDATA },
232 { SVN_DAV_PROP_NS_DAV, "md5-checksum", ELEM_md5_checksum,
233 SVN_RA_NEON__XML_CDATA },
235 { "DAV:", "version-name", ELEM_version_name, SVN_RA_NEON__XML_CDATA },
236 { "DAV:", SVN_DAV__CREATIONDATE, ELEM_creationdate, SVN_RA_NEON__XML_CDATA },
237 { "DAV:", "creator-displayname", ELEM_creator_displayname,
238 SVN_RA_NEON__XML_CDATA },
240 { "DAV:", "checked-in", ELEM_checked_in, 0 },
241 { "DAV:", "href", ELEM_href, SVN_RA_NEON__XML_CDATA },
243 { NULL }
246 static svn_error_t *simple_store_vsn_url(const char *vsn_url,
247 void *baton,
248 prop_setter_t setter,
249 apr_pool_t *pool)
251 /* store the version URL as a property */
252 SVN_ERR_W((*setter)(baton, SVN_RA_NEON__LP_VSN_URL,
253 svn_string_create(vsn_url, pool), pool),
254 _("Could not save the URL of the version resource"));
256 return NULL;
259 static svn_error_t *get_delta_base(const char **delta_base,
260 const char *relpath,
261 svn_ra_get_wc_prop_func_t get_wc_prop,
262 void *cb_baton,
263 apr_pool_t *pool)
265 const svn_string_t *value;
267 if (relpath == NULL || get_wc_prop == NULL)
269 *delta_base = NULL;
270 return SVN_NO_ERROR;
273 SVN_ERR((*get_wc_prop)(cb_baton, relpath, SVN_RA_NEON__LP_VSN_URL,
274 &value, pool));
276 *delta_base = value ? value->data : NULL;
277 return SVN_NO_ERROR;
280 /* helper func which maps certain DAV: properties to svn:wc:
281 properties. Used during checkouts and updates. */
282 static svn_error_t *set_special_wc_prop(const char *key,
283 const svn_string_t *val,
284 prop_setter_t setter,
285 void *baton,
286 apr_pool_t *pool)
288 const char *name = NULL;
290 if (strcmp(key, SVN_RA_NEON__PROP_VERSION_NAME) == 0)
291 name = SVN_PROP_ENTRY_COMMITTED_REV;
292 else if (strcmp(key, SVN_RA_NEON__PROP_CREATIONDATE) == 0)
293 name = SVN_PROP_ENTRY_COMMITTED_DATE;
294 else if (strcmp(key, SVN_RA_NEON__PROP_CREATOR_DISPLAYNAME) == 0)
295 name = SVN_PROP_ENTRY_LAST_AUTHOR;
296 else if (strcmp(key, SVN_RA_NEON__PROP_REPOSITORY_UUID) == 0)
297 name = SVN_PROP_ENTRY_UUID;
299 /* If we got a name we care about it, call the setter function. */
300 if (name)
301 SVN_ERR((*setter)(baton, name, val, pool));
303 return SVN_NO_ERROR;
307 static svn_error_t *add_props(apr_hash_t *props,
308 prop_setter_t setter,
309 void *baton,
310 apr_pool_t *pool)
312 apr_hash_index_t *hi;
314 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
316 const void *vkey;
317 void *vval;
318 const char *key;
319 const svn_string_t *val;
321 apr_hash_this(hi, &vkey, NULL, &vval);
322 key = vkey;
323 val = vval;
325 #define NSLEN (sizeof(SVN_DAV_PROP_NS_CUSTOM) - 1)
326 if (strncmp(key, SVN_DAV_PROP_NS_CUSTOM, NSLEN) == 0)
328 /* for props in the 'custom' namespace, we strip the
329 namespace and just use whatever name the user gave the
330 property. */
331 SVN_ERR((*setter)(baton, key + NSLEN, val, pool));
332 continue;
334 #undef NSLEN
336 #define NSLEN (sizeof(SVN_DAV_PROP_NS_SVN) - 1)
337 if (strncmp(key, SVN_DAV_PROP_NS_SVN, NSLEN) == 0)
339 /* This property is an 'svn:' prop, recognized by client, or
340 server, or both. Convert the URI namespace into normal
341 'svn:' prefix again before pushing it at the wc. */
342 SVN_ERR((*setter)(baton, apr_pstrcat(pool, SVN_PROP_PREFIX,
343 key + NSLEN, NULL),
344 val, pool));
346 #undef NSLEN
348 else
350 /* If we get here, then we have a property that is neither
351 in the 'custom' space, nor in the 'svn' space. So it
352 must be either in the 'network' space or 'DAV:' space.
353 The following routine converts a handful of DAV: props
354 into 'svn:wc:' or 'svn:entry:' props that libsvn_wc
355 wants. */
356 SVN_ERR(set_special_wc_prop(key, val, setter, baton, pool));
359 return SVN_NO_ERROR;
363 static svn_error_t *custom_get_request(svn_ra_neon__session_t *ras,
364 const char *url,
365 const char *relpath,
366 svn_ra_neon__block_reader reader,
367 void *subctx,
368 svn_ra_get_wc_prop_func_t get_wc_prop,
369 void *cb_baton,
370 svn_boolean_t use_base,
371 apr_pool_t *pool)
373 custom_get_ctx_t cgc = { 0 };
374 const char *delta_base;
375 svn_ra_neon__request_t *request;
376 svn_error_t *err;
378 if (use_base)
380 /* See if we can get a version URL for this resource. This will
381 refer to what we already have in the working copy, thus we
382 can get a diff against this particular resource. */
383 SVN_ERR(get_delta_base(&delta_base, relpath,
384 get_wc_prop, cb_baton, pool));
386 else
388 delta_base = NULL;
391 request = svn_ra_neon__request_create(ras, "GET", url, pool);
393 if (delta_base)
395 /* The HTTP delta draft uses an If-None-Match header holding an
396 entity tag corresponding to the copy we have. It is much more
397 natural for us to use a version URL to specify what we have.
398 Thus, we want to use the If: header to specify the URL. But
399 mod_dav sees all "State-token" items as lock tokens. When we
400 get mod_dav updated and the backend APIs expanded, then we
401 can switch to using the If: header. For now, use a custom
402 header to specify the version resource to use as the base. */
403 ne_add_request_header(request->ne_req,
404 SVN_DAV_DELTA_BASE_HEADER, delta_base);
407 svn_ra_neon__add_response_body_reader(request, ne_accept_2xx, reader, &cgc);
409 /* complete initialization of the body reading context */
410 cgc.req = request;
411 cgc.subctx = subctx;
413 /* run the request */
414 err = svn_ra_neon__request_dispatch(NULL, request, NULL, NULL,
415 200 /* OK */,
416 226 /* IM Used */,
417 pool);
418 svn_ra_neon__request_destroy(request);
420 /* The request runner raises internal errors before Neon errors,
421 pass a returned error to our callers */
423 return err;
426 /* This implements the svn_ra_neon__block_reader() callback interface. */
427 static svn_error_t *
428 fetch_file_reader(void *userdata, const char *buf, size_t len)
430 custom_get_ctx_t *cgc = userdata;
431 file_read_ctx_t *frc = cgc->subctx;
433 if (len == 0)
435 /* file is complete. */
436 return 0;
439 if (!cgc->checked_type)
441 ne_content_type ctype = { 0 };
442 int rv = ne_get_content_type(cgc->req->ne_req, &ctype);
444 if (rv != 0)
445 return
446 svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
447 _("Could not get content-type from response"));
449 /* Neon guarantees non-NULL values when rv==0 */
450 if (!strcmp(ctype.type, "application")
451 && !strcmp(ctype.subtype, "vnd.svn-svndiff"))
453 /* we are receiving an svndiff. set things up. */
454 frc->stream = svn_txdelta_parse_svndiff(frc->handler,
455 frc->handler_baton,
456 TRUE,
457 frc->pool);
460 if (ctype.value)
461 free(ctype.value);
463 cgc->checked_type = 1;
466 if (frc->stream == NULL)
468 /* receiving plain text. construct a window for it. */
470 svn_txdelta_window_t window = { 0 };
471 svn_txdelta_op_t op;
472 svn_string_t data;
474 data.data = buf;
475 data.len = len;
477 op.action_code = svn_txdelta_new;
478 op.offset = 0;
479 op.length = len;
481 window.tview_len = len; /* result will be this long */
482 window.num_ops = 1;
483 window.ops = &op;
484 window.new_data = &data;
486 /* We can't really do anything useful if we get an error here. Pass
487 it off to someone who can. */
488 SVN_RA_NEON__REQ_ERR
489 (cgc->req,
490 (*frc->handler)(&window, frc->handler_baton));
492 else
494 /* receiving svndiff. feed it to the svndiff parser. */
496 apr_size_t written = len;
498 SVN_ERR(svn_stream_write(frc->stream, buf, &written));
500 /* ### the svndiff stream parser does not obey svn_stream semantics
501 ### in its write handler. it does not output the number of bytes
502 ### consumed by the handler. specifically, it may decrement the
503 ### number by 4 for the header, then never touch it again. that
504 ### makes it appear like an incomplete write.
505 ### disable this check for now. the svndiff parser actually does
506 ### consume all bytes, all the time.
508 #if 0
509 if (written != len && cgc->err == NULL)
510 cgc->err = svn_error_createf(SVN_ERR_INCOMPLETE_DATA, NULL,
511 "Unable to completely write the svndiff "
512 "data to the parser stream "
513 "(wrote " APR_SIZE_T_FMT " "
514 "of " APR_SIZE_T_FMT " bytes)",
515 written, len);
516 #endif
519 return 0;
522 static svn_error_t *simple_fetch_file(svn_ra_neon__session_t *ras,
523 const char *url,
524 const char *relpath,
525 svn_boolean_t text_deltas,
526 void *file_baton,
527 const char *base_checksum,
528 const svn_delta_editor_t *editor,
529 svn_ra_get_wc_prop_func_t get_wc_prop,
530 void *cb_baton,
531 apr_pool_t *pool)
533 file_read_ctx_t frc = { 0 };
535 SVN_ERR_W((*editor->apply_textdelta)(file_baton,
536 base_checksum,
537 pool,
538 &frc.handler,
539 &frc.handler_baton),
540 _("Could not save file"));
542 /* Only bother with text-deltas if our caller cares. */
543 if (! text_deltas)
545 SVN_ERR((*frc.handler)(NULL, frc.handler_baton));
546 return SVN_NO_ERROR;
549 frc.pool = pool;
551 SVN_ERR(custom_get_request(ras, url, relpath,
552 fetch_file_reader, &frc,
553 get_wc_prop, cb_baton,
554 TRUE, pool));
556 /* close the handler, since the file reading completed successfully. */
557 SVN_ERR((*frc.handler)(NULL, frc.handler_baton));
559 return SVN_NO_ERROR;
562 /* Helper for svn_ra_neon__get_file. This implements
563 the svn_ra_neon__block_reader() callback interface. */
564 static svn_error_t *
565 get_file_reader(void *userdata, const char *buf, size_t len)
567 custom_get_ctx_t *cgc = userdata;
569 /* The stream we want to push data at. */
570 file_write_ctx_t *fwc = cgc->subctx;
571 svn_stream_t *stream = fwc->stream;
573 if (fwc->do_checksum)
574 apr_md5_update(&(fwc->md5_context), buf, len);
576 /* Write however many bytes were passed in by neon. */
577 SVN_ERR(svn_stream_write(stream, buf, &len));
579 return SVN_NO_ERROR;
583 /* minor helper for svn_ra_neon__get_file, of type prop_setter_t */
584 static svn_error_t *
585 add_prop_to_hash(void *baton,
586 const char *name,
587 const svn_string_t *value,
588 apr_pool_t *pool)
590 apr_hash_t *ht = (apr_hash_t *) baton;
591 apr_hash_set(ht, name, APR_HASH_KEY_STRING, value);
592 return SVN_NO_ERROR;
596 /* Helper for svn_ra_neon__get_file(), svn_ra_neon__get_dir(), and
597 svn_ra_neon__rev_proplist().
599 Loop over the properties in RSRC->propset, examining namespaces and
600 such to filter Subversion, custom, etc. properties.
602 User-visible props get added to the PROPS hash (alloced in POOL).
604 If ADD_ENTRY_PROPS is true, then "special" working copy entry-props
605 are added to the hash by set_special_wc_prop().
607 static svn_error_t *
608 filter_props(apr_hash_t *props,
609 svn_ra_neon__resource_t *rsrc,
610 svn_boolean_t add_entry_props,
611 apr_pool_t *pool)
613 apr_hash_index_t *hi;
615 for (hi = apr_hash_first(pool, rsrc->propset); hi; hi = apr_hash_next(hi))
617 const void *key;
618 const char *name;
619 void *val;
620 const svn_string_t *value;
622 apr_hash_this(hi, &key, NULL, &val);
623 name = key;
624 value = svn_string_dup(val, pool);
626 /* If the property is in the 'custom' namespace, then it's a
627 normal user-controlled property coming from the fs. Just
628 strip off this prefix and add to the hash. */
629 #define NSLEN (sizeof(SVN_DAV_PROP_NS_CUSTOM) - 1)
630 if (strncmp(name, SVN_DAV_PROP_NS_CUSTOM, NSLEN) == 0)
632 apr_hash_set(props, name + NSLEN, APR_HASH_KEY_STRING, value);
633 continue;
635 #undef NSLEN
637 /* If the property is in the 'svn' namespace, then it's a
638 normal user-controlled property coming from the fs. Just
639 strip off the URI prefix, add an 'svn:', and add to the hash. */
640 #define NSLEN (sizeof(SVN_DAV_PROP_NS_SVN) - 1)
641 if (strncmp(name, SVN_DAV_PROP_NS_SVN, NSLEN) == 0)
643 apr_hash_set(props,
644 apr_pstrcat(pool, SVN_PROP_PREFIX, name + NSLEN, NULL),
645 APR_HASH_KEY_STRING,
646 value);
647 continue;
649 #undef NSLEN
650 else if (strcmp(name, SVN_RA_NEON__PROP_CHECKED_IN) == 0)
652 /* For files, we currently only have one 'wc' prop. */
653 apr_hash_set(props, SVN_RA_NEON__LP_VSN_URL,
654 APR_HASH_KEY_STRING, value);
656 else
658 /* If it's one of the 'entry' props, this func will
659 recognize the DAV: name & add it to the hash mapped to a
660 new name recognized by libsvn_wc. */
661 if (add_entry_props)
662 SVN_ERR(set_special_wc_prop(name, value, add_prop_to_hash,
663 props, pool));
667 return SVN_NO_ERROR;
670 svn_error_t *svn_ra_neon__get_file(svn_ra_session_t *session,
671 const char *path,
672 svn_revnum_t revision,
673 svn_stream_t *stream,
674 svn_revnum_t *fetched_rev,
675 apr_hash_t **props,
676 apr_pool_t *pool)
678 svn_ra_neon__resource_t *rsrc;
679 const char *final_url;
680 svn_ra_neon__session_t *ras = session->priv;
681 const char *url = svn_path_url_add_component(ras->url->data, path, pool);
683 /* If the revision is invalid (head), then we're done. Just fetch
684 the public URL, because that will always get HEAD. */
685 if ((! SVN_IS_VALID_REVNUM(revision)) && (fetched_rev == NULL))
686 final_url = url;
688 /* If the revision is something specific, we need to create a bc_url. */
689 else
691 svn_revnum_t got_rev;
692 svn_string_t bc_url, bc_relative;
694 SVN_ERR(svn_ra_neon__get_baseline_info(NULL,
695 &bc_url, &bc_relative,
696 &got_rev,
697 ras,
698 url, revision,
699 pool));
700 final_url = svn_path_url_add_component(bc_url.data,
701 bc_relative.data,
702 pool);
703 if (fetched_rev != NULL)
704 *fetched_rev = got_rev;
707 if (stream)
709 svn_error_t *err;
710 const svn_string_t *expected_checksum = NULL;
711 file_write_ctx_t fwc;
712 ne_propname md5_propname = { SVN_DAV_PROP_NS_DAV, "md5-checksum" };
713 unsigned char digest[APR_MD5_DIGESTSIZE];
714 const char *hex_digest;
716 /* Only request a checksum if we're getting the file contents. */
717 /* ### We should arrange for the checksum to be returned in the
718 svn_ra_neon__get_baseline_info() call above; that will prevent
719 the extra round trip, at least some of the time. */
720 err = svn_ra_neon__get_one_prop(&expected_checksum,
721 ras,
722 final_url,
723 NULL,
724 &md5_propname,
725 pool);
727 /* Older servers don't serve this prop, but that's okay. */
728 /* ### temporary hack for 0.17. if the server doesn't have the prop,
729 ### then __get_one_prop returns an empty string. deal with it. */
730 if ((err && (err->apr_err == SVN_ERR_RA_DAV_PROPS_NOT_FOUND))
731 || (expected_checksum && (*expected_checksum->data == '\0')))
733 fwc.do_checksum = FALSE;
734 svn_error_clear(err);
736 else if (err)
737 return err;
738 else
739 fwc.do_checksum = TRUE;
741 fwc.stream = stream;
743 if (fwc.do_checksum)
744 apr_md5_init(&(fwc.md5_context));
746 /* Fetch the file, shoving it at the provided stream. */
747 SVN_ERR(custom_get_request(ras, final_url, path,
748 get_file_reader, &fwc,
749 ras->callbacks->get_wc_prop,
750 ras->callback_baton,
751 FALSE, pool));
753 if (fwc.do_checksum)
755 apr_md5_final(digest, &(fwc.md5_context));
756 hex_digest = svn_md5_digest_to_cstring_display(digest, pool);
758 if (strcmp(hex_digest, expected_checksum->data) != 0)
759 return svn_error_createf
760 (SVN_ERR_CHECKSUM_MISMATCH, NULL,
761 _("Checksum mismatch for '%s':\n"
762 " expected checksum: %s\n"
763 " actual checksum: %s\n"),
764 path, expected_checksum->data, hex_digest);
768 if (props)
770 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc, ras, final_url,
771 NULL, NULL /* all props */,
772 pool));
773 *props = apr_hash_make(pool);
774 SVN_ERR(filter_props(*props, rsrc, TRUE, pool));
777 return SVN_NO_ERROR;
780 /* The property we need to fetch to see whether the server we are
781 connected to supports the deadprop-count property. */
782 static const ne_propname deadprop_count_support_props[] =
784 { SVN_DAV_PROP_NS_DAV, "deadprop-count" },
785 { NULL }
788 svn_error_t *svn_ra_neon__get_dir(svn_ra_session_t *session,
789 apr_hash_t **dirents,
790 svn_revnum_t *fetched_rev,
791 apr_hash_t **props,
792 const char *path,
793 svn_revnum_t revision,
794 apr_uint32_t dirent_fields,
795 apr_pool_t *pool)
797 svn_ra_neon__resource_t *rsrc;
798 apr_hash_index_t *hi;
799 apr_hash_t *resources;
800 const char *final_url;
801 apr_size_t final_url_n_components;
802 svn_boolean_t supports_deadprop_count;
803 svn_ra_neon__session_t *ras = session->priv;
804 const char *url = svn_path_url_add_component(ras->url->data, path, pool);
806 /* If the revision is invalid (head), then we're done. Just fetch
807 the public URL, because that will always get HEAD. */
808 if ((! SVN_IS_VALID_REVNUM(revision)) && (fetched_rev == NULL))
809 final_url = url;
811 /* If the revision is something specific, we need to create a bc_url. */
812 else
814 svn_revnum_t got_rev;
815 svn_string_t bc_url, bc_relative;
817 SVN_ERR(svn_ra_neon__get_baseline_info(NULL,
818 &bc_url, &bc_relative,
819 &got_rev,
820 ras,
821 url, revision,
822 pool));
823 final_url = svn_path_url_add_component(bc_url.data,
824 bc_relative.data,
825 pool);
826 if (fetched_rev != NULL)
827 *fetched_rev = got_rev;
830 /* For issue 2151: See if we are dealing with a server that
831 understands the deadprop-count property. If it doesn't, we'll
832 need to do an allprop PROPFIND. If it does, we'll execute a more
833 targeted PROPFIND. */
835 const svn_string_t *deadprop_count;
837 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc, ras,
838 final_url, NULL,
839 deadprop_count_support_props,
840 pool));
842 deadprop_count = apr_hash_get(rsrc->propset,
843 SVN_RA_NEON__PROP_DEADPROP_COUNT,
844 APR_HASH_KEY_STRING);
846 supports_deadprop_count = (deadprop_count != NULL);
849 if (dirents)
851 ne_propname *which_props;
853 /* if we didn't ask for the has_props field, we can get individual
854 properties. */
855 if ((SVN_DIRENT_HAS_PROPS & dirent_fields) == 0
856 || supports_deadprop_count)
858 int num_props = 1; /* start with one for the final NULL */
860 if (dirent_fields & SVN_DIRENT_KIND)
861 ++num_props;
863 if (dirent_fields & SVN_DIRENT_SIZE)
864 ++num_props;
866 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
867 ++num_props;
869 if (dirent_fields & SVN_DIRENT_CREATED_REV)
870 ++num_props;
872 if (dirent_fields & SVN_DIRENT_TIME)
873 ++num_props;
875 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
876 ++num_props;
878 which_props = apr_pcalloc(pool, num_props * sizeof(ne_propname));
880 --num_props; /* damn zero based arrays... */
882 /* first, null out the end... */
883 which_props[num_props].nspace = NULL;
884 which_props[num_props--].name = NULL;
886 /* Now, go through and fill in the ones we care about, moving along
887 the array as we go. */
889 if (dirent_fields & SVN_DIRENT_KIND)
891 which_props[num_props].nspace = "DAV:";
892 which_props[num_props--].name = "resourcetype";
895 if (dirent_fields & SVN_DIRENT_SIZE)
897 which_props[num_props].nspace = "DAV:";
898 which_props[num_props--].name = "getcontentlength";
901 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
903 which_props[num_props].nspace = SVN_DAV_PROP_NS_DAV;
904 which_props[num_props--].name = "deadprop-count";
907 if (dirent_fields & SVN_DIRENT_CREATED_REV)
909 which_props[num_props].nspace = "DAV:";
910 which_props[num_props--].name = "version-name";
913 if (dirent_fields & SVN_DIRENT_TIME)
915 which_props[num_props].nspace = "DAV:";
916 which_props[num_props--].name = SVN_DAV__CREATIONDATE;
919 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
921 which_props[num_props].nspace = "DAV:";
922 which_props[num_props--].name = "creator-displayname";
925 assert(num_props == -1);
927 else
929 /* get all props, since we need them all to do has_props */
930 which_props = NULL;
933 /* Just like Nautilus, Cadaver, or any other browser, we do a
934 PROPFIND on the directory of depth 1. */
935 SVN_ERR(svn_ra_neon__get_props(&resources, ras,
936 final_url, SVN_RA_NEON__DEPTH_ONE,
937 NULL, which_props, pool));
939 /* Count the number of path components in final_url. */
940 final_url_n_components = svn_path_component_count(final_url);
942 /* Now we have a hash that maps a bunch of url children to resource
943 objects. Each resource object contains the properties of the
944 child. Parse these resources into svn_dirent_t structs. */
945 *dirents = apr_hash_make(pool);
946 for (hi = apr_hash_first(pool, resources);
948 hi = apr_hash_next(hi))
950 const void *key;
951 void *val;
952 const char *childname;
953 svn_ra_neon__resource_t *resource;
954 const svn_string_t *propval;
955 apr_hash_index_t *h;
956 svn_dirent_t *entry;
958 apr_hash_this(hi, &key, NULL, &val);
959 childname = key;
960 resource = val;
962 /* Skip the effective '.' entry that comes back from
963 SVN_RA_NEON__DEPTH_ONE. The children must have one more
964 component then final_url.
965 Note that we can't just strcmp the URLs because of URL encoding
966 differences (i.e. %3c vs. %3C etc.) */
967 if (svn_path_component_count(childname) == final_url_n_components)
968 continue;
970 entry = apr_pcalloc(pool, sizeof(*entry));
972 if (dirent_fields & SVN_DIRENT_KIND)
974 /* node kind */
975 entry->kind = resource->is_collection ? svn_node_dir
976 : svn_node_file;
979 if (dirent_fields & SVN_DIRENT_SIZE)
981 /* size */
982 propval = apr_hash_get(resource->propset,
983 SVN_RA_NEON__PROP_GETCONTENTLENGTH,
984 APR_HASH_KEY_STRING);
985 if (propval == NULL)
986 entry->size = 0;
987 else
988 entry->size = svn__atoui64(propval->data);
991 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
993 /* Does this resource contain any 'svn' or 'custom'
994 properties (e.g. ones actually created and set by the
995 user)? */
996 if (supports_deadprop_count)
998 propval = apr_hash_get(resource->propset,
999 SVN_RA_NEON__PROP_DEADPROP_COUNT,
1000 APR_HASH_KEY_STRING);
1002 if (propval == NULL)
1004 /* we thought that the server supported the
1005 deadprop-count property. apparently not. */
1006 return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
1007 _("Server response missing the "
1008 "expected deadprop-count "
1009 "property"));
1011 else
1013 apr_int64_t prop_count = svn__atoui64(propval->data);
1014 entry->has_props = (prop_count > 0);
1017 else
1019 /* The server doesn't support the deadprop_count prop,
1020 fallback */
1021 for (h = apr_hash_first(pool, resource->propset);
1022 h; h = apr_hash_next(h))
1024 const void *kkey;
1025 apr_hash_this(h, &kkey, NULL, NULL);
1027 if (strncmp((const char *) kkey, SVN_DAV_PROP_NS_CUSTOM,
1028 sizeof(SVN_DAV_PROP_NS_CUSTOM) - 1) == 0
1029 || strncmp((const char *) kkey, SVN_DAV_PROP_NS_SVN,
1030 sizeof(SVN_DAV_PROP_NS_SVN) - 1) == 0)
1031 entry->has_props = TRUE;
1036 if (dirent_fields & SVN_DIRENT_CREATED_REV)
1038 /* created_rev & friends */
1039 propval = apr_hash_get(resource->propset,
1040 SVN_RA_NEON__PROP_VERSION_NAME,
1041 APR_HASH_KEY_STRING);
1042 if (propval != NULL)
1043 entry->created_rev = SVN_STR_TO_REV(propval->data);
1046 if (dirent_fields & SVN_DIRENT_TIME)
1048 propval = apr_hash_get(resource->propset,
1049 SVN_RA_NEON__PROP_CREATIONDATE,
1050 APR_HASH_KEY_STRING);
1051 if (propval != NULL)
1052 SVN_ERR(svn_time_from_cstring(&(entry->time),
1053 propval->data, pool));
1056 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1058 propval = apr_hash_get(resource->propset,
1059 SVN_RA_NEON__PROP_CREATOR_DISPLAYNAME,
1060 APR_HASH_KEY_STRING);
1061 if (propval != NULL)
1062 entry->last_author = propval->data;
1065 apr_hash_set(*dirents,
1066 svn_path_uri_decode(svn_path_basename(childname, pool),
1067 pool),
1068 APR_HASH_KEY_STRING, entry);
1072 if (props)
1074 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc, ras, final_url,
1075 NULL, NULL /* all props */,
1076 pool));
1078 *props = apr_hash_make(pool);
1079 SVN_ERR(filter_props(*props, rsrc, TRUE, pool));
1082 return SVN_NO_ERROR;
1086 /* ------------------------------------------------------------------------- */
1088 svn_error_t *svn_ra_neon__get_latest_revnum(svn_ra_session_t *session,
1089 svn_revnum_t *latest_revnum,
1090 apr_pool_t *pool)
1092 svn_ra_neon__session_t *ras = session->priv;
1094 /* ### should we perform an OPTIONS to validate the server we're about
1095 ### to talk to? */
1097 /* we don't need any of the baseline URLs and stuff, but this does
1098 give us the latest revision number */
1099 SVN_ERR(svn_ra_neon__get_baseline_info(NULL, NULL, NULL, latest_revnum,
1100 ras, ras->root.path,
1101 SVN_INVALID_REVNUM, pool));
1103 SVN_ERR(svn_ra_neon__maybe_store_auth_info(ras, pool));
1105 return NULL;
1108 /* ------------------------------------------------------------------------- */
1111 svn_error_t *svn_ra_neon__change_rev_prop(svn_ra_session_t *session,
1112 svn_revnum_t rev,
1113 const char *name,
1114 const svn_string_t *value,
1115 apr_pool_t *pool)
1117 svn_ra_neon__session_t *ras = session->priv;
1118 svn_ra_neon__resource_t *baseline;
1119 svn_error_t *err;
1120 apr_hash_t *prop_changes = NULL;
1121 apr_array_header_t *prop_deletes = NULL;
1122 static const ne_propname wanted_props[] =
1124 { "DAV:", "auto-version" },
1125 { NULL }
1128 /* Main objective: do a PROPPATCH (allprops) on a baseline object */
1130 /* ### A Word From Our Sponsor: see issue #916.
1132 Be it heretofore known that this Subversion behavior is
1133 officially in violation of WebDAV/DeltaV. DeltaV has *no*
1134 concept of unversioned properties, anywhere. If you proppatch
1135 something, some new version of *something* is created.
1137 In particular, we've decided that a 'baseline' maps to an svn
1138 revision; if we attempted to proppatch a baseline, a *normal*
1139 DeltaV server would do an auto-checkout, patch the working
1140 baseline, auto-checkin, and create a new baseline. But
1141 mod_dav_svn just changes the baseline destructively.
1144 /* Get the baseline resource. */
1145 SVN_ERR(svn_ra_neon__get_baseline_props(NULL, &baseline,
1146 ras,
1147 ras->url->data,
1148 rev,
1149 wanted_props, /* DAV:auto-version */
1150 pool));
1152 /* ### TODO: if we got back some value for the baseline's
1153 'DAV:auto-version' property, interpret it. We *don't* want
1154 to attempt the PROPPATCH if the deltaV server is going to do
1155 auto-versioning and create a new baseline! */
1157 if (value)
1159 prop_changes = apr_hash_make(pool);
1160 apr_hash_set(prop_changes, name, APR_HASH_KEY_STRING, value);
1162 else
1164 prop_deletes = apr_array_make(pool, 1, sizeof(const char *));
1165 APR_ARRAY_PUSH(prop_deletes, const char *) = name;
1168 err = svn_ra_neon__do_proppatch(ras, baseline->url, prop_changes,
1169 prop_deletes, NULL, pool);
1170 if (err)
1171 return
1172 svn_error_create
1173 (SVN_ERR_RA_DAV_REQUEST_FAILED, err,
1174 _("DAV request failed; it's possible that the repository's "
1175 "pre-revprop-change hook either failed or is non-existent"));
1177 return SVN_NO_ERROR;
1181 svn_error_t *svn_ra_neon__rev_proplist(svn_ra_session_t *session,
1182 svn_revnum_t rev,
1183 apr_hash_t **props,
1184 apr_pool_t *pool)
1186 svn_ra_neon__session_t *ras = session->priv;
1187 svn_ra_neon__resource_t *baseline;
1189 *props = apr_hash_make(pool);
1191 /* Main objective: do a PROPFIND (allprops) on a baseline object */
1192 SVN_ERR(svn_ra_neon__get_baseline_props(NULL, &baseline,
1193 ras,
1194 ras->url->data,
1195 rev,
1196 NULL, /* get ALL properties */
1197 pool));
1199 /* Build a new property hash, based on the one in the baseline
1200 resource. In particular, convert the xml-property-namespaces
1201 into ones that the client understands. Strip away the DAV:
1202 liveprops as well. */
1203 SVN_ERR(filter_props(*props, baseline, FALSE, pool));
1205 return SVN_NO_ERROR;
1209 svn_error_t *svn_ra_neon__rev_prop(svn_ra_session_t *session,
1210 svn_revnum_t rev,
1211 const char *name,
1212 svn_string_t **value,
1213 apr_pool_t *pool)
1215 apr_hash_t *props;
1217 /* We just call svn_ra_neon__rev_proplist() and filter its results here
1218 * because sending the property name to the server may create an error
1219 * if it has a colon in its name. While more costly this allows DAV
1220 * clients to still gain access to all the allowed property names.
1221 * See Issue #1807 for more details. */
1222 SVN_ERR(svn_ra_neon__rev_proplist(session, rev, &props, pool));
1224 *value = apr_hash_get(props, name, APR_HASH_KEY_STRING);
1226 return SVN_NO_ERROR;
1232 /* -------------------------------------------------------------------------
1234 ** UPDATE HANDLING
1236 ** ### docco...
1238 ** DTD of the update report:
1239 ** ### open/add file/dir. first child is always checked-in/href (vsn_url).
1240 ** ### next are subdir elems, possibly fetch-file, then fetch-prop.
1243 /* Determine whether we're receiving the expected XML response.
1244 Return CHILD when interested in receiving the child's contents
1245 or one of SVN_RA_NEON__XML_INVALID and SVN_RA_NEON__XML_DECLINE
1246 when respectively this is the incorrect response or
1247 the element (and its children) are uninteresting */
1248 static int validate_element(svn_ra_neon__xml_elmid parent,
1249 svn_ra_neon__xml_elmid child)
1251 /* We're being very strict with the validity of XML elements here. If
1252 something exists that we don't know about, then we might not update
1253 the client properly. We also make various assumptions in the element
1254 processing functions, and the strong validation enables those
1255 assumptions. */
1257 switch (parent)
1259 case ELEM_root:
1260 if (child == ELEM_update_report)
1261 return child;
1262 else
1263 return SVN_RA_NEON__XML_INVALID;
1265 case ELEM_update_report:
1266 if (child == ELEM_target_revision
1267 || child == ELEM_open_directory
1268 || child == ELEM_resource_walk)
1269 return child;
1270 else
1271 return SVN_RA_NEON__XML_INVALID;
1273 case ELEM_resource_walk:
1274 if (child == ELEM_resource)
1275 return child;
1276 else
1277 return SVN_RA_NEON__XML_INVALID;
1279 case ELEM_resource:
1280 if (child == ELEM_checked_in)
1281 return child;
1282 else
1283 return SVN_RA_NEON__XML_INVALID;
1285 case ELEM_open_directory:
1286 if (child == ELEM_absent_directory
1287 || child == ELEM_open_directory
1288 || child == ELEM_add_directory
1289 || child == ELEM_absent_file
1290 || child == ELEM_open_file
1291 || child == ELEM_add_file
1292 || child == ELEM_fetch_props
1293 || child == ELEM_set_prop
1294 || child == ELEM_remove_prop
1295 || child == ELEM_delete_entry
1296 || child == ELEM_SVN_prop
1297 || child == ELEM_checked_in)
1298 return child;
1299 else
1300 return SVN_RA_NEON__XML_INVALID;
1302 case ELEM_add_directory:
1303 if (child == ELEM_absent_directory
1304 || child == ELEM_add_directory
1305 || child == ELEM_absent_file
1306 || child == ELEM_add_file
1307 || child == ELEM_set_prop
1308 || child == ELEM_SVN_prop
1309 || child == ELEM_checked_in)
1310 return child;
1311 else
1312 return SVN_RA_NEON__XML_INVALID;
1314 case ELEM_open_file:
1315 if (child == ELEM_checked_in
1316 || child == ELEM_fetch_file
1317 || child == ELEM_SVN_prop
1318 || child == ELEM_txdelta
1319 || child == ELEM_fetch_props
1320 || child == ELEM_set_prop
1321 || child == ELEM_remove_prop)
1322 return child;
1323 else
1324 return SVN_RA_NEON__XML_INVALID;
1326 case ELEM_add_file:
1327 if (child == ELEM_checked_in
1328 || child == ELEM_txdelta
1329 || child == ELEM_set_prop
1330 || child == ELEM_SVN_prop)
1331 return child;
1332 else
1333 return SVN_RA_NEON__XML_INVALID;
1335 case ELEM_checked_in:
1336 if (child == ELEM_href)
1337 return child;
1338 else
1339 return SVN_RA_NEON__XML_INVALID;
1341 case ELEM_set_prop:
1342 /* Prop name is an attribute, prop value is CDATA, so no child elts. */
1343 return child;
1345 case ELEM_SVN_prop:
1346 /* if (child == ELEM_version_name
1347 || child == ELEM_creationdate
1348 || child == ELEM_creator_displayname
1349 || child == ELEM_md5_checksum
1350 || child == ELEM_repository_uuid
1351 || child == ELEM_remove_prop)
1352 return child;
1353 else
1354 return SVN_RA_NEON__XML_DECLINE;
1356 /* ### TODO: someday uncomment the block above, and make the
1357 else clause return NE_XML_IGNORE. But first, neon needs to
1358 define that value. :-) */
1359 return child;
1361 default:
1362 return SVN_RA_NEON__XML_DECLINE;
1365 /* NOTREACHED */
1368 static void push_dir(report_baton_t *rb,
1369 void *baton,
1370 svn_stringbuf_t *pathbuf,
1371 apr_pool_t *pool)
1373 dir_item_t *di = apr_array_push(rb->dirs);
1375 memset(di, 0, sizeof(*di));
1376 di->baton = baton;
1377 di->pathbuf = pathbuf;
1378 di->pool = pool;
1381 /* This implements the `ne_xml_startelm_cb' prototype. */
1382 static svn_error_t *
1383 start_element(int *elem, void *userdata, int parent, const char *nspace,
1384 const char *elt_name, const char **atts)
1386 report_baton_t *rb = userdata;
1387 const char *att;
1388 svn_revnum_t base;
1389 const char *name;
1390 const char *bc_url;
1391 svn_stringbuf_t *cpath = NULL;
1392 svn_revnum_t crev = SVN_INVALID_REVNUM;
1393 dir_item_t *parent_dir;
1394 void *new_dir_baton;
1395 svn_stringbuf_t *pathbuf;
1396 apr_pool_t *subpool;
1397 const char *base_checksum = NULL;
1398 const svn_ra_neon__xml_elm_t *elm;
1400 elm = svn_ra_neon__lookup_xml_elem(report_elements, nspace, elt_name);
1401 *elem = elm ? validate_element(parent, elm->id) : SVN_RA_NEON__XML_DECLINE;
1402 if (*elem < 1) /* not a valid element */
1403 return SVN_NO_ERROR;
1405 switch (elm->id)
1407 case ELEM_update_report:
1408 att = svn_xml_get_attr_value("send-all", atts);
1409 if (att && (strcmp(att, "true") == 0))
1410 rb->receiving_all = TRUE;
1411 break;
1413 case ELEM_target_revision:
1414 att = svn_xml_get_attr_value("rev", atts);
1415 if (att == NULL)
1416 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1417 _("Missing rev attr in target-revision"
1418 " element"));
1419 SVN_ERR((*rb->editor->set_target_revision)(rb->edit_baton,
1420 SVN_STR_TO_REV(att),
1421 rb->pool));
1422 break;
1424 case ELEM_absent_directory:
1425 name = svn_xml_get_attr_value("name", atts);
1426 if (name == NULL)
1427 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1428 _("Missing name attr in absent-directory"
1429 " element"));
1431 parent_dir = &TOP_DIR(rb);
1432 pathbuf = svn_stringbuf_dup(parent_dir->pathbuf, parent_dir->pool);
1433 svn_path_add_component(pathbuf, name);
1435 SVN_ERR((*rb->editor->absent_directory)(pathbuf->data,
1436 parent_dir->baton,
1437 parent_dir->pool));
1438 break;
1440 case ELEM_absent_file:
1441 name = svn_xml_get_attr_value("name", atts);
1442 if (name == NULL)
1443 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1444 _("Missing name attr in absent-file"
1445 " element"));
1446 parent_dir = &TOP_DIR(rb);
1447 pathbuf = svn_stringbuf_dup(parent_dir->pathbuf, parent_dir->pool);
1448 svn_path_add_component(pathbuf, name);
1450 SVN_ERR((*rb->editor->absent_file)(pathbuf->data,
1451 parent_dir->baton,
1452 parent_dir->pool));
1453 break;
1455 case ELEM_resource:
1456 att = svn_xml_get_attr_value("path", atts);
1457 if (att == NULL)
1458 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1459 _("Missing path attr in resource element"));
1460 svn_stringbuf_set(rb->current_wcprop_path, att);
1461 rb->in_resource = TRUE;
1462 break;
1464 case ELEM_open_directory:
1465 att = svn_xml_get_attr_value("rev", atts);
1466 if (att == NULL)
1467 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1468 _("Missing rev attr in open-directory"
1469 " element"));
1470 base = SVN_STR_TO_REV(att);
1472 if (DIR_DEPTH(rb) == 0)
1474 /* pathbuf has to live for the whole edit! */
1475 pathbuf = svn_stringbuf_create("", rb->pool);
1477 /* During switch operations, we need to invalidate the
1478 tree's version resource URLs in case something goes
1479 wrong. */
1480 if (rb->is_switch && rb->ras->callbacks->invalidate_wc_props)
1482 SVN_ERR(rb->ras->callbacks->invalidate_wc_props
1483 (rb->ras->callback_baton, rb->target,
1484 SVN_RA_NEON__LP_VSN_URL, rb->pool));
1487 subpool = svn_pool_create(rb->pool);
1488 SVN_ERR((*rb->editor->open_root)(rb->edit_baton, base,
1489 subpool, &new_dir_baton));
1491 /* push the new baton onto the directory baton stack */
1492 push_dir(rb, new_dir_baton, pathbuf, subpool);
1494 else
1496 name = svn_xml_get_attr_value("name", atts);
1497 if (name == NULL)
1498 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1499 _("Missing name attr in open-directory"
1500 " element"));
1501 svn_stringbuf_set(rb->namestr, name);
1503 parent_dir = &TOP_DIR(rb);
1504 subpool = svn_pool_create(parent_dir->pool);
1506 pathbuf = svn_stringbuf_dup(parent_dir->pathbuf, subpool);
1507 svn_path_add_component(pathbuf, rb->namestr->data);
1509 SVN_ERR((*rb->editor->open_directory)(pathbuf->data,
1510 parent_dir->baton, base,
1511 subpool,
1512 &new_dir_baton));
1514 /* push the new baton onto the directory baton stack */
1515 push_dir(rb, new_dir_baton, pathbuf, subpool);
1518 /* Property fetching is NOT implied in replacement. */
1519 TOP_DIR(rb).fetch_props = FALSE;
1520 break;
1522 case ELEM_add_directory:
1523 name = svn_xml_get_attr_value("name", atts);
1524 if (name == NULL)
1525 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1526 _("Missing name attr in add-directory"
1527 " element"));
1528 svn_stringbuf_set(rb->namestr, name);
1530 att = svn_xml_get_attr_value("copyfrom-path", atts);
1531 if (att != NULL)
1533 cpath = rb->cpathstr;
1534 svn_stringbuf_set(cpath, att);
1536 att = svn_xml_get_attr_value("copyfrom-rev", atts);
1537 if (att == NULL)
1538 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1539 _("Missing copyfrom-rev attr in"
1540 " add-directory element"));
1541 crev = SVN_STR_TO_REV(att);
1544 parent_dir = &TOP_DIR(rb);
1545 subpool = svn_pool_create(parent_dir->pool);
1547 pathbuf = svn_stringbuf_dup(parent_dir->pathbuf, subpool);
1548 svn_path_add_component(pathbuf, rb->namestr->data);
1550 SVN_ERR((*rb->editor->add_directory)(pathbuf->data, parent_dir->baton,
1551 cpath ? cpath->data : NULL,
1552 crev, subpool,
1553 &new_dir_baton));
1555 /* push the new baton onto the directory baton stack */
1556 push_dir(rb, new_dir_baton, pathbuf, subpool);
1558 /* Property fetching is implied in addition. This flag is only
1559 for parsing old-style reports; it is ignored when talking to
1560 a modern server. */
1561 TOP_DIR(rb).fetch_props = TRUE;
1563 bc_url = svn_xml_get_attr_value("bc-url", atts);
1565 /* In non-modern report responses, we're just told to fetch the
1566 props later. In that case, we can at least do a pre-emptive
1567 depth-1 propfind on the directory right now; this prevents
1568 individual propfinds on added-files later on, thus reducing
1569 the number of network turnarounds (though not by as much as
1570 simply getting a modern report response!). */
1571 if ((! rb->receiving_all) && bc_url)
1573 apr_hash_t *bc_children;
1574 SVN_ERR(svn_ra_neon__get_props(&bc_children,
1575 rb->ras,
1576 bc_url,
1577 SVN_RA_NEON__DEPTH_ONE,
1578 NULL, NULL /* allprops */,
1579 TOP_DIR(rb).pool));
1581 /* re-index the results into a more usable hash.
1582 bc_children maps bc-url->resource_t, but we want the
1583 dir_item_t's hash to map vc-url->resource_t. */
1584 if (bc_children)
1586 apr_hash_index_t *hi;
1587 TOP_DIR(rb).children = apr_hash_make(TOP_DIR(rb).pool);
1589 for (hi = apr_hash_first(TOP_DIR(rb).pool, bc_children);
1590 hi; hi = apr_hash_next(hi))
1592 void *val;
1593 svn_ra_neon__resource_t *rsrc;
1594 const svn_string_t *vc_url;
1596 apr_hash_this(hi, NULL, NULL, &val);
1597 rsrc = val;
1599 vc_url = apr_hash_get(rsrc->propset,
1600 SVN_RA_NEON__PROP_CHECKED_IN,
1601 APR_HASH_KEY_STRING);
1602 if (vc_url)
1603 apr_hash_set(TOP_DIR(rb).children,
1604 vc_url->data, vc_url->len,
1605 rsrc->propset);
1610 break;
1612 case ELEM_open_file:
1613 att = svn_xml_get_attr_value("rev", atts);
1614 if (att == NULL)
1615 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1616 _("Missing rev attr in open-file"
1617 " element"));
1618 base = SVN_STR_TO_REV(att);
1620 name = svn_xml_get_attr_value("name", atts);
1621 if (name == NULL)
1622 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1623 _("Missing name attr in open-file"
1624 " element"));
1625 svn_stringbuf_set(rb->namestr, name);
1627 parent_dir = &TOP_DIR(rb);
1628 rb->file_pool = svn_pool_create(parent_dir->pool);
1629 rb->result_checksum = NULL;
1631 /* Add this file's name into the directory's path buffer. It will be
1632 removed in end_element() */
1633 svn_path_add_component(parent_dir->pathbuf, rb->namestr->data);
1635 SVN_ERR((*rb->editor->open_file)(parent_dir->pathbuf->data,
1636 parent_dir->baton, base,
1637 rb->file_pool,
1638 &rb->file_baton));
1640 /* Property fetching is NOT implied in replacement. */
1641 rb->fetch_props = FALSE;
1643 break;
1645 case ELEM_add_file:
1646 name = svn_xml_get_attr_value("name", atts);
1647 if (name == NULL)
1648 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1649 _("Missing name attr in add-file"
1650 " element"));
1651 svn_stringbuf_set(rb->namestr, name);
1653 att = svn_xml_get_attr_value("copyfrom-path", atts);
1654 if (att != NULL)
1656 cpath = rb->cpathstr;
1657 svn_stringbuf_set(cpath, att);
1659 att = svn_xml_get_attr_value("copyfrom-rev", atts);
1660 if (att == NULL)
1661 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1662 _("Missing copyfrom-rev attr in add-file"
1663 " element"));
1664 crev = SVN_STR_TO_REV(att);
1667 parent_dir = &TOP_DIR(rb);
1668 rb->file_pool = svn_pool_create(parent_dir->pool);
1669 rb->result_checksum = NULL;
1671 /* Add this file's name into the directory's path buffer. It will be
1672 removed in end_element() */
1673 svn_path_add_component(parent_dir->pathbuf, rb->namestr->data);
1675 SVN_ERR((*rb->editor->add_file)(parent_dir->pathbuf->data,
1676 parent_dir->baton,
1677 cpath ? cpath->data : NULL,
1678 crev, rb->file_pool,
1679 &rb->file_baton));
1681 /* Property fetching is implied in addition. This flag is only
1682 for parsing old-style reports; it is ignored when talking to
1683 a modern server. */
1684 rb->fetch_props = TRUE;
1686 break;
1688 case ELEM_txdelta:
1689 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
1690 <fetch-file>s and such) when *not* in "send-all" mode. As a
1691 client, we're smart enough to know that's wrong, so when not
1692 in "receiving-all" mode, we'll ignore <txdelta> tags
1693 altogether. */
1694 if (! rb->receiving_all)
1695 break;
1697 SVN_ERR((*rb->editor->apply_textdelta)(rb->file_baton,
1698 NULL, /* ### base_checksum */
1699 rb->file_pool,
1700 &(rb->whandler),
1701 &(rb->whandler_baton)));
1703 rb->svndiff_decoder = svn_txdelta_parse_svndiff(rb->whandler,
1704 rb->whandler_baton,
1705 TRUE, rb->file_pool);
1707 rb->base64_decoder = svn_base64_decode(rb->svndiff_decoder,
1708 rb->file_pool);
1709 break;
1711 case ELEM_set_prop:
1713 const char *encoding = svn_xml_get_attr_value("encoding", atts);
1714 name = svn_xml_get_attr_value("name", atts);
1715 if (name == NULL)
1716 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1717 _("Missing name attr in set-prop element"));
1718 svn_stringbuf_set(rb->namestr, name);
1719 if (encoding)
1720 svn_stringbuf_set(rb->encoding, encoding);
1721 else
1722 svn_stringbuf_setempty(rb->encoding);
1725 break;
1727 case ELEM_remove_prop:
1728 name = svn_xml_get_attr_value("name", atts);
1729 if (name == NULL)
1730 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1731 _("Missing name attr in remove-prop element"));
1732 svn_stringbuf_set(rb->namestr, name);
1734 /* Removing a prop. */
1735 if (rb->file_baton == NULL)
1736 SVN_ERR(rb->editor->change_dir_prop(TOP_DIR(rb).baton,
1737 rb->namestr->data,
1738 NULL, TOP_DIR(rb).pool));
1739 else
1740 SVN_ERR(rb->editor->change_file_prop(rb->file_baton, rb->namestr->data,
1741 NULL, rb->file_pool));
1742 break;
1744 case ELEM_fetch_props:
1745 if (!rb->fetch_content)
1747 /* If this is just a status check, the specifics of the
1748 property change are uninteresting. Simply call our
1749 editor function with bogus data so it registers a
1750 property mod. */
1751 svn_stringbuf_set(rb->namestr, SVN_PROP_PREFIX "BOGOSITY");
1753 if (rb->file_baton == NULL)
1754 SVN_ERR(rb->editor->change_dir_prop(TOP_DIR(rb).baton,
1755 rb->namestr->data,
1756 NULL, TOP_DIR(rb).pool));
1757 else
1758 SVN_ERR(rb->editor->change_file_prop(rb->file_baton,
1759 rb->namestr->data,
1760 NULL, rb->file_pool));
1762 else
1764 /* Note that we need to fetch props for this... */
1765 if (rb->file_baton == NULL)
1766 TOP_DIR(rb).fetch_props = TRUE; /* ...directory. */
1767 else
1768 rb->fetch_props = TRUE; /* ...file. */
1770 break;
1772 case ELEM_fetch_file:
1773 base_checksum = svn_xml_get_attr_value("base-checksum", atts);
1774 rb->result_checksum = NULL;
1776 /* If we aren't expecting to see the file contents inline, we
1777 should ignore server requests to fetch them.
1779 ### This conditional was added to counteract a little bug in
1780 Subversion 0.33.0's mod_dav_svn whereby both the <txdelta>
1781 and <fetch-file> tags were being transmitted. Someday, we
1782 should remove the conditional again to give the server the
1783 option of sending inline text-deltas for some files while
1784 telling the client to fetch others. */
1785 if (! rb->receiving_all)
1787 /* assert: rb->href->len > 0 */
1788 SVN_ERR(simple_fetch_file(rb->ras,
1789 rb->href->data,
1790 TOP_DIR(rb).pathbuf->data,
1791 rb->fetch_content,
1792 rb->file_baton,
1793 base_checksum,
1794 rb->editor,
1795 rb->ras->callbacks->get_wc_prop,
1796 rb->ras->callback_baton,
1797 rb->file_pool));
1799 break;
1801 case ELEM_delete_entry:
1802 name = svn_xml_get_attr_value("name", atts);
1803 if (name == NULL)
1804 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1805 _("Missing name attr in delete-entry"
1806 " element"));
1807 svn_stringbuf_set(rb->namestr, name);
1809 parent_dir = &TOP_DIR(rb);
1811 /* Pool use is a little non-standard here. When lots of items in the
1812 same directory get deleted each one will trigger a call to
1813 editor->delete_entry, but we don't have a pool that readily fits
1814 the usual iteration pattern and so memory use could grow without
1815 bound (see issue 1635). To avoid such growth we use a temporary,
1816 short-lived, pool. */
1817 subpool = svn_pool_create(parent_dir->pool);
1819 pathbuf = svn_stringbuf_dup(parent_dir->pathbuf, subpool);
1820 svn_path_add_component(pathbuf, rb->namestr->data);
1822 SVN_ERR((*rb->editor->delete_entry)(pathbuf->data,
1823 SVN_INVALID_REVNUM,
1824 TOP_DIR(rb).baton,
1825 subpool));
1826 svn_pool_destroy(subpool);
1827 break;
1829 default:
1830 break;
1833 *elem = elm->id;
1835 return SVN_NO_ERROR;
1839 static svn_error_t *
1840 add_node_props(report_baton_t *rb, apr_pool_t *pool)
1842 svn_ra_neon__resource_t *rsrc = NULL;
1843 apr_hash_t *props = NULL;
1845 /* Do nothing if parsing a modern report, because the properties
1846 already come inline. */
1847 if (rb->receiving_all)
1848 return SVN_NO_ERROR;
1850 /* Do nothing if we aren't fetching content. */
1851 if (!rb->fetch_content)
1852 return SVN_NO_ERROR;
1854 if (rb->file_baton)
1856 if (! rb->fetch_props)
1857 return SVN_NO_ERROR;
1859 /* Check to see if your parent directory already has your props
1860 stored, possibly from a depth-1 propfind. Otherwise just do
1861 a propfind directly on the file url. */
1862 if ( ! ((TOP_DIR(rb).children)
1863 && (props = apr_hash_get(TOP_DIR(rb).children, rb->href->data,
1864 APR_HASH_KEY_STRING))) )
1866 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc,
1867 rb->ras,
1868 rb->href->data,
1869 NULL,
1870 NULL,
1871 pool));
1872 props = rsrc->propset;
1875 SVN_ERR(add_props(props,
1876 rb->editor->change_file_prop,
1877 rb->file_baton,
1878 pool));
1880 else
1882 if (! TOP_DIR(rb).fetch_props)
1883 return SVN_NO_ERROR;
1885 /* Check to see if your props are already stored, possibly from
1886 a depth-1 propfind. Otherwise just do a propfind directly on
1887 the directory url. */
1888 if ( ! ((TOP_DIR(rb).children)
1889 && (props = apr_hash_get(TOP_DIR(rb).children,
1890 TOP_DIR(rb).vsn_url,
1891 APR_HASH_KEY_STRING))) )
1893 SVN_ERR(svn_ra_neon__get_props_resource(&rsrc,
1894 rb->ras,
1895 TOP_DIR(rb).vsn_url,
1896 NULL,
1897 NULL,
1898 pool));
1899 props = rsrc->propset;
1902 SVN_ERR(add_props(props,
1903 rb->editor->change_dir_prop,
1904 TOP_DIR(rb).baton,
1905 pool));
1908 return SVN_NO_ERROR;
1911 /* This implements the `svn_ra_neon__cdata_cb_t' prototype. */
1912 static svn_error_t *
1913 cdata_handler(void *userdata, int state, const char *cdata, size_t len)
1915 report_baton_t *rb = userdata;
1917 switch(state)
1919 case ELEM_href:
1920 case ELEM_set_prop:
1921 case ELEM_md5_checksum:
1922 case ELEM_version_name:
1923 case ELEM_creationdate:
1924 case ELEM_creator_displayname:
1925 svn_stringbuf_appendbytes(rb->cdata_accum, cdata, len);
1926 break;
1928 case ELEM_txdelta:
1930 apr_size_t nlen = len;
1932 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
1933 <fetch-file>s and such) when *not* in "send-all" mode. As a
1934 client, we're smart enough to know that's wrong, so when not
1935 in "receiving-all" mode, we'll ignore <txdelta> tags
1936 altogether. */
1937 if (! rb->receiving_all)
1938 break;
1940 SVN_ERR(svn_stream_write(rb->base64_decoder, cdata, &nlen));
1941 if (nlen != len)
1943 /* Short write without associated error? "Can't happen." */
1944 return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
1945 _("Error writing to '%s': unexpected EOF"),
1946 svn_path_local_style(rb->namestr->data,
1947 rb->pool));
1950 break;
1953 return 0; /* no error */
1956 /* This implements the `svn_ra_neon_endelm_cb_t' prototype. */
1957 static svn_error_t *
1958 end_element(void *userdata, int state,
1959 const char *nspace, const char *elt_name)
1961 report_baton_t *rb = userdata;
1962 const svn_delta_editor_t *editor = rb->editor;
1963 const svn_ra_neon__xml_elm_t *elm;
1965 elm = svn_ra_neon__lookup_xml_elem(report_elements, nspace, elt_name);
1967 if (elm == NULL)
1968 return SVN_NO_ERROR;
1970 switch (elm->id)
1972 case ELEM_resource:
1973 rb->in_resource = FALSE;
1974 break;
1976 case ELEM_update_report:
1977 /* End of report; close up the editor. */
1978 SVN_ERR((*rb->editor->close_edit)(rb->edit_baton, rb->pool));
1979 rb->edit_baton = NULL;
1980 break;
1982 case ELEM_add_directory:
1983 case ELEM_open_directory:
1985 /* fetch node props, unless this is the top dir and the real
1986 target of the operation is not the top dir. */
1987 if (! ((DIR_DEPTH(rb) == 1) && *rb->target))
1988 SVN_ERR(add_node_props(rb, TOP_DIR(rb).pool));
1990 /* Close the directory on top of the stack, and pop it. Also,
1991 destroy the subpool used exclusive by this directory and its
1992 children. */
1993 SVN_ERR((*rb->editor->close_directory)(TOP_DIR(rb).baton,
1994 TOP_DIR(rb).pool));
1995 svn_pool_destroy(TOP_DIR(rb).pool);
1996 apr_array_pop(rb->dirs);
1997 break;
1999 case ELEM_add_file:
2000 /* we wait until the close element to do the work. this allows us to
2001 retrieve the href before fetching. */
2003 /* fetch file */
2004 if (! rb->receiving_all)
2006 SVN_ERR(simple_fetch_file(rb->ras,
2007 rb->href->data,
2008 TOP_DIR(rb).pathbuf->data,
2009 rb->fetch_content,
2010 rb->file_baton,
2011 NULL, /* no base checksum in an add */
2012 rb->editor,
2013 rb->ras->callbacks->get_wc_prop,
2014 rb->ras->callback_baton,
2015 rb->file_pool));
2017 /* fetch node props as necessary. */
2018 SVN_ERR(add_node_props(rb, rb->file_pool));
2021 /* close the file and mark that we are no longer operating on a file */
2022 SVN_ERR((*rb->editor->close_file)(rb->file_baton,
2023 rb->result_checksum,
2024 rb->file_pool));
2025 rb->file_baton = NULL;
2027 /* Yank this file out of the directory's path buffer. */
2028 svn_path_remove_component(TOP_DIR(rb).pathbuf);
2029 svn_pool_destroy(rb->file_pool);
2030 rb->file_pool = NULL;
2031 break;
2033 case ELEM_txdelta:
2034 /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
2035 <fetch-file>s and such) when *not* in "send-all" mode. As a
2036 client, we're smart enough to know that's wrong, so when not
2037 in "receiving-all" mode, we'll ignore <txdelta> tags
2038 altogether. */
2039 if (! rb->receiving_all)
2040 break;
2042 SVN_ERR(svn_stream_close(rb->base64_decoder));
2043 rb->whandler = NULL;
2044 rb->whandler_baton = NULL;
2045 rb->svndiff_decoder = NULL;
2046 rb->base64_decoder = NULL;
2047 break;
2049 case ELEM_open_file:
2050 /* fetch node props as necessary. */
2051 SVN_ERR(add_node_props(rb, rb->file_pool));
2053 /* close the file and mark that we are no longer operating on a file */
2054 SVN_ERR((*rb->editor->close_file)(rb->file_baton,
2055 rb->result_checksum,
2056 rb->file_pool));
2057 rb->file_baton = NULL;
2059 /* Yank this file out of the directory's path buffer. */
2060 svn_path_remove_component(TOP_DIR(rb).pathbuf);
2061 svn_pool_destroy(rb->file_pool);
2062 rb->file_pool = NULL;
2063 break;
2065 case ELEM_set_prop:
2067 svn_string_t decoded_value;
2068 const svn_string_t *decoded_value_p;
2069 apr_pool_t *pool;
2071 if (rb->file_baton)
2072 pool = rb->file_pool;
2073 else
2074 pool = TOP_DIR(rb).pool;
2076 decoded_value.data = rb->cdata_accum->data;
2077 decoded_value.len = rb->cdata_accum->len;
2079 /* Determine the cdata encoding, if any. */
2080 if (svn_stringbuf_isempty(rb->encoding))
2082 decoded_value_p = &decoded_value;
2084 else if (strcmp(rb->encoding->data, "base64") == 0)
2086 decoded_value_p = svn_base64_decode_string(&decoded_value, pool);
2087 svn_stringbuf_setempty(rb->encoding);
2089 else
2091 SVN_ERR(svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING, NULL,
2092 _("Unknown XML encoding: '%s'"),
2093 rb->encoding->data));
2094 abort(); /* Not reached. */
2097 /* Set the prop. */
2098 if (rb->file_baton)
2100 SVN_ERR(rb->editor->change_file_prop(rb->file_baton,
2101 rb->namestr->data,
2102 decoded_value_p, pool));
2104 else
2106 SVN_ERR(rb->editor->change_dir_prop(TOP_DIR(rb).baton,
2107 rb->namestr->data,
2108 decoded_value_p, pool));
2112 svn_stringbuf_setempty(rb->cdata_accum);
2113 break;
2115 case ELEM_href:
2116 if (rb->fetch_content)
2117 /* record the href that we just found */
2118 SVN_ERR(svn_ra_neon__copy_href(rb->href, rb->cdata_accum->data,
2119 rb->scratch_pool));
2121 svn_stringbuf_setempty(rb->cdata_accum);
2123 /* do nothing if we aren't fetching content. */
2124 if (!rb->fetch_content)
2125 break;
2127 /* if we're within a <resource> tag, then just call the generic
2128 RA set_wcprop_callback directly; no need to use the
2129 update-editor. */
2130 if (rb->in_resource)
2132 svn_string_t href_val;
2133 href_val.data = rb->href->data;
2134 href_val.len = rb->href->len;
2136 if (rb->ras->callbacks->set_wc_prop != NULL)
2137 SVN_ERR(rb->ras->callbacks->set_wc_prop
2138 (rb->ras->callback_baton,
2139 rb->current_wcprop_path->data,
2140 SVN_RA_NEON__LP_VSN_URL,
2141 &href_val,
2142 rb->scratch_pool));
2143 svn_pool_clear(rb->scratch_pool);
2145 /* else we're setting a wcprop in the context of an editor drive. */
2146 else if (rb->file_baton == NULL)
2148 /* Update the wcprop here, unless this is the top directory
2149 and the real target of this operation is something other
2150 than the top directory. */
2151 if (! ((DIR_DEPTH(rb) == 1) && *rb->target))
2153 SVN_ERR(simple_store_vsn_url(rb->href->data, TOP_DIR(rb).baton,
2154 rb->editor->change_dir_prop,
2155 TOP_DIR(rb).pool));
2157 /* save away the URL in case a fetch-props arrives after all of
2158 the subdir processing. we will need this copy of the URL to
2159 fetch the properties (i.e. rb->href will be toast by then). */
2160 TOP_DIR(rb).vsn_url = apr_pmemdup(TOP_DIR(rb).pool,
2161 rb->href->data,
2162 rb->href->len + 1);
2165 else
2167 SVN_ERR(simple_store_vsn_url(rb->href->data, rb->file_baton,
2168 rb->editor->change_file_prop,
2169 rb->file_pool));
2171 break;
2173 case ELEM_md5_checksum:
2174 /* We only care about file checksums. */
2175 if (rb->file_baton)
2177 rb->result_checksum = apr_pstrdup(rb->file_pool,
2178 rb->cdata_accum->data);
2180 svn_stringbuf_setempty(rb->cdata_accum);
2181 break;
2183 case ELEM_version_name:
2184 case ELEM_creationdate:
2185 case ELEM_creator_displayname:
2187 /* The name of the xml tag is the property that we want to set. */
2188 apr_pool_t *pool =
2189 rb->file_baton ? rb->file_pool : TOP_DIR(rb).pool;
2190 prop_setter_t setter =
2191 rb->file_baton ? editor->change_file_prop : editor->change_dir_prop;
2192 const char *name = apr_pstrcat(pool, elm->nspace, elm->name, NULL);
2193 void *baton = rb->file_baton ? rb->file_baton : TOP_DIR(rb).baton;
2194 svn_string_t valstr;
2196 valstr.data = rb->cdata_accum->data;
2197 valstr.len = rb->cdata_accum->len;
2198 SVN_ERR(set_special_wc_prop(name, &valstr, setter, baton, pool));
2199 svn_stringbuf_setempty(rb->cdata_accum);
2201 break;
2203 default:
2204 break;
2207 return SVN_NO_ERROR;
2211 static svn_error_t * reporter_set_path(void *report_baton,
2212 const char *path,
2213 svn_revnum_t revision,
2214 svn_depth_t depth,
2215 svn_boolean_t start_empty,
2216 const char *lock_token,
2217 apr_pool_t *pool)
2219 report_baton_t *rb = report_baton;
2220 const char *entry;
2221 svn_stringbuf_t *qpath = NULL;
2222 const char *tokenstring = "";
2223 const char *depthstring = apr_psprintf(pool, "depth=\"%s\"",
2224 svn_depth_to_word(depth));
2226 if (lock_token)
2227 tokenstring = apr_psprintf(pool, "lock-token=\"%s\"", lock_token);
2229 svn_xml_escape_cdata_cstring(&qpath, path, pool);
2230 if (start_empty)
2231 entry = apr_psprintf(pool,
2232 "<S:entry rev=\"%ld\" %s %s"
2233 " start-empty=\"true\">%s</S:entry>" DEBUG_CR,
2234 revision, depthstring, tokenstring, qpath->data);
2235 else
2236 entry = apr_psprintf(pool,
2237 "<S:entry rev=\"%ld\" %s %s>"
2238 "%s</S:entry>" DEBUG_CR,
2239 revision, depthstring, tokenstring, qpath->data);
2241 return svn_io_file_write_full(rb->tmpfile, entry, strlen(entry), NULL, pool);
2245 static svn_error_t * reporter_link_path(void *report_baton,
2246 const char *path,
2247 const char *url,
2248 svn_revnum_t revision,
2249 svn_depth_t depth,
2250 svn_boolean_t start_empty,
2251 const char *lock_token,
2252 apr_pool_t *pool)
2254 report_baton_t *rb = report_baton;
2255 const char *entry;
2256 svn_stringbuf_t *qpath = NULL, *qlinkpath = NULL;
2257 svn_string_t bc_relative;
2258 const char *tokenstring = "";
2259 const char *depthstring = apr_psprintf(pool, "depth=\"%s\"",
2260 svn_depth_to_word(depth));
2262 if (lock_token)
2263 tokenstring = apr_psprintf(pool, "lock-token=\"%s\"", lock_token);
2265 /* Convert the copyfrom_* url/rev "public" pair into a Baseline
2266 Collection (BC) URL that represents the revision -- and a
2267 relative path under that BC. */
2268 SVN_ERR(svn_ra_neon__get_baseline_info(NULL, NULL, &bc_relative, NULL,
2269 rb->ras,
2270 url, revision,
2271 pool));
2274 svn_xml_escape_cdata_cstring(&qpath, path, pool);
2275 svn_xml_escape_attr_cstring(&qlinkpath, bc_relative.data, pool);
2276 if (start_empty)
2277 entry = apr_psprintf(pool,
2278 "<S:entry rev=\"%ld\" %s %s"
2279 " linkpath=\"/%s\" start-empty=\"true\""
2280 ">%s</S:entry>" DEBUG_CR,
2281 revision, depthstring, tokenstring,
2282 qlinkpath->data, qpath->data);
2283 else
2284 entry = apr_psprintf(pool,
2285 "<S:entry rev=\"%ld\" %s %s"
2286 " linkpath=\"/%s\">%s</S:entry>" DEBUG_CR,
2287 revision, depthstring, tokenstring,
2288 qlinkpath->data, qpath->data);
2290 return svn_io_file_write_full(rb->tmpfile, entry, strlen(entry), NULL, pool);
2294 static svn_error_t * reporter_delete_path(void *report_baton,
2295 const char *path,
2296 apr_pool_t *pool)
2298 report_baton_t *rb = report_baton;
2299 const char *s;
2300 svn_stringbuf_t *qpath = NULL;
2302 svn_xml_escape_cdata_cstring(&qpath, path, pool);
2303 s = apr_psprintf(pool,
2304 "<S:missing>%s</S:missing>" DEBUG_CR,
2305 qpath->data);
2307 return svn_io_file_write_full(rb->tmpfile, s, strlen(s), NULL, pool);
2311 static svn_error_t * reporter_abort_report(void *report_baton,
2312 apr_pool_t *pool)
2314 report_baton_t *rb = report_baton;
2316 (void) apr_file_close(rb->tmpfile);
2318 return SVN_NO_ERROR;
2322 static svn_error_t * reporter_finish_report(void *report_baton,
2323 apr_pool_t *pool)
2325 report_baton_t *rb = report_baton;
2326 svn_error_t *err;
2327 const char *vcc;
2328 apr_hash_t *request_headers = apr_hash_make(pool);
2329 apr_hash_set(request_headers, "Accept-Encoding", APR_HASH_KEY_STRING,
2330 "svndiff1;q=0.9,svndiff;q=0.8");
2333 #define SVN_RA_NEON__REPORT_TAIL "</S:update-report>" DEBUG_CR
2334 /* write the final closing gunk to our request body. */
2335 SVN_ERR(svn_io_file_write_full(rb->tmpfile,
2336 SVN_RA_NEON__REPORT_TAIL,
2337 sizeof(SVN_RA_NEON__REPORT_TAIL) - 1,
2338 NULL, pool));
2339 #undef SVN_RA_NEON__REPORT_TAIL
2341 /* get the editor process prepped */
2342 rb->dirs = apr_array_make(rb->pool, 5, sizeof(dir_item_t));
2343 rb->namestr = MAKE_BUFFER(rb->pool);
2344 rb->cpathstr = MAKE_BUFFER(rb->pool);
2345 rb->encoding = MAKE_BUFFER(rb->pool);
2346 rb->href = MAKE_BUFFER(rb->pool);
2348 /* get the VCC. if this doesn't work out for us, don't forget to
2349 remove the tmpfile before returning the error. */
2350 if ((err = svn_ra_neon__get_vcc(&vcc, rb->ras,
2351 rb->ras->url->data, pool)))
2353 (void) apr_file_close(rb->tmpfile);
2354 return err;
2357 /* dispatch the REPORT. */
2358 err = svn_ra_neon__parsed_request(rb->ras, "REPORT", vcc,
2359 NULL, rb->tmpfile, NULL,
2360 start_element,
2361 cdata_handler,
2362 end_element,
2364 request_headers, NULL,
2365 rb->spool_response, pool);
2367 /* we're done with the file */
2368 (void) apr_file_close(rb->tmpfile);
2370 SVN_ERR(err);
2372 /* We got the whole HTTP response thing done. *Whew*. Our edit
2373 baton should have been closed by now, so return a failure if it
2374 hasn't been. */
2375 if (rb->edit_baton)
2377 return svn_error_createf
2378 (SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2379 _("REPORT response handling failed to complete the editor drive"));
2382 /* store auth info if we can. */
2383 SVN_ERR(svn_ra_neon__maybe_store_auth_info(rb->ras, pool));
2385 return SVN_NO_ERROR;
2388 static const svn_ra_reporter3_t ra_neon_reporter = {
2389 reporter_set_path,
2390 reporter_delete_path,
2391 reporter_link_path,
2392 reporter_finish_report,
2393 reporter_abort_report
2397 /* Make a generic REPORTER / REPORT_BATON for reporting the state of
2398 the working copy against REVISION during updates or status checks.
2399 The server will drive EDITOR / EDIT_BATON to indicate how to
2400 transform the working copy into the requested target.
2402 SESSION is the RA session in use. TARGET is an optional single
2403 path component will restrict the scope of the operation to an entry
2404 in the directory represented by the SESSION's URL, or empty if the
2405 entire directory is meant to be the target.
2407 DEPTH is the requested depth of the operation. It will be
2408 transmitted to the server, which (if it understands depths) can use
2409 the information to limit the information it sends back. Also store
2410 DEPTH in the REPORT_BATON: that way, if the server is old and does
2411 not understand depth requests, the client can notice this when the
2412 response starts streaming in, and adjust accordingly (as of this
2413 writnig, by wrapping REPORTER->editor and REPORTER->edit_baton in a
2414 filtering editor that simply tosses out the data the client doesn't
2415 want).
2417 If SEND_COPYFROM_ARGS is set, then ask the server to transmit
2418 copyfrom args in add_file() in add_directory() calls.
2420 If IGNORE_ANCESTRY is set, the server will transmit real diffs
2421 between the working copy and the target even if those objects are
2422 not historically related. Otherwise, the response will generally
2423 look like a giant delete followed by a giant add.
2425 RESOURCE_WALK controls whether to ask the DAV server to supply an
2426 entire tree's worth of version-resource-URL working copy cache
2427 updates.
2429 FETCH_CONTENT is used by the REPORT response parser to determine
2430 whether it should bother getting the contents of files represented
2431 in the delta response (of if a directory delta is all that is of
2432 interest).
2434 If SEND_ALL is set, the server will be asked to embed contents into
2435 the main response.
2437 If SPOOL_RESPONSE is set, the REPORT response will be cached to
2438 disk in a tmpfile (in full), then read back and parsed.
2440 Oh, and do all this junk in POOL. */
2441 static svn_error_t *
2442 make_reporter(svn_ra_session_t *session,
2443 const svn_ra_reporter3_t **reporter,
2444 void **report_baton,
2445 svn_revnum_t revision,
2446 const char *target,
2447 const char *dst_path,
2448 svn_depth_t depth,
2449 svn_boolean_t send_copyfrom_args,
2450 svn_boolean_t ignore_ancestry,
2451 svn_boolean_t resource_walk,
2452 const svn_delta_editor_t *editor,
2453 void *edit_baton,
2454 svn_boolean_t fetch_content,
2455 svn_boolean_t send_all,
2456 svn_boolean_t spool_response,
2457 apr_pool_t *pool)
2459 svn_ra_neon__session_t *ras = session->priv;
2460 report_baton_t *rb;
2461 const char *s;
2462 svn_stringbuf_t *xml_s;
2463 const svn_delta_editor_t *filter_editor;
2464 void *filter_baton;
2465 svn_boolean_t has_target = *target ? TRUE : FALSE;
2466 svn_boolean_t server_supports_depth;
2468 SVN_ERR(svn_ra_neon__has_capability(session, &server_supports_depth,
2469 SVN_RA_CAPABILITY_DEPTH, pool));
2470 /* We can skip the depth filtering when the user requested
2471 depth_files or depth_infinity because the server will
2472 transmit the right stuff anyway. */
2473 if ((depth != svn_depth_files)
2474 && (depth != svn_depth_infinity)
2475 && ! server_supports_depth)
2477 SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
2478 &filter_baton,
2479 editor,
2480 edit_baton,
2481 depth,
2482 has_target,
2483 pool));
2484 editor = filter_editor;
2485 edit_baton = filter_baton;
2488 rb = apr_pcalloc(pool, sizeof(*rb));
2489 rb->ras = ras;
2490 rb->pool = pool;
2491 rb->scratch_pool = svn_pool_create(pool);
2492 rb->editor = editor;
2493 rb->edit_baton = edit_baton;
2494 rb->fetch_content = fetch_content;
2495 rb->in_resource = FALSE;
2496 rb->current_wcprop_path = svn_stringbuf_create("", pool);
2497 rb->is_switch = dst_path ? TRUE : FALSE;
2498 rb->target = target;
2499 rb->receiving_all = FALSE;
2500 rb->spool_response = spool_response;
2501 rb->whandler = NULL;
2502 rb->whandler_baton = NULL;
2503 rb->svndiff_decoder = NULL;
2504 rb->base64_decoder = NULL;
2505 rb->cdata_accum = svn_stringbuf_create("", pool);
2506 rb->send_copyfrom_args = send_copyfrom_args;
2508 /* Neon "pulls" request body content from the caller. The reporter is
2509 organized where data is "pushed" into self. To match these up, we use
2510 an intermediate file -- push data into the file, then let Neon pull
2511 from the file.
2513 Note: one day we could spin up a thread and use a pipe between this
2514 code and Neon. We write to a pipe, Neon reads from the pipe. Each
2515 thread can block on the pipe, waiting for the other to complete its
2516 work.
2519 /* Use the client callback to create a tmpfile. */
2520 SVN_ERR(ras->callbacks->open_tmp_file(&rb->tmpfile, ras->callback_baton,
2521 pool));
2523 /* prep the file */
2524 s = apr_psprintf(pool, "<S:update-report send-all=\"%s\" xmlns:S=\""
2525 SVN_XML_NAMESPACE "\">" DEBUG_CR,
2526 send_all ? "true" : "false");
2527 SVN_ERR(svn_io_file_write_full(rb->tmpfile, s, strlen(s), NULL, pool));
2529 /* always write the original source path. this is part of the "new
2530 style" update-report syntax. if the tmpfile is used in an "old
2531 style' update-report request, older servers will just ignore this
2532 unknown xml element. */
2533 xml_s = NULL;
2534 svn_xml_escape_cdata_cstring(&xml_s, ras->url->data, pool);
2535 s = apr_psprintf(pool, "<S:src-path>%s</S:src-path>" DEBUG_CR, xml_s->data);
2536 SVN_ERR(svn_io_file_write_full(rb->tmpfile, s, strlen(s), NULL, pool));
2538 /* an invalid revnum means "latest". we can just omit the target-revision
2539 element in that case. */
2540 if (SVN_IS_VALID_REVNUM(revision))
2542 s = apr_psprintf(pool,
2543 "<S:target-revision>%ld</S:target-revision>" DEBUG_CR,
2544 revision);
2545 SVN_ERR(svn_io_file_write_full(rb->tmpfile, s, strlen(s), NULL, pool));
2548 /* Pre-0.36 servers don't like to see an empty target string. */
2549 if (*target)
2551 xml_s = NULL;
2552 svn_xml_escape_cdata_cstring(&xml_s, target, pool);
2553 s = apr_psprintf(pool, "<S:update-target>%s</S:update-target>" DEBUG_CR,
2554 xml_s->data);
2555 SVN_ERR(svn_io_file_write_full(rb->tmpfile, s, strlen(s), NULL, pool));
2559 /* A NULL dst_path is also no problem; this is only passed during a
2560 'switch' operation. If NULL, we don't mention it in the custom
2561 report, and mod_dav_svn automatically runs dir_delta() on two
2562 identical paths. */
2563 if (dst_path)
2565 xml_s = NULL;
2566 svn_xml_escape_cdata_cstring(&xml_s, dst_path, pool);
2567 s = apr_psprintf(pool, "<S:dst-path>%s</S:dst-path>" DEBUG_CR,
2568 xml_s->data);
2569 SVN_ERR(svn_io_file_write_full(rb->tmpfile, s, strlen(s), NULL, pool));
2572 /* Old servers know "recursive" but not "depth"; help them DTRT. */
2573 if (depth == svn_depth_files || depth == svn_depth_empty)
2575 const char *data = "<S:recursive>no</S:recursive>" DEBUG_CR;
2576 SVN_ERR(svn_io_file_write_full(rb->tmpfile, data, strlen(data),
2577 NULL, pool));
2580 /* mod_dav_svn defaults to svn_depth_infinity, but we always send anyway. */
2582 s = apr_psprintf(pool, "<S:depth>%s</S:depth>" DEBUG_CR,
2583 svn_depth_to_word(depth));
2584 SVN_ERR(svn_io_file_write_full(rb->tmpfile, s, strlen(s), NULL, pool));
2587 /* mod_dav_svn will use ancestry in diffs unless it finds this element. */
2588 if (ignore_ancestry)
2590 const char *data = "<S:ignore-ancestry>yes</S:ignore-ancestry>" DEBUG_CR;
2591 SVN_ERR(svn_io_file_write_full(rb->tmpfile, data, strlen(data),
2592 NULL, pool));
2595 /* mod_dav_svn 1.5 and later won't send copyfrom args unless it
2596 finds this element. older mod_dav_svn modules should just
2597 ignore the unknown element. */
2598 if (send_copyfrom_args)
2600 const char *data =
2601 "<S:send-copyfrom-args>yes</S:send-copyfrom-args>" DEBUG_CR;
2602 SVN_ERR(svn_io_file_write_full(rb->tmpfile, data, strlen(data),
2603 NULL, pool));
2606 /* If we want a resource walk to occur, note that now. */
2607 if (resource_walk)
2609 const char *data = "<S:resource-walk>yes</S:resource-walk>" DEBUG_CR;
2610 SVN_ERR(svn_io_file_write_full(rb->tmpfile, data, strlen(data),
2611 NULL, pool));
2614 /* When in 'send-all' mode, mod_dav_svn will assume that it should
2615 calculate and transmit real text-deltas (instead of empty windows
2616 that merely indicate "text is changed") unless it finds this
2617 element. When not in 'send-all' mode, mod_dav_svn will never
2618 send text-deltas at all.
2620 NOTE: Do NOT count on servers actually obeying this, as some exist
2621 which obey send-all, but do not check for this directive at all! */
2622 if (send_all && (! fetch_content))
2624 const char *data = "<S:text-deltas>no</S:text-deltas>" DEBUG_CR;
2625 SVN_ERR(svn_io_file_write_full(rb->tmpfile, data, strlen(data),
2626 NULL, pool));
2629 *reporter = &ra_neon_reporter;
2630 *report_baton = rb;
2632 return SVN_NO_ERROR;
2636 svn_error_t * svn_ra_neon__do_update(svn_ra_session_t *session,
2637 const svn_ra_reporter3_t **reporter,
2638 void **report_baton,
2639 svn_revnum_t revision_to_update_to,
2640 const char *update_target,
2641 svn_depth_t depth,
2642 svn_boolean_t send_copyfrom_args,
2643 const svn_delta_editor_t *wc_update,
2644 void *wc_update_baton,
2645 apr_pool_t *pool)
2647 return make_reporter(session,
2648 reporter,
2649 report_baton,
2650 revision_to_update_to,
2651 update_target,
2652 NULL,
2653 depth,
2654 send_copyfrom_args,
2655 FALSE,
2656 FALSE,
2657 wc_update,
2658 wc_update_baton,
2659 TRUE, /* fetch_content */
2660 TRUE, /* send_all */
2661 FALSE, /* spool_response */
2662 pool);
2666 svn_error_t * svn_ra_neon__do_status(svn_ra_session_t *session,
2667 const svn_ra_reporter3_t **reporter,
2668 void **report_baton,
2669 const char *status_target,
2670 svn_revnum_t revision,
2671 svn_depth_t depth,
2672 const svn_delta_editor_t *wc_status,
2673 void *wc_status_baton,
2674 apr_pool_t *pool)
2676 return make_reporter(session,
2677 reporter,
2678 report_baton,
2679 revision,
2680 status_target,
2681 NULL,
2682 depth,
2683 FALSE,
2684 FALSE,
2685 FALSE,
2686 wc_status,
2687 wc_status_baton,
2688 FALSE, /* fetch_content */
2689 TRUE, /* send_all */
2690 FALSE, /* spool_response */
2691 pool);
2695 svn_error_t * svn_ra_neon__do_switch(svn_ra_session_t *session,
2696 const svn_ra_reporter3_t **reporter,
2697 void **report_baton,
2698 svn_revnum_t revision_to_update_to,
2699 const char *update_target,
2700 svn_depth_t depth,
2701 const char *switch_url,
2702 const svn_delta_editor_t *wc_update,
2703 void *wc_update_baton,
2704 apr_pool_t *pool)
2706 return make_reporter(session,
2707 reporter,
2708 report_baton,
2709 revision_to_update_to,
2710 update_target,
2711 switch_url,
2712 depth,
2713 FALSE, /* ### TODO(sussman): no copyfrom args */
2714 TRUE,
2715 FALSE, /* ### Disabled, pre-1.2 servers sometimes
2716 return incorrect resource-walk data */
2717 wc_update,
2718 wc_update_baton,
2719 TRUE, /* fetch_content */
2720 TRUE, /* send_all */
2721 FALSE, /* spool_response */
2722 pool);
2726 svn_error_t * svn_ra_neon__do_diff(svn_ra_session_t *session,
2727 const svn_ra_reporter3_t **reporter,
2728 void **report_baton,
2729 svn_revnum_t revision,
2730 const char *diff_target,
2731 svn_depth_t depth,
2732 svn_boolean_t ignore_ancestry,
2733 svn_boolean_t text_deltas,
2734 const char *versus_url,
2735 const svn_delta_editor_t *wc_diff,
2736 void *wc_diff_baton,
2737 apr_pool_t *pool)
2739 return make_reporter(session,
2740 reporter,
2741 report_baton,
2742 revision,
2743 diff_target,
2744 versus_url,
2745 depth,
2746 FALSE,
2747 ignore_ancestry,
2748 FALSE,
2749 wc_diff,
2750 wc_diff_baton,
2751 text_deltas, /* fetch_content */
2752 FALSE, /* send_all */
2753 TRUE, /* spool_response */
2754 pool);