summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_ra_serf
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commitd25dac7fcc6acc838b71bbda8916fd9665c709ab (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_ra_serf
downloadFreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip
FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_ra_serf')
-rw-r--r--subversion/libsvn_ra_serf/README84
-rw-r--r--subversion/libsvn_ra_serf/blame.c375
-rw-r--r--subversion/libsvn_ra_serf/blncache.c179
-rw-r--r--subversion/libsvn_ra_serf/blncache.h90
-rw-r--r--subversion/libsvn_ra_serf/commit.c2468
-rw-r--r--subversion/libsvn_ra_serf/get_deleted_rev.c178
-rw-r--r--subversion/libsvn_ra_serf/getdate.c161
-rw-r--r--subversion/libsvn_ra_serf/getlocations.c201
-rw-r--r--subversion/libsvn_ra_serf/getlocationsegments.c206
-rw-r--r--subversion/libsvn_ra_serf/getlocks.c277
-rw-r--r--subversion/libsvn_ra_serf/inherited_props.c344
-rw-r--r--subversion/libsvn_ra_serf/locks.c654
-rw-r--r--subversion/libsvn_ra_serf/log.c604
-rw-r--r--subversion/libsvn_ra_serf/merge.c430
-rw-r--r--subversion/libsvn_ra_serf/mergeinfo.c246
-rw-r--r--subversion/libsvn_ra_serf/options.c625
-rw-r--r--subversion/libsvn_ra_serf/property.c1263
-rw-r--r--subversion/libsvn_ra_serf/ra_serf.h1785
-rw-r--r--subversion/libsvn_ra_serf/replay.c920
-rw-r--r--subversion/libsvn_ra_serf/sb_bucket.c185
-rw-r--r--subversion/libsvn_ra_serf/serf.c1246
-rw-r--r--subversion/libsvn_ra_serf/update.c3639
-rw-r--r--subversion/libsvn_ra_serf/util.c2614
-rw-r--r--subversion/libsvn_ra_serf/util_error.c100
-rw-r--r--subversion/libsvn_ra_serf/xml.c825
25 files changed, 19699 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_serf/README b/subversion/libsvn_ra_serf/README
new file mode 100644
index 0000000..d3baf33
--- /dev/null
+++ b/subversion/libsvn_ra_serf/README
@@ -0,0 +1,84 @@
+ra_serf status
+==============
+
+This library is an RA-layer implementation of a WebDAV client that uses Serf.
+
+Serf's homepage is at:
+ http://code.google.com/p/serf/
+
+The latest serf releases can be fetched at:
+ http://code.google.com/p/serf/downloads/list
+
+The latest serf sources can be fetched via SVN at:
+ http://serf.googlecode.com/svn/trunk/
+
+ra_serf can be enabled with the following configure flags:
+ "--with-serf=/path/to/serf/install"
+As Neon is currently Subversion's default RA DAV layer, you also need
+to add "http-library = serf" to your ~/.subversion/servers file to
+choose ra_serf at runtime. Alternately, you can build with only
+support for ra_serf:
+ "--without-neon --with-serf=/path/to/serf/install"
+
+For more about how ra_serf/ra_neon talk WebDAV, consult notes/webdav-protocol.
+
+Working copies are interchangable between ra_serf and ra_neon. (They both use
+the svn:wc:ra_dav:version-url property to store the latest revision of a file.)
+
+Completed tasks
+---------------
+- Core functionality complete (see regression test status below)
+- https support (SSL)
+- Basic authentication
+- Update parallelization/pipelining (also for status/diff/switch/etc)
+ - Does not require inline base64-encoding of content
+ - 4 connections are open on an update (matches browser's default behavior)
+ - 1 connection is used for the REPORT; 3 are used to fetch files & props
+- Supports http-compression config flag
+- SSL client and server certificates
+- Proxy support
+- NTLM/SSPI integration for Windows folks
+- REPORT body buckets can now be read twice (#3212)
+
+Regression test status
+----------------------
+All current regression tests are known to pass on:
+ - Debian/AMD64 with APR 1.3.x
+ - Mac OS X
+ - Solaris
+ - Windows
+
+Things to do before the next release (1.6.x timeframe)
+------------------------------------------------------
+
+- Digest authentication
+
+- Fix the editor API violation (TBC, #2932)
+
+Nice to haves
+-------------
+
+- Move some of the code from ra_serf into serf. Serf doesn't have a very
+ high-level API; but the code in util.c can go a long way towards that.
+
+- Commit parallellization/pipelining
+ - Determine how to use HTTP pipelining and multiple connections for commit
+ - May need response from CHECKOUT to issue PUT/PROPPATCH
+ - ra_svn has a custom commit pipelining that may be worth investigating too
+
+- Use PROPFIND Depth: 1 when we are adding a directory locally to skip
+ fetching properties on files
+
+- Discover server's keep-alive setting via OPTIONS requests and notify serf
+
+- Fix bug in mod_dav_svn that omits remove-prop in the update-report when a
+ lock is broken and send-all is false.
+ (See upd_change_xxx_prop in mod_dav_svn/update.c)
+
+- Fix bug in mod_dav_svn/mod_deflate that causes it to hold onto the entire
+ REPORT response until it is completed. (This is why ra_serf doesn't request
+ gzip compression on the REPORT requests.)
+
+- Remove remaining abort()s - ;-) aka add better debug logging
+
+- Support for HTTP/1.0 pnly proxies.
diff --git a/subversion/libsvn_ra_serf/blame.c b/subversion/libsvn_ra_serf/blame.c
new file mode 100644
index 0000000..fa4243c
--- /dev/null
+++ b/subversion/libsvn_ra_serf/blame.c
@@ -0,0 +1,375 @@
+/*
+ * blame.c : entry point for blame RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_path.h"
+#include "svn_base64.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_string_private.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+typedef enum blame_state_e {
+ INITIAL = 0,
+ FILE_REVS_REPORT,
+ FILE_REV,
+ REV_PROP,
+ SET_PROP,
+ REMOVE_PROP,
+ MERGED_REVISION,
+ TXDELTA
+} blame_state_e;
+
+
+typedef struct blame_context_t {
+ /* pool passed to get_file_revs */
+ apr_pool_t *pool;
+
+ /* parameters set by our caller */
+ const char *path;
+ svn_revnum_t start;
+ svn_revnum_t end;
+ svn_boolean_t include_merged_revisions;
+
+ /* blame handler and baton */
+ svn_file_rev_handler_t file_rev;
+ void *file_rev_baton;
+
+ /* As we parse each FILE_REV, we collect data in these variables:
+ property changes and new content. STREAM is valid when we're
+ in the TXDELTA state, processing the incoming cdata. */
+ apr_hash_t *rev_props;
+ apr_array_header_t *prop_diffs;
+ apr_pool_t *state_pool; /* put property stuff in here */
+
+ svn_stream_t *stream;
+
+} blame_context_t;
+
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t blame_ttable[] = {
+ { INITIAL, S_, "file-revs-report", FILE_REVS_REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { FILE_REVS_REPORT, S_, "file-rev", FILE_REV,
+ FALSE, { "path", "rev", NULL }, TRUE },
+
+ { FILE_REV, S_, "rev-prop", REV_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+ { FILE_REV, S_, "set-prop", SET_PROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+ { FILE_REV, S_, "remove-prop", REMOVE_PROP,
+ FALSE, { "name", NULL }, TRUE },
+
+ { FILE_REV, S_, "merged-revision", MERGED_REVISION,
+ FALSE, { NULL }, TRUE },
+
+ { FILE_REV, S_, "txdelta", TXDELTA,
+ FALSE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+blame_opened(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool)
+{
+ blame_context_t *blame_ctx = baton;
+
+ if (entered_state == FILE_REV)
+ {
+ apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
+
+ /* Child elements will store properties in these structures. */
+ blame_ctx->rev_props = apr_hash_make(state_pool);
+ blame_ctx->prop_diffs = apr_array_make(state_pool,
+ 5, sizeof(svn_prop_t));
+ blame_ctx->state_pool = state_pool;
+
+ /* Clear this, so we can detect the absence of a TXDELTA. */
+ blame_ctx->stream = NULL;
+ }
+ else if (entered_state == TXDELTA)
+ {
+ apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
+ apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV);
+ const char *path;
+ const char *rev;
+ const char *merged_revision;
+ svn_txdelta_window_handler_t txdelta;
+ void *txdelta_baton;
+
+ path = svn_hash_gets(gathered, "path");
+ rev = svn_hash_gets(gathered, "rev");
+ merged_revision = svn_hash_gets(gathered, "merged-revision");
+
+ SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
+ path, SVN_STR_TO_REV(rev),
+ blame_ctx->rev_props,
+ merged_revision != NULL,
+ &txdelta, &txdelta_baton,
+ blame_ctx->prop_diffs,
+ state_pool));
+
+ blame_ctx->stream = svn_base64_decode(svn_txdelta_parse_svndiff(
+ txdelta, txdelta_baton,
+ TRUE /* error_on_early_close */,
+ state_pool),
+ state_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+blame_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ blame_context_t *blame_ctx = baton;
+
+ if (leaving_state == FILE_REV)
+ {
+ /* Note that we test STREAM, but any pointer is currently invalid.
+ It was closed when left the TXDELTA state. */
+ if (blame_ctx->stream == NULL)
+ {
+ const char *path;
+ const char *rev;
+
+ path = svn_hash_gets(attrs, "path");
+ rev = svn_hash_gets(attrs, "rev");
+
+ /* Send a "no content" notification. */
+ SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
+ path, SVN_STR_TO_REV(rev),
+ blame_ctx->rev_props,
+ FALSE /* result_of_merge */,
+ NULL, NULL, /* txdelta / baton */
+ blame_ctx->prop_diffs,
+ scratch_pool));
+ }
+ }
+ else if (leaving_state == MERGED_REVISION)
+ {
+ svn_ra_serf__xml_note(xes, FILE_REV, "merged-revision", "*");
+ }
+ else if (leaving_state == TXDELTA)
+ {
+ SVN_ERR(svn_stream_close(blame_ctx->stream));
+ }
+ else
+ {
+ const char *name;
+ const svn_string_t *value;
+
+ SVN_ERR_ASSERT(leaving_state == REV_PROP
+ || leaving_state == SET_PROP
+ || leaving_state == REMOVE_PROP);
+
+ name = apr_pstrdup(blame_ctx->state_pool,
+ svn_hash_gets(attrs, "name"));
+
+ if (leaving_state == REMOVE_PROP)
+ {
+ value = NULL;
+ }
+ else
+ {
+ const char *encoding = svn_hash_gets(attrs, "encoding");
+
+ if (encoding && strcmp(encoding, "base64") == 0)
+ value = svn_base64_decode_string(cdata, blame_ctx->state_pool);
+ else
+ value = svn_string_dup(cdata, blame_ctx->state_pool);
+ }
+
+ if (leaving_state == REV_PROP)
+ {
+ svn_hash_sets(blame_ctx->rev_props, name, value);
+ }
+ else
+ {
+ svn_prop_t *prop = apr_array_push(blame_ctx->prop_diffs);
+
+ prop->name = name;
+ prop->value = value;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_cdata_t */
+static svn_error_t *
+blame_cdata(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ blame_context_t *blame_ctx = baton;
+
+ if (current_state == TXDELTA)
+ {
+ SVN_ERR(svn_stream_write(blame_ctx->stream, data, &len));
+ /* Ignore the returned LEN value. */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_file_revs_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ blame_context_t *blame_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:file-revs-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:start-revision", apr_ltoa(pool, blame_ctx->start),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:end-revision", apr_ltoa(pool, blame_ctx->end),
+ alloc);
+
+ if (blame_ctx->include_merged_revisions)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:include-merged-revisions", NULL,
+ alloc);
+ }
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", blame_ctx->path,
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:file-revs-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t rev_handler,
+ void *rev_handler_baton,
+ apr_pool_t *pool)
+{
+ blame_context_t *blame_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx));
+ blame_ctx->pool = pool;
+ blame_ctx->path = path;
+ blame_ctx->file_rev = rev_handler;
+ blame_ctx->file_rev_baton = rev_handler_baton;
+ blame_ctx->start = start;
+ blame_ctx->end = end;
+ blame_ctx->include_merged_revisions = include_merged_revisions;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, end,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(blame_ttable,
+ blame_opened,
+ blame_closed,
+ blame_cdata,
+ blame_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->body_delegate = create_file_revs_body;
+ handler->body_delegate_baton = blame_ctx;
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ err = svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err);
+
+ return svn_error_trace(err);
+}
diff --git a/subversion/libsvn_ra_serf/blncache.c b/subversion/libsvn_ra_serf/blncache.c
new file mode 100644
index 0000000..d6abcdf
--- /dev/null
+++ b/subversion/libsvn_ra_serf/blncache.c
@@ -0,0 +1,179 @@
+/*
+ * blncache.c: DAV baseline information cache.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_pools.h>
+
+#include "svn_hash.h"
+#include "svn_dirent_uri.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+
+#include "blncache.h"
+
+/* Baseline information cache object. */
+typedef struct baseline_info_t
+{
+ const char *bc_url; /* baseline collection URL. */
+ svn_revnum_t revision; /* revision associated with the baseline. */
+
+} baseline_info_t;
+
+/* Module-private structure used to hold the caches. */
+struct svn_ra_serf__blncache_t
+{
+ /* A hash mapping 'svn_revnum_t *' baseline revisions to 'const
+ * char *' baseline collection URLs.
+ */
+ apr_hash_t *revnum_to_bc;
+
+ /* A hash mapping 'const char *' baseline URLs to 'baseline_info_t *'
+ * structures. (Allocated from the same pool as 'revnum_to_bc'.)
+ */
+ apr_hash_t *baseline_info;
+};
+
+
+
+/* Return a pointer to an 'baseline_info_t' structure allocated from
+ * POOL and populated with BC_URL (which is duped into POOL) and
+ * REVISION.
+ */
+static baseline_info_t *
+baseline_info_make(const char *bc_url,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ baseline_info_t *result = apr_palloc(pool, sizeof(*result));
+
+ result->bc_url = apr_pstrdup(pool, bc_url);
+ result->revision = revision;
+
+ return result;
+}
+
+/* Set in HASH the value VAL for the KEY (whose key length is KLEN).
+ * KEY will be duped into HASH's pool.
+ */
+static void
+hash_set_copy(apr_hash_t *hash,
+ const void *key,
+ apr_ssize_t klen,
+ const void *val)
+{
+ if (klen == APR_HASH_KEY_STRING)
+ klen = strlen(key);
+ apr_hash_set(hash, apr_pmemdup(apr_hash_pool_get(hash), key, klen),
+ klen, val);
+}
+
+
+svn_error_t *
+svn_ra_serf__blncache_create(svn_ra_serf__blncache_t **blncache_p,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__blncache_t *blncache = apr_pcalloc(pool, sizeof(*blncache));
+ apr_pool_t *cache_pool;
+
+ /* Create subpool for cached data. It will be cleared if we reach maximum
+ * cache size.*/
+ cache_pool = svn_pool_create(pool);
+ blncache->revnum_to_bc = apr_hash_make(cache_pool);
+ blncache->baseline_info = apr_hash_make(cache_pool);
+
+ *blncache_p = blncache;
+
+ return SVN_NO_ERROR;
+}
+
+#define MAX_CACHE_SIZE 1000
+
+svn_error_t *
+svn_ra_serf__blncache_set(svn_ra_serf__blncache_t *blncache,
+ const char *baseline_url,
+ svn_revnum_t revision,
+ const char *bc_url,
+ apr_pool_t *pool)
+{
+ if (bc_url && SVN_IS_VALID_REVNUM(revision))
+ {
+ apr_pool_t *cache_pool = apr_hash_pool_get(blncache->revnum_to_bc);
+
+ /* If the caches are too big, delete and recreate 'em and move along. */
+ if (MAX_CACHE_SIZE < (apr_hash_count(blncache->baseline_info)
+ + apr_hash_count(blncache->revnum_to_bc)))
+ {
+ svn_pool_clear(cache_pool);
+ blncache->revnum_to_bc = apr_hash_make(cache_pool);
+ blncache->baseline_info = apr_hash_make(cache_pool);
+ }
+
+ hash_set_copy(blncache->revnum_to_bc, &revision, sizeof(revision),
+ apr_pstrdup(cache_pool, bc_url));
+
+ if (baseline_url)
+ {
+ hash_set_copy(blncache->baseline_info, baseline_url,
+ APR_HASH_KEY_STRING,
+ baseline_info_make(bc_url, revision, cache_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#undef MAX_CACHE_SIZE
+
+svn_error_t *
+svn_ra_serf__blncache_get_bc_url(const char **bc_url_p,
+ svn_ra_serf__blncache_t *blncache,
+ svn_revnum_t revnum,
+ apr_pool_t *pool)
+{
+ const char *value = apr_hash_get(blncache->revnum_to_bc,
+ &revnum, sizeof(revnum));
+ *bc_url_p = value ? apr_pstrdup(pool, value) : NULL;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__blncache_get_baseline_info(const char **bc_url_p,
+ svn_revnum_t *revision_p,
+ svn_ra_serf__blncache_t *blncache,
+ const char *baseline_url,
+ apr_pool_t *pool)
+{
+ baseline_info_t *info = svn_hash_gets(blncache->baseline_info, baseline_url);
+ if (info)
+ {
+ *bc_url_p = apr_pstrdup(pool, info->bc_url);
+ *revision_p = info->revision;
+ }
+ else
+ {
+ *bc_url_p = NULL;
+ *revision_p = SVN_INVALID_REVNUM;
+ }
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/subversion/libsvn_ra_serf/blncache.h b/subversion/libsvn_ra_serf/blncache.h
new file mode 100644
index 0000000..5ad4eba
--- /dev/null
+++ b/subversion/libsvn_ra_serf/blncache.h
@@ -0,0 +1,90 @@
+/*
+ * blncache.h: DAV baseline information cache.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_RA_SERF_BLNCACHE_H
+#define SVN_LIBSVN_RA_SERF_BLNCACHE_H
+
+#include <apr_pools.h>
+
+#include "svn_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Baseline information cache. Baseline information cache holds information
+ * about DAV baseline (bln):
+ * 1. URL of the baseline (bln)
+ * 2. Revision number associated with baseline
+ * 3. URL of baseline collection (bc).
+ */
+typedef struct svn_ra_serf__blncache_t svn_ra_serf__blncache_t;
+
+/* Creates new instance of baseline cache. Sets BLNCACHE_P with
+ * a pointer to new instance, allocated in POOL.
+ */
+svn_error_t *
+svn_ra_serf__blncache_create(svn_ra_serf__blncache_t **blncache_p,
+ apr_pool_t *pool);
+
+/* Add information about baseline. BLNCACHE is a pointer to
+ * baseline cache previously created using svn_ra_serf__blncache_create
+ * function. BASELINE_URL is URL of baseline (can be NULL if unknown).
+ * REVNUM is revision number associated with baseline. Use SVN_INVALID_REVNUM
+ * to indicate that revision is unknown.
+ * BC_URL is URL of baseline collection (can be NULL if unknwon).
+ */
+svn_error_t *
+svn_ra_serf__blncache_set(svn_ra_serf__blncache_t *blncache,
+ const char *baseline_url,
+ svn_revnum_t revnum,
+ const char *bc_url,
+ apr_pool_t *pool);
+
+/* Sets *BC_URL_P with a pointer to baseline collection URL for the given
+ * REVNUM. *BC_URL_P will be NULL if cache doesn't have information about
+ * this baseline.
+ */
+svn_error_t *
+svn_ra_serf__blncache_get_bc_url(const char **bc_url_p,
+ svn_ra_serf__blncache_t *blncache,
+ svn_revnum_t revnum,
+ apr_pool_t *pool);
+
+/* Sets *BC_URL_P with pointer to baseline collection URL and *REVISION_P
+ * with revision number of baseline BASELINE_URL. *BC_URL_P will be NULL,
+ * *REVNUM_P will SVN_INVALID_REVNUM if cache doesn't have such
+ * information.
+ */
+svn_error_t *
+svn_ra_serf__blncache_get_baseline_info(const char **bc_url_p,
+ svn_revnum_t *revnum_p,
+ svn_ra_serf__blncache_t *blncache,
+ const char *baseline_url,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_RA_SERF_BLNCACHE_H*/
diff --git a/subversion/libsvn_ra_serf/commit.c b/subversion/libsvn_ra_serf/commit.c
new file mode 100644
index 0000000..aaf1717
--- /dev/null
+++ b/subversion/libsvn_ra_serf/commit.c
@@ -0,0 +1,2468 @@
+/*
+ * commit.c : entry point for commit RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_base64.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_skel.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/* Baton passed back with the commit editor. */
+typedef struct commit_context_t {
+ /* Pool for our commit. */
+ apr_pool_t *pool;
+
+ svn_ra_serf__session_t *session;
+ svn_ra_serf__connection_t *conn;
+
+ apr_hash_t *revprop_table;
+
+ svn_commit_callback2_t callback;
+ void *callback_baton;
+
+ apr_hash_t *lock_tokens;
+ svn_boolean_t keep_locks;
+ apr_hash_t *deleted_entries; /* deleted files (for delete+add detection) */
+
+ /* HTTP v2 stuff */
+ const char *txn_url; /* txn URL (!svn/txn/TXN_NAME) */
+ const char *txn_root_url; /* commit anchor txn root URL */
+
+ /* HTTP v1 stuff (only valid when 'txn_url' is NULL) */
+ const char *activity_url; /* activity base URL... */
+ const char *baseline_url; /* the working-baseline resource */
+ const char *checked_in_url; /* checked-in root to base CHECKOUTs from */
+ const char *vcc_url; /* vcc url */
+
+} commit_context_t;
+
+#define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
+
+/* Structure associated with a PROPPATCH request. */
+typedef struct proppatch_context_t {
+ apr_pool_t *pool;
+
+ const char *relpath;
+ const char *path;
+
+ commit_context_t *commit;
+
+ /* Changed and removed properties. */
+ apr_hash_t *changed_props;
+ apr_hash_t *removed_props;
+
+ /* Same, for the old value (*old_value_p). */
+ apr_hash_t *previous_changed_props;
+ apr_hash_t *previous_removed_props;
+
+ /* In HTTP v2, this is the file/directory version we think we're changing. */
+ svn_revnum_t base_revision;
+
+} proppatch_context_t;
+
+typedef struct delete_context_t {
+ const char *path;
+
+ svn_revnum_t revision;
+
+ const char *lock_token;
+ apr_hash_t *lock_token_hash;
+ svn_boolean_t keep_locks;
+
+} delete_context_t;
+
+/* Represents a directory. */
+typedef struct dir_context_t {
+ /* Pool for our directory. */
+ apr_pool_t *pool;
+
+ /* The root commit we're in progress for. */
+ commit_context_t *commit;
+
+ /* URL to operate against (used for CHECKOUT and PROPPATCH before
+ HTTP v2, for PROPPATCH in HTTP v2). */
+ const char *url;
+
+ /* How many pending changes we have left in this directory. */
+ unsigned int ref_count;
+
+ /* Is this directory being added? (Otherwise, just opened.) */
+ svn_boolean_t added;
+
+ /* Our parent */
+ struct dir_context_t *parent_dir;
+
+ /* The directory name; if "", we're the 'root' */
+ const char *relpath;
+
+ /* The basename of the directory. "" for the 'root' */
+ const char *name;
+
+ /* The base revision of the dir. */
+ svn_revnum_t base_revision;
+
+ const char *copy_path;
+ svn_revnum_t copy_revision;
+
+ /* Changed and removed properties */
+ apr_hash_t *changed_props;
+ apr_hash_t *removed_props;
+
+ /* The checked-out working resource for this directory. May be NULL; if so
+ call checkout_dir() first. */
+ const char *working_url;
+
+} dir_context_t;
+
+/* Represents a file to be committed. */
+typedef struct file_context_t {
+ /* Pool for our file. */
+ apr_pool_t *pool;
+
+ /* The root commit we're in progress for. */
+ commit_context_t *commit;
+
+ /* Is this file being added? (Otherwise, just opened.) */
+ svn_boolean_t added;
+
+ dir_context_t *parent_dir;
+
+ const char *relpath;
+ const char *name;
+
+ /* The checked-out working resource for this file. */
+ const char *working_url;
+
+ /* The base revision of the file. */
+ svn_revnum_t base_revision;
+
+ /* Copy path and revision */
+ const char *copy_path;
+ svn_revnum_t copy_revision;
+
+ /* stream */
+ svn_stream_t *stream;
+
+ /* Temporary file containing the svndiff. */
+ apr_file_t *svndiff;
+
+ /* Our base checksum as reported by the WC. */
+ const char *base_checksum;
+
+ /* Our resulting checksum as reported by the WC. */
+ const char *result_checksum;
+
+ /* Changed and removed properties. */
+ apr_hash_t *changed_props;
+ apr_hash_t *removed_props;
+
+ /* URL to PUT the file at. */
+ const char *url;
+
+} file_context_t;
+
+
+/* Setup routines and handlers for various requests we'll invoke. */
+
+static svn_error_t *
+return_response_err(svn_ra_serf__handler_t *handler)
+{
+ svn_error_t *err;
+
+ /* We should have captured SLINE and LOCATION in the HANDLER. */
+ SVN_ERR_ASSERT(handler->handler_pool != NULL);
+
+ /* Ye Olde Fallback Error */
+ err = svn_error_compose_create(
+ handler->server_error != NULL
+ ? handler->server_error->error
+ : SVN_NO_ERROR,
+ svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("%s of '%s': %d %s"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason));
+
+ /* Try to return one of the standard errors for 301, 404, etc.,
+ then look for an error embedded in the response. */
+ return svn_error_compose_create(svn_ra_serf__error_on_status(
+ handler->sline.code,
+ handler->path,
+ handler->location),
+ err);
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_checkout_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ const char *activity_url = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout",
+ "xmlns:D", "DAV:",
+ NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL);
+
+ SVN_ERR_ASSERT(activity_url != NULL);
+ svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
+ activity_url,
+ strlen(activity_url));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set");
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:apply-to-version", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout");
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+
+/* Using the HTTPv1 protocol, perform a CHECKOUT of NODE_URL within the
+ given COMMIT_CTX. The resulting working resource will be returned in
+ *WORKING_URL, allocated from RESULT_POOL. All temporary allocations
+ are performed in SCRATCH_POOL.
+
+ ### are these URLs actually repos relpath values? or fspath? or maybe
+ ### the abspath portion of the full URL.
+
+ This function operates synchronously.
+
+ Strictly speaking, we could perform "all" of the CHECKOUT requests
+ when the commit starts, and only block when we need a specific
+ answer. Or, at a minimum, send off these individual requests async
+ and block when we need the answer (eg PUT or PROPPATCH).
+
+ However: the investment to speed this up is not worthwhile, given
+ that CHECKOUT (and the related round trip) is completely obviated
+ in HTTPv2.
+*/
+static svn_error_t *
+checkout_node(const char **working_url,
+ const commit_context_t *commit_ctx,
+ const char *node_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t handler = { 0 };
+ apr_status_t status;
+ apr_uri_t uri;
+
+ /* HANDLER_POOL is the scratch pool since we don't need to remember
+ anything from the handler. We just want the working resource. */
+ handler.handler_pool = scratch_pool;
+ handler.session = commit_ctx->session;
+ handler.conn = commit_ctx->conn;
+
+ handler.body_delegate = create_checkout_body;
+ handler.body_delegate_baton = (/* const */ void *)commit_ctx->activity_url;
+ handler.body_type = "text/xml";
+
+ handler.response_handler = svn_ra_serf__expect_empty_body;
+ handler.response_baton = &handler;
+
+ handler.method = "CHECKOUT";
+ handler.path = node_url;
+
+ SVN_ERR(svn_ra_serf__context_run_one(&handler, scratch_pool));
+
+ if (handler.sline.code != 201)
+ return svn_error_trace(return_response_err(&handler));
+
+ if (handler.location == NULL)
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("No Location header received"));
+
+ /* We only want the path portion of the Location header.
+ (code.google.com sometimes returns an 'http:' scheme for an
+ 'https:' transaction ... we'll work around that by stripping the
+ scheme, host, and port here and re-adding the correct ones
+ later. */
+ status = apr_uri_parse(scratch_pool, handler.location, &uri);
+ if (status)
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Error parsing Location header value"));
+
+ *working_url = svn_urlpath__canonicalize(uri.path, result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This is a wrapper around checkout_node() (which see for
+ documentation) which simply retries the CHECKOUT request when it
+ fails due to an SVN_ERR_APMOD_BAD_BASELINE error return from the
+ server.
+
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=4127 for
+ details.
+*/
+static svn_error_t *
+retry_checkout_node(const char **working_url,
+ const commit_context_t *commit_ctx,
+ const char *node_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ int retry_count = 5; /* Magic, arbitrary number. */
+
+ do
+ {
+ svn_error_clear(err);
+
+ err = checkout_node(working_url, commit_ctx, node_url,
+ result_pool, scratch_pool);
+
+ /* There's a small chance of a race condition here if Apache is
+ experiencing heavy commit concurrency or if the network has
+ long latency. It's possible that the value of HEAD changed
+ between the time we fetched the latest baseline and the time
+ we try to CHECKOUT that baseline. If that happens, Apache
+ will throw us a BAD_BASELINE error (deltaV says you can only
+ checkout the latest baseline). We just ignore that specific
+ error and retry a few times, asking for the latest baseline
+ again. */
+ if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE))
+ return err;
+ }
+ while (err && retry_count--);
+
+ return err;
+}
+
+
+static svn_error_t *
+checkout_dir(dir_context_t *dir,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ dir_context_t *p_dir = dir;
+ const char *checkout_url;
+ const char **working;
+
+ if (dir->working_url)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* Is this directory or one of our parent dirs newly added?
+ * If so, we're already implicitly checked out. */
+ while (p_dir)
+ {
+ if (p_dir->added)
+ {
+ /* Implicitly checkout this dir now. */
+ dir->working_url = svn_path_url_add_component2(
+ dir->parent_dir->working_url,
+ dir->name, dir->pool);
+ return SVN_NO_ERROR;
+ }
+ p_dir = p_dir->parent_dir;
+ }
+
+ /* We could be called twice for the root: once to checkout the baseline;
+ * once to checkout the directory itself if we need to do so.
+ * Note: CHECKOUT_URL should live longer than HANDLER.
+ */
+ if (!dir->parent_dir && !dir->commit->baseline_url)
+ {
+ checkout_url = dir->commit->vcc_url;
+ working = &dir->commit->baseline_url;
+ }
+ else
+ {
+ checkout_url = dir->url;
+ working = &dir->working_url;
+ }
+
+ /* Checkout our directory into the activity URL now. */
+ err = retry_checkout_node(working, dir->commit, checkout_url,
+ dir->pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CONFLICT)
+ SVN_ERR_W(err, apr_psprintf(scratch_pool,
+ _("Directory '%s' is out of date; try updating"),
+ svn_dirent_local_style(dir->relpath, scratch_pool)));
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *CHECKED_IN_URL to the appropriate DAV version url for
+ * RELPATH (relative to the root of SESSION).
+ *
+ * Try to find this version url in three ways:
+ * First, if SESSION->callbacks->get_wc_prop() is defined, try to read the
+ * version url from the working copy properties.
+ * Second, if the version url of the parent directory PARENT_VSN_URL is
+ * defined, set *CHECKED_IN_URL to the concatenation of PARENT_VSN_URL with
+ * RELPATH.
+ * Else, fetch the version url for the root of SESSION using CONN and
+ * BASE_REVISION, and set *CHECKED_IN_URL to the concatenation of that
+ * with RELPATH.
+ *
+ * Allocate the result in RESULT_POOL, and use SCRATCH_POOL for
+ * temporary allocation.
+ */
+static svn_error_t *
+get_version_url(const char **checked_in_url,
+ svn_ra_serf__session_t *session,
+ const char *relpath,
+ svn_revnum_t base_revision,
+ const char *parent_vsn_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *root_checkout;
+
+ if (session->wc_callbacks->get_wc_prop)
+ {
+ const svn_string_t *current_version;
+
+ SVN_ERR(session->wc_callbacks->get_wc_prop(
+ session->wc_callback_baton,
+ relpath,
+ SVN_RA_SERF__WC_CHECKED_IN_URL,
+ &current_version, scratch_pool));
+
+ if (current_version)
+ {
+ *checked_in_url =
+ svn_urlpath__canonicalize(current_version->data, result_pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (parent_vsn_url)
+ {
+ root_checkout = parent_vsn_url;
+ }
+ else
+ {
+ const char *propfind_url;
+ svn_ra_serf__connection_t *conn = session->conns[0];
+
+ if (SVN_IS_VALID_REVNUM(base_revision))
+ {
+ /* mod_dav_svn can't handle the "Label:" header that
+ svn_ra_serf__deliver_props() is going to try to use for
+ this lookup, so we'll do things the hard(er) way, by
+ looking up the version URL from a resource in the
+ baseline collection. */
+ /* ### conn==NULL for session->conns[0]. same as CONN. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url,
+ NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, base_revision,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ propfind_url = session->session_url.path;
+ }
+
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout,
+ conn, propfind_url, base_revision,
+ "checked-in",
+ scratch_pool, scratch_pool));
+ if (!root_checkout)
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Path '%s' not present"),
+ session->session_url.path);
+
+ root_checkout = svn_urlpath__canonicalize(root_checkout, scratch_pool);
+ }
+
+ *checked_in_url = svn_path_url_add_component2(root_checkout, relpath,
+ result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+checkout_file(file_context_t *file,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ dir_context_t *parent_dir = file->parent_dir;
+ const char *checkout_url;
+
+ /* Is one of our parent dirs newly added? If so, we're already
+ * implicitly checked out.
+ */
+ while (parent_dir)
+ {
+ if (parent_dir->added)
+ {
+ /* Implicitly checkout this file now. */
+ file->working_url = svn_path_url_add_component2(
+ parent_dir->working_url,
+ svn_relpath_skip_ancestor(
+ parent_dir->relpath, file->relpath),
+ file->pool);
+ return SVN_NO_ERROR;
+ }
+ parent_dir = parent_dir->parent_dir;
+ }
+
+ SVN_ERR(get_version_url(&checkout_url,
+ file->commit->session,
+ file->relpath, file->base_revision,
+ NULL, scratch_pool, scratch_pool));
+
+ /* Checkout our file into the activity URL now. */
+ err = retry_checkout_node(&file->working_url, file->commit, checkout_url,
+ file->pool, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CONFLICT)
+ SVN_ERR_W(err, apr_psprintf(scratch_pool,
+ _("File '%s' is out of date; try updating"),
+ svn_dirent_local_style(file->relpath, scratch_pool)));
+ return err;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Helper function for proppatch_walker() below. */
+static svn_error_t *
+get_encoding_and_cdata(const char **encoding_p,
+ const svn_string_t **encoded_value_p,
+ serf_bucket_alloc_t *alloc,
+ const svn_string_t *value,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (value == NULL)
+ {
+ *encoding_p = NULL;
+ *encoded_value_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* If a property is XML-safe, XML-encode it. Else, base64-encode
+ it. */
+ if (svn_xml_is_xml_safe(value->data, value->len))
+ {
+ svn_stringbuf_t *xml_esc = NULL;
+ svn_xml_escape_cdata_string(&xml_esc, value, scratch_pool);
+ *encoding_p = NULL;
+ *encoded_value_p = svn_string_create_from_buf(xml_esc, result_pool);
+ }
+ else
+ {
+ *encoding_p = "base64";
+ *encoded_value_p = svn_base64_encode_string2(value, TRUE, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+typedef struct walker_baton_t {
+ serf_bucket_t *body_bkt;
+ apr_pool_t *body_pool;
+
+ apr_hash_t *previous_changed_props;
+ apr_hash_t *previous_removed_props;
+
+ const char *path;
+
+ /* Hack, since change_rev_prop(old_value_p != NULL, value = NULL) uses D:set
+ rather than D:remove... (see notes/http-and-webdav/webdav-protocol) */
+ enum {
+ filter_all_props,
+ filter_props_with_old_value,
+ filter_props_without_old_value
+ } filter;
+
+ /* Is the property being deleted? */
+ svn_boolean_t deleting;
+} walker_baton_t;
+
+/* If we have (recorded in WB) the old value of the property named NS:NAME,
+ * then set *HAVE_OLD_VAL to TRUE and set *OLD_VAL_P to that old value
+ * (which may be NULL); else set *HAVE_OLD_VAL to FALSE. */
+static svn_error_t *
+derive_old_val(svn_boolean_t *have_old_val,
+ const svn_string_t **old_val_p,
+ walker_baton_t *wb,
+ const char *ns,
+ const char *name)
+{
+ *have_old_val = FALSE;
+
+ if (wb->previous_changed_props)
+ {
+ const svn_string_t *val;
+ val = svn_ra_serf__get_prop_string(wb->previous_changed_props,
+ wb->path, ns, name);
+ if (val)
+ {
+ *have_old_val = TRUE;
+ *old_val_p = val;
+ }
+ }
+
+ if (wb->previous_removed_props)
+ {
+ const svn_string_t *val;
+ val = svn_ra_serf__get_prop_string(wb->previous_removed_props,
+ wb->path, ns, name);
+ if (val)
+ {
+ *have_old_val = TRUE;
+ *old_val_p = NULL;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+proppatch_walker(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ walker_baton_t *wb = baton;
+ serf_bucket_t *body_bkt = wb->body_bkt;
+ serf_bucket_t *cdata_bkt;
+ serf_bucket_alloc_t *alloc;
+ const char *encoding;
+ svn_boolean_t have_old_val;
+ const svn_string_t *old_val;
+ const svn_string_t *encoded_value;
+ const char *prop_name;
+
+ SVN_ERR(derive_old_val(&have_old_val, &old_val, wb, ns, name));
+
+ /* Jump through hoops to work with D:remove and its val = (""-for-NULL)
+ * representation. */
+ if (wb->filter != filter_all_props)
+ {
+ if (wb->filter == filter_props_with_old_value && ! have_old_val)
+ return SVN_NO_ERROR;
+ if (wb->filter == filter_props_without_old_value && have_old_val)
+ return SVN_NO_ERROR;
+ }
+ if (wb->deleting)
+ val = NULL;
+
+ alloc = body_bkt->allocator;
+
+ SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, val,
+ wb->body_pool, scratch_pool));
+ if (encoded_value)
+ {
+ cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data,
+ encoded_value->len,
+ alloc);
+ }
+ else
+ {
+ cdata_bkt = NULL;
+ }
+
+ /* Use the namespace prefix instead of adding the xmlns attribute to support
+ property names containing ':' */
+ if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ prop_name = apr_pstrcat(wb->body_pool, "S:", name, (char *)NULL);
+ else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ prop_name = apr_pstrcat(wb->body_pool, "C:", name, (char *)NULL);
+
+ if (cdata_bkt)
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
+ "V:encoding", encoding,
+ NULL);
+ else
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name,
+ "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
+ NULL);
+
+ if (have_old_val)
+ {
+ const char *encoding2;
+ const svn_string_t *encoded_value2;
+ serf_bucket_t *cdata_bkt2;
+
+ SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2,
+ alloc, old_val,
+ wb->body_pool, scratch_pool));
+
+ if (encoded_value2)
+ {
+ cdata_bkt2 = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value2->data,
+ encoded_value2->len,
+ alloc);
+ }
+ else
+ {
+ cdata_bkt2 = NULL;
+ }
+
+ if (cdata_bkt2)
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "V:" SVN_DAV__OLD_VALUE,
+ "V:encoding", encoding2,
+ NULL);
+ else
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "V:" SVN_DAV__OLD_VALUE,
+ "V:" SVN_DAV__OLD_VALUE__ABSENT, "1",
+ NULL);
+
+ if (cdata_bkt2)
+ serf_bucket_aggregate_append(body_bkt, cdata_bkt2);
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
+ "V:" SVN_DAV__OLD_VALUE);
+ }
+ if (cdata_bkt)
+ serf_bucket_aggregate_append(body_bkt, cdata_bkt);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, prop_name);
+
+ return SVN_NO_ERROR;
+}
+
+/* Possible add the lock-token "If:" precondition header to HEADERS if
+ an examination of COMMIT_CTX and RELPATH indicates that this is the
+ right thing to do.
+
+ Generally speaking, if the client provided a lock token for
+ RELPATH, it's the right thing to do. There is a notable instance
+ where this is not the case, however. If the file at RELPATH was
+ explicitly deleted in this commit already, then mod_dav removed its
+ lock token when it fielded the DELETE request, so we don't want to
+ set the lock precondition again. (See
+ http://subversion.tigris.org/issues/show_bug.cgi?id=3674 for details.)
+*/
+static svn_error_t *
+maybe_set_lock_token_header(serf_bucket_t *headers,
+ commit_context_t *commit_ctx,
+ const char *relpath,
+ apr_pool_t *pool)
+{
+ const char *token;
+
+ if (! (relpath && commit_ctx->lock_tokens))
+ return SVN_NO_ERROR;
+
+ if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
+ {
+ token = svn_hash_gets(commit_ctx->lock_tokens, relpath);
+ if (token)
+ {
+ const char *token_header;
+ const char *token_uri;
+ apr_uri_t uri = commit_ctx->session->session_url;
+
+ /* Supplying the optional URI affects apache response when
+ the lock is broken, see issue 4369. When present any URI
+ must be absolute (RFC 2518 9.4). */
+ uri.path = (char *)svn_path_url_add_component2(uri.path, relpath,
+ pool);
+ token_uri = apr_uri_unparse(pool, &uri, 0);
+
+ token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)",
+ (char *)NULL);
+ serf_bucket_headers_set(headers, "If", token_header);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_proppatch_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ proppatch_context_t *proppatch = baton;
+
+ if (SVN_IS_VALID_REVNUM(proppatch->base_revision))
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_psprintf(pool, "%ld",
+ proppatch->base_revision));
+ }
+
+ SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit,
+ proppatch->relpath, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct proppatch_body_baton_t {
+ proppatch_context_t *proppatch;
+
+ /* Content in the body should be allocated here, to live long enough. */
+ apr_pool_t *body_pool;
+};
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_proppatch_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *scratch_pool)
+{
+ struct proppatch_body_baton_t *pbb = baton;
+ proppatch_context_t *ctx = pbb->proppatch;
+ serf_bucket_t *body_bkt;
+ walker_baton_t wb = { 0 };
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:propertyupdate",
+ "xmlns:D", "DAV:",
+ "xmlns:V", SVN_DAV_PROP_NS_DAV,
+ "xmlns:C", SVN_DAV_PROP_NS_CUSTOM,
+ "xmlns:S", SVN_DAV_PROP_NS_SVN,
+ NULL);
+
+ wb.body_bkt = body_bkt;
+ wb.body_pool = pbb->body_pool;
+ wb.previous_changed_props = ctx->previous_changed_props;
+ wb.previous_removed_props = ctx->previous_removed_props;
+ wb.path = ctx->path;
+
+ if (apr_hash_count(ctx->changed_props) > 0)
+ {
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+
+ wb.filter = filter_all_props;
+ wb.deleting = FALSE;
+ SVN_ERR(svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path,
+ SVN_INVALID_REVNUM,
+ proppatch_walker, &wb,
+ scratch_pool));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
+ }
+
+ if (apr_hash_count(ctx->removed_props) > 0)
+ {
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+
+ wb.filter = filter_props_with_old_value;
+ wb.deleting = TRUE;
+ SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
+ SVN_INVALID_REVNUM,
+ proppatch_walker, &wb,
+ scratch_pool));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set");
+ }
+
+ if (apr_hash_count(ctx->removed_props) > 0)
+ {
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+
+ wb.filter = filter_props_without_old_value;
+ wb.deleting = TRUE;
+ SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path,
+ SVN_INVALID_REVNUM,
+ proppatch_walker, &wb,
+ scratch_pool));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove");
+ }
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:propertyupdate");
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t*
+proppatch_resource(proppatch_context_t *proppatch,
+ commit_context_t *commit,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *handler;
+ struct proppatch_body_baton_t pbb;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->method = "PROPPATCH";
+ handler->path = proppatch->path;
+ handler->conn = commit->conn;
+ handler->session = commit->session;
+
+ handler->header_delegate = setup_proppatch_headers;
+ handler->header_delegate_baton = proppatch;
+
+ pbb.proppatch = proppatch;
+ pbb.body_pool = pool;
+ handler->body_delegate = create_proppatch_body;
+ handler->body_delegate_baton = &pbb;
+
+ handler->response_handler = svn_ra_serf__handle_multistatus_only;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ if (handler->sline.code != 207
+ || (handler->server_error != NULL
+ && handler->server_error->error != NULL))
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_PROPPATCH_FAILED,
+ return_response_err(handler),
+ _("At least one property change failed; repository"
+ " is unchanged"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_put_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ file_context_t *ctx = baton;
+ apr_off_t offset;
+
+ /* We need to flush the file, make it unbuffered (so that it can be
+ * zero-copied via mmap), and reset the position before attempting to
+ * deliver the file.
+ *
+ * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
+ * and zero-copy the PUT body. However, on older APR versions, we can't
+ * check the buffer status; but serf will fall through and create a file
+ * bucket for us on the buffered svndiff handle.
+ */
+ apr_file_flush(ctx->svndiff);
+#if APR_VERSION_AT_LEAST(1, 3, 0)
+ apr_file_buffer_set(ctx->svndiff, NULL, 0);
+#endif
+ offset = 0;
+ apr_file_seek(ctx->svndiff, APR_SET, &offset);
+
+ *body_bkt = serf_bucket_file_create(ctx->svndiff, alloc);
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_empty_put_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_put_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ file_context_t *ctx = baton;
+
+ if (SVN_IS_VALID_REVNUM(ctx->base_revision))
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_psprintf(pool, "%ld", ctx->base_revision));
+ }
+
+ if (ctx->base_checksum)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_BASE_FULLTEXT_MD5_HEADER,
+ ctx->base_checksum);
+ }
+
+ if (ctx->result_checksum)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER,
+ ctx->result_checksum);
+ }
+
+ SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit,
+ ctx->relpath, pool));
+
+ return APR_SUCCESS;
+}
+
+static svn_error_t *
+setup_copy_file_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ file_context_t *file = baton;
+ apr_uri_t uri;
+ const char *absolute_uri;
+
+ /* The Dest URI must be absolute. Bummer. */
+ uri = file->commit->session->session_url;
+ uri.path = (char*)file->url;
+ absolute_uri = apr_uri_unparse(pool, &uri, 0);
+
+ serf_bucket_headers_set(headers, "Destination", absolute_uri);
+
+ serf_bucket_headers_setn(headers, "Depth", "0");
+ serf_bucket_headers_setn(headers, "Overwrite", "T");
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_copy_dir_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = baton;
+ apr_uri_t uri;
+ const char *absolute_uri;
+
+ /* The Dest URI must be absolute. Bummer. */
+ uri = dir->commit->session->session_url;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ uri.path = (char *)dir->url;
+ }
+ else
+ {
+ uri.path = (char *)svn_path_url_add_component2(
+ dir->parent_dir->working_url,
+ dir->name, pool);
+ }
+ absolute_uri = apr_uri_unparse(pool, &uri, 0);
+
+ serf_bucket_headers_set(headers, "Destination", absolute_uri);
+
+ serf_bucket_headers_setn(headers, "Depth", "infinity");
+ serf_bucket_headers_setn(headers, "Overwrite", "T");
+
+ /* Implicitly checkout this dir now. */
+ dir->working_url = apr_pstrdup(dir->pool, uri.path);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_delete_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ delete_context_t *ctx = baton;
+
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_ltoa(pool, ctx->revision));
+
+ if (ctx->lock_token_hash)
+ {
+ ctx->lock_token = svn_hash_gets(ctx->lock_token_hash, ctx->path);
+
+ if (ctx->lock_token)
+ {
+ const char *token_header;
+
+ token_header = apr_pstrcat(pool, "<", ctx->path, "> (<",
+ ctx->lock_token, ">)", (char *)NULL);
+
+ serf_bucket_headers_set(headers, "If", token_header);
+
+ if (ctx->keep_locks)
+ serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_KEEP_LOCKS);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_delete_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ delete_context_t *ctx = baton;
+ serf_bucket_t *body;
+
+ body = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body, alloc);
+
+ svn_ra_serf__merge_lock_token_list(ctx->lock_token_hash, ctx->path,
+ body, alloc, pool);
+
+ *body_bkt = body;
+ return SVN_NO_ERROR;
+}
+
+/* Helper function to write the svndiff stream to temporary file. */
+static svn_error_t *
+svndiff_stream_write(void *file_baton,
+ const char *data,
+ apr_size_t *len)
+{
+ file_context_t *ctx = file_baton;
+ apr_status_t status;
+
+ status = apr_file_write_full(ctx->svndiff, data, *len, NULL);
+ if (status)
+ return svn_error_wrap_apr(status, _("Failed writing updated file"));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* POST against 'me' resource handlers. */
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_txn_post_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ apr_hash_t *revprops = baton;
+ svn_skel_t *request_skel;
+ svn_stringbuf_t *skel_str;
+
+ request_skel = svn_skel__make_empty_list(pool);
+ if (revprops)
+ {
+ svn_skel_t *proplist_skel;
+
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, revprops, pool));
+ svn_skel__prepend(proplist_skel, request_skel);
+ svn_skel__prepend_str("create-txn-with-props", request_skel, pool);
+ skel_str = svn_skel__unparse(request_skel, pool);
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING(skel_str->data, alloc);
+ }
+ else
+ {
+ *body_bkt = SERF_BUCKET_SIMPLE_STRING("( create-txn )", alloc);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_header_delegate_t */
+static svn_error_t *
+setup_post_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+#ifdef SVN_DAV_SEND_VTXN_NAME
+ /* Enable this to exercise the VTXN-NAME code based on a client
+ supplied transaction name. */
+ serf_bucket_headers_set(headers, SVN_DAV_VTXN_NAME_HEADER,
+ svn_uuid_generate(pool));
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Handler baton for POST request. */
+typedef struct post_response_ctx_t
+{
+ svn_ra_serf__handler_t *handler;
+ commit_context_t *commit_ctx;
+} post_response_ctx_t;
+
+
+/* This implements serf_bucket_headers_do_callback_fn_t. */
+static int
+post_headers_iterator_callback(void *baton,
+ const char *key,
+ const char *val)
+{
+ post_response_ctx_t *prc = baton;
+ commit_context_t *prc_cc = prc->commit_ctx;
+ svn_ra_serf__session_t *sess = prc_cc->session;
+
+ /* If we provided a UUID to the POST request, we should get back
+ from the server an SVN_DAV_VTXN_NAME_HEADER header; otherwise we
+ expect the SVN_DAV_TXN_NAME_HEADER. We certainly don't expect to
+ see both. */
+
+ if (svn_cstring_casecmp(key, SVN_DAV_TXN_NAME_HEADER) == 0)
+ {
+ /* Build out txn and txn-root URLs using the txn name we're
+ given, and store the whole lot of it in the commit context. */
+ prc_cc->txn_url =
+ svn_path_url_add_component2(sess->txn_stub, val, prc_cc->pool);
+ prc_cc->txn_root_url =
+ svn_path_url_add_component2(sess->txn_root_stub, val, prc_cc->pool);
+ }
+
+ if (svn_cstring_casecmp(key, SVN_DAV_VTXN_NAME_HEADER) == 0)
+ {
+ /* Build out vtxn and vtxn-root URLs using the vtxn name we're
+ given, and store the whole lot of it in the commit context. */
+ prc_cc->txn_url =
+ svn_path_url_add_component2(sess->vtxn_stub, val, prc_cc->pool);
+ prc_cc->txn_root_url =
+ svn_path_url_add_component2(sess->vtxn_root_stub, val, prc_cc->pool);
+ }
+
+ return 0;
+}
+
+
+/* A custom serf_response_handler_t which is mostly a wrapper around
+ svn_ra_serf__expect_empty_body -- it just notices POST response
+ headers, too.
+
+ Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+post_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ post_response_ctx_t *prc = baton;
+ serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
+
+ /* Then see which ones we can discover. */
+ serf_bucket_headers_do(hdrs, post_headers_iterator_callback, prc);
+
+ /* Execute the 'real' response handler to XML-parse the repsonse body. */
+ return svn_ra_serf__expect_empty_body(request, response,
+ prc->handler, scratch_pool);
+}
+
+
+
+/* Commit baton callbacks */
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **root_baton)
+{
+ commit_context_t *ctx = edit_baton;
+ svn_ra_serf__handler_t *handler;
+ proppatch_context_t *proppatch_ctx;
+ dir_context_t *dir;
+ apr_hash_index_t *hi;
+ const char *proppatch_target = NULL;
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
+ {
+ post_response_ctx_t *prc;
+ const char *rel_path;
+ svn_boolean_t post_with_revprops
+ = (NULL != svn_hash_gets(ctx->session->supported_posts,
+ "create-txn-with-props"));
+
+ /* Create our activity URL now on the server. */
+ handler = apr_pcalloc(ctx->pool, sizeof(*handler));
+ handler->handler_pool = ctx->pool;
+ handler->method = "POST";
+ handler->body_type = SVN_SKEL_MIME_TYPE;
+ handler->body_delegate = create_txn_post_body;
+ handler->body_delegate_baton =
+ post_with_revprops ? ctx->revprop_table : NULL;
+ handler->header_delegate = setup_post_headers;
+ handler->header_delegate_baton = NULL;
+ handler->path = ctx->session->me_resource;
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ prc = apr_pcalloc(ctx->pool, sizeof(*prc));
+ prc->handler = handler;
+ prc->commit_ctx = ctx;
+
+ handler->response_handler = post_response_handler;
+ handler->response_baton = prc;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
+
+ if (handler->sline.code != 201)
+ {
+ apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ switch (handler->sline.code)
+ {
+ case 403:
+ status = SVN_ERR_RA_DAV_FORBIDDEN;
+ break;
+ case 404:
+ status = SVN_ERR_FS_NOT_FOUND;
+ break;
+ }
+
+ return svn_error_createf(status, NULL,
+ _("%s of '%s': %d %s (%s://%s)"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason,
+ ctx->session->session_url.scheme,
+ ctx->session->session_url.hostinfo);
+ }
+ if (! (ctx->txn_root_url && ctx->txn_url))
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("POST request did not return transaction information"));
+ }
+
+ /* Fixup the txn_root_url to point to the anchor of the commit. */
+ SVN_ERR(svn_ra_serf__get_relative_path(&rel_path,
+ ctx->session->session_url.path,
+ ctx->session, NULL, dir_pool));
+ ctx->txn_root_url = svn_path_url_add_component2(ctx->txn_root_url,
+ rel_path, ctx->pool);
+
+ /* Build our directory baton. */
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+ dir->pool = dir_pool;
+ dir->commit = ctx;
+ dir->base_revision = base_revision;
+ dir->relpath = "";
+ dir->name = "";
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+ dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url);
+
+ /* If we included our revprops in the POST, we need not
+ PROPPATCH them. */
+ proppatch_target = post_with_revprops ? NULL : ctx->txn_url;
+ }
+ else
+ {
+ const char *activity_str = ctx->session->activity_collection_url;
+
+ if (!activity_str)
+ SVN_ERR(svn_ra_serf__v1_get_activity_collection(&activity_str,
+ ctx->session->conns[0],
+ ctx->pool,
+ ctx->pool));
+
+ /* Cache the result. */
+ if (activity_str)
+ {
+ ctx->session->activity_collection_url =
+ apr_pstrdup(ctx->session->pool, activity_str);
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include the "
+ "requested activity-collection-set value"));
+ }
+
+ ctx->activity_url =
+ svn_path_url_add_component2(activity_str, svn_uuid_generate(ctx->pool),
+ ctx->pool);
+
+ /* Create our activity URL now on the server. */
+ handler = apr_pcalloc(ctx->pool, sizeof(*handler));
+ handler->handler_pool = ctx->pool;
+ handler->method = "MKACTIVITY";
+ handler->path = ctx->activity_url;
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool));
+
+ if (handler->sline.code != 201)
+ {
+ apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ switch (handler->sline.code)
+ {
+ case 403:
+ status = SVN_ERR_RA_DAV_FORBIDDEN;
+ break;
+ case 404:
+ status = SVN_ERR_FS_NOT_FOUND;
+ break;
+ }
+
+ return svn_error_createf(status, NULL,
+ _("%s of '%s': %d %s (%s://%s)"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason,
+ ctx->session->session_url.scheme,
+ ctx->session->session_url.hostinfo);
+ }
+
+ /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&(ctx->vcc_url), ctx->session,
+ ctx->conn, ctx->pool));
+
+
+ /* Build our directory baton. */
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+ dir->pool = dir_pool;
+ dir->commit = ctx;
+ dir->base_revision = base_revision;
+ dir->relpath = "";
+ dir->name = "";
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+
+ SVN_ERR(get_version_url(&dir->url, dir->commit->session,
+ dir->relpath,
+ dir->base_revision, ctx->checked_in_url,
+ dir->pool, dir->pool /* scratch_pool */));
+ ctx->checked_in_url = dir->url;
+
+ /* Checkout our root dir */
+ SVN_ERR(checkout_dir(dir, dir->pool /* scratch_pool */));
+
+ proppatch_target = ctx->baseline_url;
+ }
+
+ /* Unless this is NULL -- which means we don't need to PROPPATCH the
+ transaction with our revprops -- then, you know, PROPPATCH the
+ transaction with our revprops. */
+ if (proppatch_target)
+ {
+ proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = dir_pool;
+ proppatch_ctx->commit = ctx;
+ proppatch_ctx->path = proppatch_target;
+ proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
+
+ for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *name = svn__apr_hash_index_key(hi);
+ svn_string_t *value = svn__apr_hash_index_val(hi);
+ const char *ns;
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ svn_ra_serf__set_prop(proppatch_ctx->changed_props,
+ proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
+ }
+
+ SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool));
+ }
+
+ *root_baton = dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = parent_baton;
+ delete_context_t *delete_ctx;
+ svn_ra_serf__handler_t *handler;
+ const char *delete_target;
+ svn_error_t *err;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ delete_target = svn_path_url_add_component2(dir->commit->txn_root_url,
+ path, dir->pool);
+ }
+ else
+ {
+ /* Ensure our directory has been checked out */
+ SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
+ delete_target = svn_path_url_add_component2(dir->working_url,
+ svn_relpath_basename(path,
+ NULL),
+ pool);
+ }
+
+ /* DELETE our entry */
+ delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
+ delete_ctx->path = apr_pstrdup(pool, path);
+ delete_ctx->revision = revision;
+ delete_ctx->lock_token_hash = dir->commit->lock_tokens;
+ delete_ctx->keep_locks = dir->commit->keep_locks;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->session = dir->commit->session;
+ handler->conn = dir->commit->conn;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ handler->header_delegate = setup_delete_headers;
+ handler->header_delegate_baton = delete_ctx;
+
+ handler->method = "DELETE";
+ handler->path = delete_target;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ if (err &&
+ (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN ||
+ err->apr_err == SVN_ERR_FS_NO_LOCK_TOKEN ||
+ err->apr_err == SVN_ERR_FS_LOCK_OWNER_MISMATCH ||
+ err->apr_err == SVN_ERR_FS_PATH_ALREADY_LOCKED))
+ {
+ svn_error_clear(err);
+
+ /* An error has been registered on the connection. Reset the thing
+ so that we can use it again. */
+ serf_connection_reset(handler->conn->conn);
+
+ handler->body_delegate = create_delete_body;
+ handler->body_delegate_baton = delete_ctx;
+ handler->body_type = "text/xml";
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+ }
+ else if (err)
+ {
+ return err;
+ }
+
+ /* 204 No Content: item successfully deleted */
+ if (handler->sline.code != 204)
+ {
+ return svn_error_trace(return_response_err(handler));
+ }
+
+ svn_hash_sets(dir->commit->deleted_entries,
+ apr_pstrdup(dir->commit->pool, path), (void *)1);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ dir_context_t *parent = parent_baton;
+ dir_context_t *dir;
+ svn_ra_serf__handler_t *handler;
+ apr_status_t status;
+ const char *mkcol_target;
+
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+
+ dir->pool = dir_pool;
+ dir->parent_dir = parent;
+ dir->commit = parent->commit;
+ dir->added = TRUE;
+ dir->base_revision = SVN_INVALID_REVNUM;
+ dir->copy_revision = copyfrom_revision;
+ dir->copy_path = copyfrom_path;
+ dir->relpath = apr_pstrdup(dir->pool, path);
+ dir->name = svn_relpath_basename(dir->relpath, NULL);
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
+ path, dir->pool);
+ mkcol_target = dir->url;
+ }
+ else
+ {
+ /* Ensure our parent is checked out. */
+ SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */));
+
+ dir->url = svn_path_url_add_component2(parent->commit->checked_in_url,
+ dir->name, dir->pool);
+ mkcol_target = svn_path_url_add_component2(
+ parent->working_url,
+ dir->name, dir->pool);
+ }
+
+ handler = apr_pcalloc(dir->pool, sizeof(*handler));
+ handler->handler_pool = dir->pool;
+ handler->conn = dir->commit->conn;
+ handler->session = dir->commit->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+ if (!dir->copy_path)
+ {
+ handler->method = "MKCOL";
+ handler->path = mkcol_target;
+ }
+ else
+ {
+ apr_uri_t uri;
+ const char *req_url;
+
+ status = apr_uri_parse(dir->pool, dir->copy_path, &uri);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unable to parse URL '%s'"),
+ dir->copy_path);
+ }
+
+ /* ### conn==NULL for session->conns[0]. same as commit->conn. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ dir->commit->session,
+ NULL /* conn */,
+ uri.path, dir->copy_revision,
+ dir_pool, dir_pool));
+
+ handler->method = "COPY";
+ handler->path = req_url;
+
+ handler->header_delegate = setup_copy_dir_headers;
+ handler->header_delegate_baton = dir;
+ }
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool));
+
+ switch (handler->sline.code)
+ {
+ case 201: /* Created: item was successfully copied */
+ case 204: /* No Content: item successfully replaced an existing target */
+ break;
+
+ case 403:
+ return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
+ _("Access to '%s' forbidden"),
+ handler->path);
+ default:
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Adding directory failed: %s on %s "
+ "(%d %s)"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason);
+ }
+
+ *child_baton = dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *dir_pool,
+ void **child_baton)
+{
+ dir_context_t *parent = parent_baton;
+ dir_context_t *dir;
+
+ dir = apr_pcalloc(dir_pool, sizeof(*dir));
+
+ dir->pool = dir_pool;
+
+ dir->parent_dir = parent;
+ dir->commit = parent->commit;
+
+ dir->added = FALSE;
+ dir->base_revision = base_revision;
+ dir->relpath = apr_pstrdup(dir->pool, path);
+ dir->name = svn_relpath_basename(dir->relpath, NULL);
+ dir->changed_props = apr_hash_make(dir->pool);
+ dir->removed_props = apr_hash_make(dir->pool);
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ dir->url = svn_path_url_add_component2(parent->commit->txn_root_url,
+ path, dir->pool);
+ }
+ else
+ {
+ SVN_ERR(get_version_url(&dir->url,
+ dir->commit->session,
+ dir->relpath, dir->base_revision,
+ dir->commit->checked_in_url,
+ dir->pool, dir->pool /* scratch_pool */));
+ }
+ *child_baton = dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = dir_baton;
+ const char *ns;
+ const char *proppatch_target;
+
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ proppatch_target = dir->url;
+ }
+ else
+ {
+ /* Ensure we have a checked out dir. */
+ SVN_ERR(checkout_dir(dir, pool /* scratch_pool */));
+
+ proppatch_target = dir->working_url;
+ }
+
+ name = apr_pstrdup(dir->pool, name);
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ if (value)
+ {
+ value = svn_string_dup(value, dir->pool);
+ svn_ra_serf__set_prop(dir->changed_props, proppatch_target,
+ ns, name, value, dir->pool);
+ }
+ else
+ {
+ value = svn_string_create_empty(dir->pool);
+ svn_ra_serf__set_prop(dir->removed_props, proppatch_target,
+ ns, name, value, dir->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ dir_context_t *dir = dir_baton;
+
+ /* Huh? We're going to be called before the texts are sent. Ugh.
+ * Therefore, just wave politely at our caller.
+ */
+
+ /* PROPPATCH our prop change and pass it along. */
+ if (apr_hash_count(dir->changed_props) ||
+ apr_hash_count(dir->removed_props))
+ {
+ proppatch_context_t *proppatch_ctx;
+
+ proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = pool;
+ proppatch_ctx->commit = dir->commit;
+ proppatch_ctx->relpath = dir->relpath;
+ proppatch_ctx->changed_props = dir->changed_props;
+ proppatch_ctx->removed_props = dir->removed_props;
+ proppatch_ctx->base_revision = dir->base_revision;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ proppatch_ctx->path = dir->url;
+ }
+ else
+ {
+ proppatch_ctx->path = dir->working_url;
+ }
+
+ SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copy_path,
+ svn_revnum_t copy_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ dir_context_t *dir = parent_baton;
+ file_context_t *new_file;
+ const char *deleted_parent = path;
+
+ new_file = apr_pcalloc(file_pool, sizeof(*new_file));
+ new_file->pool = file_pool;
+
+ dir->ref_count++;
+
+ new_file->parent_dir = dir;
+ new_file->commit = dir->commit;
+ new_file->relpath = apr_pstrdup(new_file->pool, path);
+ new_file->name = svn_relpath_basename(new_file->relpath, NULL);
+ new_file->added = TRUE;
+ new_file->base_revision = SVN_INVALID_REVNUM;
+ new_file->copy_path = copy_path;
+ new_file->copy_revision = copy_revision;
+ new_file->changed_props = apr_hash_make(new_file->pool);
+ new_file->removed_props = apr_hash_make(new_file->pool);
+
+ /* Ensure that the file doesn't exist by doing a HEAD on the
+ resource. If we're using HTTP v2, we'll just look into the
+ transaction root tree for this thing. */
+ if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit))
+ {
+ new_file->url = svn_path_url_add_component2(dir->commit->txn_root_url,
+ path, new_file->pool);
+ }
+ else
+ {
+ /* Ensure our parent directory has been checked out */
+ SVN_ERR(checkout_dir(dir, new_file->pool /* scratch_pool */));
+
+ new_file->url =
+ svn_path_url_add_component2(dir->working_url,
+ new_file->name, new_file->pool);
+ }
+
+ while (deleted_parent && deleted_parent[0] != '\0')
+ {
+ if (svn_hash_gets(dir->commit->deleted_entries, deleted_parent))
+ {
+ break;
+ }
+ deleted_parent = svn_relpath_dirname(deleted_parent, file_pool);
+ }
+
+ if (! ((dir->added && !dir->copy_path) ||
+ (deleted_parent && deleted_parent[0] != '\0')))
+ {
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(new_file->pool, sizeof(*handler));
+ handler->handler_pool = new_file->pool;
+ handler->session = new_file->commit->session;
+ handler->conn = new_file->commit->conn;
+ handler->method = "HEAD";
+ handler->path = svn_path_url_add_component2(
+ dir->commit->session->session_url.path,
+ path, new_file->pool);
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, new_file->pool));
+
+ if (handler->sline.code != 404)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_ALREADY_EXISTS, NULL,
+ _("File '%s' already exists"), path);
+ }
+ }
+
+ *file_baton = new_file;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *file_pool,
+ void **file_baton)
+{
+ dir_context_t *parent = parent_baton;
+ file_context_t *new_file;
+
+ new_file = apr_pcalloc(file_pool, sizeof(*new_file));
+ new_file->pool = file_pool;
+
+ parent->ref_count++;
+
+ new_file->parent_dir = parent;
+ new_file->commit = parent->commit;
+ new_file->relpath = apr_pstrdup(new_file->pool, path);
+ new_file->name = svn_relpath_basename(new_file->relpath, NULL);
+ new_file->added = FALSE;
+ new_file->base_revision = base_revision;
+ new_file->changed_props = apr_hash_make(new_file->pool);
+ new_file->removed_props = apr_hash_make(new_file->pool);
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit))
+ {
+ new_file->url = svn_path_url_add_component2(parent->commit->txn_root_url,
+ path, new_file->pool);
+ }
+ else
+ {
+ /* CHECKOUT the file into our activity. */
+ SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */));
+
+ new_file->url = new_file->working_url;
+ }
+
+ *file_baton = new_file;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ file_context_t *ctx = file_baton;
+
+ /* Store the stream in a temporary file; we'll give it to serf when we
+ * close this file.
+ *
+ * TODO: There should be a way we can stream the request body instead of
+ * writing to a temporary file (ugh). A special svn stream serf bucket
+ * that returns EAGAIN until we receive the done call? But, when
+ * would we run through the serf context? Grr.
+ *
+ * ctx->pool is the same for all files in the commit that send a
+ * textdelta so this file is explicitly closed in close_file to
+ * avoid too many simultaneously open files.
+ */
+
+ SVN_ERR(svn_io_open_unique_file3(&ctx->svndiff, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ ctx->pool, pool));
+
+ ctx->stream = svn_stream_create(ctx, pool);
+ svn_stream_set_write(ctx->stream, svndiff_stream_write);
+
+ svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+
+ if (base_checksum)
+ ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ file_context_t *file = file_baton;
+ const char *ns;
+
+ name = apr_pstrdup(file->pool, name);
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ if (value)
+ {
+ value = svn_string_dup(value, file->pool);
+ svn_ra_serf__set_prop(file->changed_props, file->url,
+ ns, name, value, file->pool);
+ }
+ else
+ {
+ value = svn_string_create_empty(file->pool);
+
+ svn_ra_serf__set_prop(file->removed_props, file->url,
+ ns, name, value, file->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *scratch_pool)
+{
+ file_context_t *ctx = file_baton;
+ svn_boolean_t put_empty_file = FALSE;
+ apr_status_t status;
+
+ ctx->result_checksum = text_checksum;
+
+ if (ctx->copy_path)
+ {
+ svn_ra_serf__handler_t *handler;
+ apr_uri_t uri;
+ const char *req_url;
+
+ status = apr_uri_parse(scratch_pool, ctx->copy_path, &uri);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unable to parse URL '%s'"),
+ ctx->copy_path);
+ }
+
+ /* ### conn==NULL for session->conns[0]. same as commit->conn. */
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ ctx->commit->session,
+ NULL /* conn */,
+ uri.path, ctx->copy_revision,
+ scratch_pool, scratch_pool));
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+ handler->handler_pool = scratch_pool;
+ handler->method = "COPY";
+ handler->path = req_url;
+ handler->conn = ctx->commit->conn;
+ handler->session = ctx->commit->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ handler->header_delegate = setup_copy_file_headers;
+ handler->header_delegate_baton = ctx;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+
+ if (handler->sline.code != 201 && handler->sline.code != 204)
+ {
+ return svn_error_trace(return_response_err(handler));
+ }
+ }
+
+ /* If we got no stream of changes, but this is an added-without-history
+ * file, make a note that we'll be PUTting a zero-byte file to the server.
+ */
+ if ((!ctx->stream) && ctx->added && (!ctx->copy_path))
+ put_empty_file = TRUE;
+
+ /* If we had a stream of changes, push them to the server... */
+ if (ctx->stream || put_empty_file)
+ {
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+ handler->handler_pool = scratch_pool;
+ handler->method = "PUT";
+ handler->path = ctx->url;
+ handler->conn = ctx->commit->conn;
+ handler->session = ctx->commit->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ if (put_empty_file)
+ {
+ handler->body_delegate = create_empty_put_body;
+ handler->body_delegate_baton = ctx;
+ handler->body_type = "text/plain";
+ }
+ else
+ {
+ handler->body_delegate = create_put_body;
+ handler->body_delegate_baton = ctx;
+ handler->body_type = SVN_SVNDIFF_MIME_TYPE;
+ }
+
+ handler->header_delegate = setup_put_headers;
+ handler->header_delegate_baton = ctx;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+
+ if (handler->sline.code != 204 && handler->sline.code != 201)
+ {
+ return svn_error_trace(return_response_err(handler));
+ }
+ }
+
+ if (ctx->svndiff)
+ SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
+
+ /* If we had any prop changes, push them via PROPPATCH. */
+ if (apr_hash_count(ctx->changed_props) ||
+ apr_hash_count(ctx->removed_props))
+ {
+ proppatch_context_t *proppatch;
+
+ proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch));
+ proppatch->pool = ctx->pool;
+ proppatch->relpath = ctx->relpath;
+ proppatch->path = ctx->url;
+ proppatch->commit = ctx->commit;
+ proppatch->changed_props = ctx->changed_props;
+ proppatch->removed_props = ctx->removed_props;
+ proppatch->base_revision = ctx->base_revision;
+
+ SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ commit_context_t *ctx = edit_baton;
+ const char *merge_target =
+ ctx->activity_url ? ctx->activity_url : ctx->txn_url;
+ const svn_commit_info_t *commit_info;
+ int response_code;
+
+ /* MERGE our activity */
+ SVN_ERR(svn_ra_serf__run_merge(&commit_info, &response_code,
+ ctx->session,
+ ctx->session->conns[0],
+ merge_target,
+ ctx->lock_tokens,
+ ctx->keep_locks,
+ pool, pool));
+
+ if (response_code != 200)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("MERGE request failed: returned %d "
+ "(during commit)"),
+ response_code);
+ }
+
+ /* Inform the WC that we did a commit. */
+ if (ctx->callback)
+ SVN_ERR(ctx->callback(commit_info, ctx->callback_baton, pool));
+
+ /* If we're using activities, DELETE our completed activity. */
+ if (ctx->activity_url)
+ {
+ svn_ra_serf__handler_t *handler;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->method = "DELETE";
+ handler->path = ctx->activity_url;
+ handler->conn = ctx->conn;
+ handler->session = ctx->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ SVN_ERR_ASSERT(handler->sline.code == 204);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+abort_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ commit_context_t *ctx = edit_baton;
+ svn_ra_serf__handler_t *handler;
+
+ /* If an activity or transaction wasn't even created, don't bother
+ trying to delete it. */
+ if (! (ctx->activity_url || ctx->txn_url))
+ return SVN_NO_ERROR;
+
+ /* An error occurred on conns[0]. serf 0.4.0 remembers that the connection
+ had a problem. We need to reset it, in order to use it again. */
+ serf_connection_reset(ctx->session->conns[0]->conn);
+
+ /* DELETE our aborted activity */
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->handler_pool = pool;
+ handler->method = "DELETE";
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
+ handler->path = ctx->txn_url;
+ else
+ handler->path = ctx->activity_url;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ /* 204 if deleted,
+ 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
+ 404 if the activity wasn't found. */
+ if (handler->sline.code != 204
+ && handler->sline.code != 403
+ && handler->sline.code != 404
+ )
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
+ const svn_delta_editor_t **ret_editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_delta_editor_t *editor;
+ commit_context_t *ctx;
+ const char *repos_root;
+ const char *base_relpath;
+ svn_boolean_t supports_ephemeral_props;
+
+ ctx = apr_pcalloc(pool, sizeof(*ctx));
+
+ ctx->pool = pool;
+
+ ctx->session = session;
+ ctx->conn = session->conns[0];
+
+ ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool);
+
+ /* If the server supports ephemeral properties, add some carrying
+ interesting version information. */
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props,
+ SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ pool));
+ if (supports_ephemeral_props)
+ {
+ svn_hash_sets(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_CLIENT_COMPAT_VERSION),
+ svn_string_create(SVN_VER_NUMBER, pool));
+ svn_hash_sets(ctx->revprop_table,
+ apr_pstrdup(pool, SVN_PROP_TXN_USER_AGENT),
+ svn_string_create(session->useragent, pool));
+ }
+
+ ctx->callback = callback;
+ ctx->callback_baton = callback_baton;
+
+ ctx->lock_tokens = lock_tokens;
+ ctx->keep_locks = keep_locks;
+
+ ctx->deleted_entries = apr_hash_make(ctx->pool);
+
+ editor = svn_delta_default_editor(pool);
+ editor->open_root = open_root;
+ editor->delete_entry = delete_entry;
+ editor->add_directory = add_directory;
+ editor->open_directory = open_directory;
+ editor->change_dir_prop = change_dir_prop;
+ editor->close_directory = close_directory;
+ editor->add_file = add_file;
+ editor->open_file = open_file;
+ editor->apply_textdelta = apply_textdelta;
+ editor->change_file_prop = change_file_prop;
+ editor->close_file = close_file;
+ editor->close_edit = close_edit;
+ editor->abort_edit = abort_edit;
+
+ *ret_editor = editor;
+ *edit_baton = ctx;
+
+ SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root, pool));
+ base_relpath = svn_uri_skip_ancestor(repos_root, session->session_url_str,
+ pool);
+
+ SVN_ERR(svn_editor__insert_shims(ret_editor, edit_baton, *ret_editor,
+ *edit_baton, repos_root, base_relpath,
+ session->shim_callbacks, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ proppatch_context_t *proppatch_ctx;
+ commit_context_t *commit;
+ const char *proppatch_target;
+ const char *ns;
+ svn_error_t *err;
+
+ if (old_value_p)
+ {
+ svn_boolean_t capable;
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &capable,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ pool));
+
+ /* How did you get past the same check in svn_ra_change_rev_prop2()? */
+ SVN_ERR_ASSERT(capable);
+ }
+
+ commit = apr_pcalloc(pool, sizeof(*commit));
+
+ commit->pool = pool;
+
+ commit->session = session;
+ commit->conn = session->conns[0];
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ proppatch_target = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
+ }
+ else
+ {
+ const char *vcc_url;
+
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session,
+ commit->conn, pool));
+
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target,
+ commit->conn, vcc_url, rev,
+ "href",
+ pool, pool));
+ }
+
+ if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0)
+ {
+ ns = SVN_DAV_PROP_NS_SVN;
+ name += sizeof(SVN_PROP_PREFIX) - 1;
+ }
+ else
+ {
+ ns = SVN_DAV_PROP_NS_CUSTOM;
+ }
+
+ /* PROPPATCH our log message and pass it along. */
+ proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = pool;
+ proppatch_ctx->commit = commit;
+ proppatch_ctx->path = proppatch_target;
+ proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool);
+ if (old_value_p)
+ {
+ proppatch_ctx->previous_changed_props = apr_hash_make(proppatch_ctx->pool);
+ proppatch_ctx->previous_removed_props = apr_hash_make(proppatch_ctx->pool);
+ }
+ proppatch_ctx->base_revision = SVN_INVALID_REVNUM;
+
+ if (old_value_p && *old_value_p)
+ {
+ svn_ra_serf__set_prop(proppatch_ctx->previous_changed_props,
+ proppatch_ctx->path,
+ ns, name, *old_value_p, proppatch_ctx->pool);
+ }
+ else if (old_value_p)
+ {
+ svn_string_t *dummy_value = svn_string_create_empty(proppatch_ctx->pool);
+
+ svn_ra_serf__set_prop(proppatch_ctx->previous_removed_props,
+ proppatch_ctx->path,
+ ns, name, dummy_value, proppatch_ctx->pool);
+ }
+
+ if (value)
+ {
+ svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
+ }
+ else
+ {
+ value = svn_string_create_empty(proppatch_ctx->pool);
+
+ svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path,
+ ns, name, value, proppatch_ctx->pool);
+ }
+
+ err = proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool);
+ if (err)
+ return
+ svn_error_create
+ (SVN_ERR_RA_DAV_REQUEST_FAILED, err,
+ _("DAV request failed; it's possible that the repository's "
+ "pre-revprop-change hook either failed or is non-existent"));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/get_deleted_rev.c b/subversion/libsvn_ra_serf/get_deleted_rev.c
new file mode 100644
index 0000000..40f6b1d
--- /dev/null
+++ b/subversion/libsvn_ra_serf/get_deleted_rev.c
@@ -0,0 +1,178 @@
+/*
+ * get_deleted_rev.c : ra_serf get_deleted_rev API implementation.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include "svn_ra.h"
+#include "svn_xml.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum drev_state_e {
+ INITIAL = 0,
+ REPORT,
+ VERSION_NAME
+};
+
+typedef struct drev_context_t {
+ const char *path;
+ svn_revnum_t peg_revision;
+ svn_revnum_t end_revision;
+
+ /* What revision was PATH@PEG_REVISION first deleted within
+ the range PEG_REVISION-END-END_REVISION? */
+ svn_revnum_t *revision_deleted;
+
+} drev_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t getdrev_ttable[] = {
+ { INITIAL, S_, "get-deleted-rev-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+getdrev_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ drev_context_t *drev_ctx = baton;
+
+ SVN_ERR_ASSERT(leaving_state == VERSION_NAME);
+ SVN_ERR_ASSERT(cdata != NULL);
+
+ *drev_ctx->revision_deleted = SVN_STR_TO_REV(cdata->data);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_getdrev_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ drev_context_t *drev_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:get-deleted-rev-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ "xmlns:D", "DAV:",
+ NULL, NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", drev_ctx->path,
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:peg-revision",
+ apr_ltoa(pool, drev_ctx->peg_revision),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:end-revision",
+ apr_ltoa(pool, drev_ctx->end_revision),
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:get-deleted-rev-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_deleted_rev(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool)
+{
+ drev_context_t *drev_ctx;
+ svn_ra_serf__session_t *ras = session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ drev_ctx = apr_pcalloc(pool, sizeof(*drev_ctx));
+ drev_ctx->path = path;
+ drev_ctx->peg_revision = peg_revision;
+ drev_ctx->end_revision = end_revision;
+ drev_ctx->revision_deleted = revision_deleted;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ ras, NULL /* conn */,
+ NULL /* url */, peg_revision,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(getdrev_ttable,
+ NULL, getdrev_closed, NULL,
+ drev_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->body_delegate = create_getdrev_body;
+ handler->body_delegate_baton = drev_ctx;
+ handler->conn = ras->conns[0];
+ handler->session = ras;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ /* Map status 501: Method Not Implemented to our not implemented error.
+ 1.5.x servers and older don't support this report. */
+ if (handler->sline.code == 501)
+ return svn_error_createf(SVN_ERR_RA_NOT_IMPLEMENTED, err,
+ _("'%s' REPORT not implemented"),
+ "get-deleted-rev");
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/getdate.c b/subversion/libsvn_ra_serf/getdate.c
new file mode 100644
index 0000000..867e86f
--- /dev/null
+++ b/subversion/libsvn_ra_serf/getdate.c
@@ -0,0 +1,161 @@
+/*
+ * getdate.c : entry point for get_dated_revision for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+
+#include "private/svn_dav_protocol.h"
+
+#include "svn_private_config.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum date_state_e {
+ INITIAL = 0,
+ REPORT,
+ VERSION_NAME
+};
+
+
+typedef struct date_context_t {
+ /* The time asked about. */
+ apr_time_t time;
+
+ /* What was the youngest revision at that time? */
+ svn_revnum_t *revision;
+
+} date_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t date_ttable[] = {
+ { INITIAL, S_, "dated-rev-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+date_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ date_context_t *date_ctx = baton;
+
+ SVN_ERR_ASSERT(leaving_state == VERSION_NAME);
+ SVN_ERR_ASSERT(cdata != NULL);
+
+ *date_ctx->revision = SVN_STR_TO_REV(cdata->data);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_getdate_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ date_context_t *date_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "S:dated-rev-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ "xmlns:D", "DAV:",
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "D:" SVN_DAV__CREATIONDATE,
+ svn_time_to_cstring(date_ctx->time, pool),
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "S:dated-rev-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_dated_revision(svn_ra_session_t *ra_session,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool)
+{
+ date_context_t *date_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *report_target;
+
+ date_ctx = apr_palloc(pool, sizeof(*date_ctx));
+ date_ctx->time = tm;
+ date_ctx->revision = revision;
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(date_ttable,
+ NULL, date_closed, NULL,
+ date_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = report_target;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->body_delegate = create_getdate_body;
+ handler->body_delegate_baton = date_ctx;
+
+ *date_ctx->revision = SVN_INVALID_REVNUM;
+
+ /* ### use svn_ra_serf__error_on_status() ? */
+
+ return svn_error_trace(svn_ra_serf__context_run_one(handler, pool));
+}
diff --git a/subversion/libsvn_ra_serf/getlocations.c b/subversion/libsvn_ra_serf/getlocations.c
new file mode 100644
index 0000000..d3e3175
--- /dev/null
+++ b/subversion/libsvn_ra_serf/getlocations.c
@@ -0,0 +1,201 @@
+/*
+ * getlocations.c : entry point for get_locations RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_xml.h"
+#include "svn_private_config.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum loc_state_e {
+ INITIAL = 0,
+ REPORT,
+ LOCATION
+};
+
+typedef struct loc_context_t {
+ /* pool to allocate memory from */
+ apr_pool_t *pool;
+
+ /* parameters set by our caller */
+ const char *path;
+ const apr_array_header_t *location_revisions;
+ svn_revnum_t peg_revision;
+
+ /* Returned location hash */
+ apr_hash_t *paths;
+
+} loc_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t getloc_ttable[] = {
+ { INITIAL, S_, "get-locations-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, S_, "location", LOCATION,
+ FALSE, { "?rev", "?path", NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+getloc_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ loc_context_t *loc_ctx = baton;
+ const char *revstr;
+ const char *path;
+
+ SVN_ERR_ASSERT(leaving_state == LOCATION);
+
+ revstr = svn_hash_gets(attrs, "rev");
+ path = svn_hash_gets(attrs, "path");
+ if (revstr != NULL && path != NULL)
+ {
+ svn_revnum_t rev = SVN_STR_TO_REV(revstr);
+ apr_hash_set(loc_ctx->paths,
+ apr_pmemdup(loc_ctx->pool, &rev, sizeof(rev)), sizeof(rev),
+ apr_pstrdup(loc_ctx->pool, path));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_get_locations_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ loc_context_t *loc_ctx = baton;
+ int i;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:get-locations",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ "xmlns:D", "DAV:",
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", loc_ctx->path,
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:peg-revision", apr_ltoa(pool, loc_ctx->peg_revision),
+ alloc);
+
+ for (i = 0; i < loc_ctx->location_revisions->nelts; i++)
+ {
+ svn_revnum_t rev = APR_ARRAY_IDX(loc_ctx->location_revisions, i, svn_revnum_t);
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:location-revision", apr_ltoa(pool, rev),
+ alloc);
+ }
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:get-locations");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_locations(svn_ra_session_t *ra_session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool)
+{
+ loc_context_t *loc_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ loc_ctx = apr_pcalloc(pool, sizeof(*loc_ctx));
+ loc_ctx->pool = pool;
+ loc_ctx->path = path;
+ loc_ctx->peg_revision = peg_revision;
+ loc_ctx->location_revisions = location_revisions;
+ loc_ctx->paths = apr_hash_make(loc_ctx->pool);
+
+ *locations = loc_ctx->paths;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, peg_revision,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(getloc_ttable,
+ NULL, getloc_closed, NULL,
+ loc_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_delegate = create_get_locations_body;
+ handler->body_delegate_baton = loc_ctx;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ req_url,
+ handler->location),
+ err));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/getlocationsegments.c b/subversion/libsvn_ra_serf/getlocationsegments.c
new file mode 100644
index 0000000..d2b69bf
--- /dev/null
+++ b/subversion/libsvn_ra_serf/getlocationsegments.c
@@ -0,0 +1,206 @@
+/*
+ * getlocationsegments.c : entry point for get_location_segments
+ * RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_xml.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+
+typedef struct gls_context_t {
+ /* parameters set by our caller */
+ svn_revnum_t peg_revision;
+ svn_revnum_t start_rev;
+ svn_revnum_t end_rev;
+ const char *path;
+
+ /* location segment callback function/baton */
+ svn_location_segment_receiver_t receiver;
+ void *receiver_baton;
+
+} gls_context_t;
+
+enum {
+ INITIAL = 0,
+ REPORT,
+ SEGMENT
+};
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t gls_ttable[] = {
+ { INITIAL, S_, "get-location-segments-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, S_, "location-segment", SEGMENT,
+ FALSE, { "?path", "range-start", "range-end", NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+gls_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ gls_context_t *gls_ctx = baton;
+ const char *path;
+ const char *start_str;
+ const char *end_str;
+ svn_location_segment_t segment;
+
+ SVN_ERR_ASSERT(leaving_state == SEGMENT);
+
+ path = svn_hash_gets(attrs, "path");
+ start_str = svn_hash_gets(attrs, "range-start");
+ end_str = svn_hash_gets(attrs, "range-end");
+
+ /* The transition table said these must exist. */
+ SVN_ERR_ASSERT(start_str && end_str);
+
+ segment.path = path; /* may be NULL */
+ segment.range_start = SVN_STR_TO_REV(start_str);
+ segment.range_end = SVN_STR_TO_REV(end_str);
+ SVN_ERR(gls_ctx->receiver(&segment, gls_ctx->receiver_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_gls_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ gls_context_t *gls_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:get-location-segments",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", gls_ctx->path,
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:peg-revision",
+ apr_ltoa(pool, gls_ctx->peg_revision),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:start-revision",
+ apr_ltoa(pool, gls_ctx->start_rev),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:end-revision",
+ apr_ltoa(pool, gls_ctx->end_rev),
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:get-location-segments");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_location_segments(svn_ra_session_t *ra_session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ gls_context_t *gls_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ gls_ctx = apr_pcalloc(pool, sizeof(*gls_ctx));
+ gls_ctx->path = path;
+ gls_ctx->peg_revision = peg_revision;
+ gls_ctx->start_rev = start_rev;
+ gls_ctx->end_rev = end_rev;
+ gls_ctx->receiver = receiver;
+ gls_ctx->receiver_baton = receiver_baton;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, peg_revision,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(gls_ttable,
+ NULL, gls_closed, NULL,
+ gls_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_delegate = create_gls_body;
+ handler->body_delegate_baton = gls_ctx;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ err = svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err);
+
+ if (err && (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, NULL);
+
+ return svn_error_trace(err);
+}
diff --git a/subversion/libsvn_ra_serf/getlocks.c b/subversion/libsvn_ra_serf/getlocks.c
new file mode 100644
index 0000000..61b8b8c
--- /dev/null
+++ b/subversion/libsvn_ra_serf/getlocks.c
@@ -0,0 +1,277 @@
+/*
+ * getlocks.c : entry point for get_locks RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_time.h"
+#include "svn_xml.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_fspath.h"
+#include "svn_private_config.h"
+
+#include "../libsvn_ra/ra_loader.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum {
+ INITIAL = 0,
+ REPORT,
+ LOCK,
+ PATH,
+ TOKEN,
+ OWNER,
+ COMMENT,
+ CREATION_DATE,
+ EXPIRATION_DATE
+};
+
+typedef struct lock_context_t {
+ apr_pool_t *pool;
+
+ /* target and requested depth of the operation. */
+ const char *path;
+ svn_depth_t requested_depth;
+
+ /* return hash */
+ apr_hash_t *hash;
+
+} lock_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t getlocks_ttable[] = {
+ { INITIAL, S_, "get-locks-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, S_, "lock", LOCK,
+ FALSE, { NULL }, TRUE },
+
+ { LOCK, S_, "path", PATH,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, "token", TOKEN,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, "owner", OWNER,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, "comment", COMMENT,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, SVN_DAV__CREATIONDATE, CREATION_DATE,
+ TRUE, { NULL }, TRUE },
+
+ { LOCK, S_, "expirationdate", EXPIRATION_DATE,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+getlocks_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ lock_context_t *lock_ctx = baton;
+
+ if (leaving_state == LOCK)
+ {
+ const char *path = svn_hash_gets(attrs, "path");
+ svn_boolean_t save_lock = FALSE;
+
+ /* Filter out unwanted paths. Since Subversion only allows
+ locks on files, we can treat depth=immediates the same as
+ depth=files for filtering purposes. Meaning, we'll keep
+ this lock if:
+
+ a) its path is the very path we queried, or
+ b) we've asked for a fully recursive answer, or
+ c) we've asked for depth=files or depth=immediates, and this
+ lock is on an immediate child of our query path.
+ */
+ if (strcmp(lock_ctx->path, path) == 0
+ || lock_ctx->requested_depth == svn_depth_infinity)
+ {
+ save_lock = TRUE;
+ }
+ else if (lock_ctx->requested_depth == svn_depth_files
+ || lock_ctx->requested_depth == svn_depth_immediates)
+ {
+ const char *relpath = svn_fspath__skip_ancestor(lock_ctx->path,
+ path);
+ if (relpath && (svn_path_component_count(relpath) == 1))
+ save_lock = TRUE;
+ }
+
+ if (save_lock)
+ {
+ /* We get to put the structure on the stack rather than using
+ svn_lock_create(). Bwahahaha.... */
+ svn_lock_t lock = { 0 };
+ const char *date;
+ svn_lock_t *result_lock;
+
+ /* Note: these "attributes" came from child elements. Some of
+ them may have not been sent, so the value will be NULL. */
+
+ lock.path = path;
+ lock.token = svn_hash_gets(attrs, "token");
+ lock.owner = svn_hash_gets(attrs, "owner");
+ lock.comment = svn_hash_gets(attrs, "comment");
+
+ date = svn_hash_gets(attrs, SVN_DAV__CREATIONDATE);
+ if (date)
+ SVN_ERR(svn_time_from_cstring(&lock.creation_date, date,
+ scratch_pool));
+
+ date = svn_hash_gets(attrs, "expirationdate");
+ if (date)
+ SVN_ERR(svn_time_from_cstring(&lock.expiration_date, date,
+ scratch_pool));
+
+ result_lock = svn_lock_dup(&lock, lock_ctx->pool);
+ svn_hash_sets(lock_ctx->hash, result_lock->path, result_lock);
+ }
+ }
+ else
+ {
+ const char *name;
+
+ SVN_ERR_ASSERT(cdata != NULL);
+
+ if (leaving_state == PATH)
+ name = "path";
+ else if (leaving_state == TOKEN)
+ name = "token";
+ else if (leaving_state == OWNER)
+ name = "owner";
+ else if (leaving_state == COMMENT)
+ name = "comment";
+ else if (leaving_state == CREATION_DATE)
+ name = SVN_DAV__CREATIONDATE;
+ else if (leaving_state == EXPIRATION_DATE)
+ name = "expirationdate";
+ else
+ SVN_ERR_MALFUNCTION();
+
+ /* Store the lock information onto the LOCK elemstate. */
+ svn_ra_serf__xml_note(xes, LOCK, name, cdata->data);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_getlocks_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ lock_context_t *lock_ctx = baton;
+ serf_bucket_t *buckets;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(
+ buckets, alloc, "S:get-locks-report", "xmlns:S", SVN_XML_NAMESPACE,
+ "depth", svn_depth_to_word(lock_ctx->requested_depth), NULL);
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "S:get-locks-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool)
+{
+ lock_context_t *lock_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url, *rel_path;
+
+ req_url = svn_path_url_add_component2(session->session_url.path, path, pool);
+ SVN_ERR(svn_ra_serf__get_relative_path(&rel_path, req_url, session,
+ NULL, pool));
+
+ lock_ctx = apr_pcalloc(pool, sizeof(*lock_ctx));
+ lock_ctx->pool = pool;
+ lock_ctx->path = apr_pstrcat(pool, "/", rel_path, (char *)NULL);
+ lock_ctx->requested_depth = depth;
+ lock_ctx->hash = apr_hash_make(pool);
+
+ xmlctx = svn_ra_serf__xml_context_create(getlocks_ttable,
+ NULL, getlocks_closed, NULL,
+ lock_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->body_delegate = create_getlocks_body;
+ handler->body_delegate_baton = lock_ctx;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+
+ /* We get a 404 when a path doesn't exist in HEAD, but it might
+ have existed earlier (E.g. 'svn ls http://s/svn/trunk/file@1' */
+ if (handler->sline.code != 404)
+ {
+ SVN_ERR(svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location));
+ }
+
+ *locks = lock_ctx->hash;
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/inherited_props.c b/subversion/libsvn_ra_serf/inherited_props.c
new file mode 100644
index 0000000..9283a5a
--- /dev/null
+++ b/subversion/libsvn_ra_serf/inherited_props.c
@@ -0,0 +1,344 @@
+/*
+ * inherited_props.c : ra_serf implementation of svn_ra_get_inherited_props
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_tables.h>
+#include <apr_xml.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_ra.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_base64.h"
+
+#include "private/svn_dav_protocol.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_private_config.h"
+#include "ra_serf.h"
+
+
+/* The current state of our XML parsing. */
+typedef enum iprops_state_e {
+ NONE = 0,
+ IPROPS_REPORT,
+ IPROPS_ITEM,
+ IPROPS_PATH,
+ IPROPS_PROPNAME,
+ IPROPS_PROPVAL
+} iprops_state_e;
+
+/* Struct for accumulating inherited props. */
+typedef struct iprops_context_t {
+ /* The depth-first ordered array of svn_prop_inherited_item_t *
+ structures we are building. */
+ apr_array_header_t *iprops;
+
+ /* Pool in which to allocate elements of IPROPS. */
+ apr_pool_t *pool;
+
+ /* The repository's root URL. */
+ const char *repos_root_url;
+
+ /* Current CDATA values*/
+ svn_stringbuf_t *curr_path;
+ svn_stringbuf_t *curr_propname;
+ svn_stringbuf_t *curr_propval;
+ const char *curr_prop_val_encoding;
+
+ /* Current element in IPROPS. */
+ svn_prop_inherited_item_t *curr_iprop;
+
+ /* Serf context completion flag for svn_ra_serf__context_run_wait() */
+ svn_boolean_t done;
+
+ /* Path we are finding inherited properties for. This is relative to
+ the RA session passed to svn_ra_serf__get_inherited_props. */
+ const char *path;
+ /* The revision of PATH*/
+ svn_revnum_t revision;
+} iprops_context_t;
+
+static svn_error_t *
+start_element(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ iprops_context_t *iprops_ctx = parser->user_data;
+ iprops_state_e state;
+
+ state = parser->state->current_state;
+ if (state == NONE
+ && strcmp(name.name, SVN_DAV__INHERITED_PROPS_REPORT) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_REPORT);
+ }
+ else if (state == IPROPS_REPORT &&
+ strcmp(name.name, SVN_DAV__IPROP_ITEM) == 0)
+ {
+ svn_stringbuf_setempty(iprops_ctx->curr_path);
+ svn_stringbuf_setempty(iprops_ctx->curr_propname);
+ svn_stringbuf_setempty(iprops_ctx->curr_propval);
+ iprops_ctx->curr_prop_val_encoding = NULL;
+ iprops_ctx->curr_iprop = NULL;
+ svn_ra_serf__xml_push_state(parser, IPROPS_ITEM);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PROPVAL) == 0)
+ {
+ const char *prop_val_encoding = svn_xml_get_attr_value("encoding",
+ attrs);
+ iprops_ctx->curr_prop_val_encoding = apr_pstrdup(iprops_ctx->pool,
+ prop_val_encoding);
+ svn_ra_serf__xml_push_state(parser, IPROPS_PROPVAL);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PATH) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_PATH);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PROPNAME) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_PROPNAME);
+ }
+ else if (state == IPROPS_ITEM &&
+ strcmp(name.name, SVN_DAV__IPROP_PROPVAL) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, IPROPS_PROPVAL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_element(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ iprops_context_t *iprops_ctx = parser->user_data;
+ iprops_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == IPROPS_REPORT &&
+ strcmp(name.name, SVN_DAV__INHERITED_PROPS_REPORT) == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_PATH
+ && strcmp(name.name, SVN_DAV__IPROP_PATH) == 0)
+ {
+ iprops_ctx->curr_iprop = apr_palloc(
+ iprops_ctx->pool, sizeof(svn_prop_inherited_item_t));
+
+ iprops_ctx->curr_iprop->path_or_url =
+ svn_path_url_add_component2(iprops_ctx->repos_root_url,
+ iprops_ctx->curr_path->data,
+ iprops_ctx->pool);
+ iprops_ctx->curr_iprop->prop_hash = apr_hash_make(iprops_ctx->pool);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_PROPVAL
+ && strcmp(name.name, SVN_DAV__IPROP_PROPVAL) == 0)
+ {
+ const svn_string_t *prop_val;
+
+ if (iprops_ctx->curr_prop_val_encoding)
+ {
+ svn_string_t encoded_prop_val;
+
+ if (strcmp(iprops_ctx->curr_prop_val_encoding, "base64") != 0)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
+
+ encoded_prop_val.data = iprops_ctx->curr_propval->data;
+ encoded_prop_val.len = iprops_ctx->curr_propval->len;
+ prop_val = svn_base64_decode_string(&encoded_prop_val,
+ iprops_ctx->pool);
+ }
+ else
+ {
+ prop_val = svn_string_create_from_buf(iprops_ctx->curr_propval,
+ iprops_ctx->pool);
+ }
+
+ svn_hash_sets(iprops_ctx->curr_iprop->prop_hash,
+ apr_pstrdup(iprops_ctx->pool,
+ iprops_ctx->curr_propname->data),
+ prop_val);
+ /* Clear current propname and propval in the event there are
+ multiple properties on the current path. */
+ svn_stringbuf_setempty(iprops_ctx->curr_propname);
+ svn_stringbuf_setempty(iprops_ctx->curr_propval);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_PROPNAME
+ && strcmp(name.name, SVN_DAV__IPROP_PROPNAME) == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IPROPS_ITEM
+ && strcmp(name.name, SVN_DAV__IPROP_ITEM) == 0)
+ {
+ APR_ARRAY_PUSH(iprops_ctx->iprops, svn_prop_inherited_item_t *) =
+ iprops_ctx->curr_iprop;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+cdata_handler(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ iprops_context_t *iprops_ctx = parser->user_data;
+ iprops_state_e state = parser->state->current_state;
+
+ switch (state)
+ {
+ case IPROPS_PATH:
+ svn_stringbuf_appendbytes(iprops_ctx->curr_path, data, len);
+ break;
+
+ case IPROPS_PROPNAME:
+ svn_stringbuf_appendbytes(iprops_ctx->curr_propname, data, len);
+ break;
+
+ case IPROPS_PROPVAL:
+ svn_stringbuf_appendbytes(iprops_ctx->curr_propval, data, len);
+ break;
+
+ default:
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_iprops_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ iprops_context_t *iprops_ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__INHERITED_PROPS_REPORT,
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:" SVN_DAV__REVISION,
+ apr_ltoa(pool, iprops_ctx->revision),
+ alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__PATH,
+ iprops_ctx->path, alloc);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__INHERITED_PROPS_REPORT);
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+/* Request a inherited-props-report from the URL attached to RA_SESSION,
+ and fill the IPROPS array hash with the results. */
+svn_error_t *
+svn_ra_serf__get_inherited_props(svn_ra_session_t *ra_session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ iprops_context_t *iprops_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ const char *req_url;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url,
+ NULL /* latest_revnum */,
+ session,
+ NULL /* conn */,
+ NULL /* url */,
+ revision,
+ result_pool, scratch_pool));
+
+ SVN_ERR_ASSERT(session->repos_root_str);
+
+ iprops_ctx = apr_pcalloc(scratch_pool, sizeof(*iprops_ctx));
+ iprops_ctx->done = FALSE;
+ iprops_ctx->repos_root_url = session->repos_root_str;
+ iprops_ctx->pool = result_pool;
+ iprops_ctx->curr_path = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_propname = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_propval = svn_stringbuf_create_empty(scratch_pool);
+ iprops_ctx->curr_iprop = NULL;
+ iprops_ctx->iprops = apr_array_make(result_pool, 1,
+ sizeof(svn_prop_inherited_item_t *));
+ iprops_ctx->path = path;
+ iprops_ctx->revision = revision;
+
+ handler = apr_pcalloc(scratch_pool, sizeof(*handler));
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->conn = session->conns[0];
+ handler->session = session;
+ handler->body_delegate = create_iprops_body;
+ handler->body_delegate_baton = iprops_ctx;
+ handler->body_type = "text/xml";
+ handler->handler_pool = scratch_pool;
+
+ parser_ctx = apr_pcalloc(scratch_pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = scratch_pool;
+ parser_ctx->user_data = iprops_ctx;
+ parser_ctx->start = start_element;
+ parser_ctx->end = end_element;
+ parser_ctx->cdata = cdata_handler;
+ parser_ctx->done = &iprops_ctx->done;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ err = svn_ra_serf__context_run_one(handler, scratch_pool);
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err));
+
+ if (iprops_ctx->done)
+ *iprops = iprops_ctx->iprops;
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/locks.c b/subversion/libsvn_ra_serf/locks.c
new file mode 100644
index 0000000..db2d371
--- /dev/null
+++ b/subversion/libsvn_ra_serf/locks.c
@@ -0,0 +1,654 @@
+/*
+ * locks.c : entry point for locking RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_dav.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_config.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum {
+ INITIAL = 0,
+ MULTISTATUS,
+ RESPONSE,
+ PROPSTAT,
+ PROP,
+ LOCK_DISCOVERY,
+ ACTIVE_LOCK,
+ LOCK_TYPE,
+ LOCK_SCOPE,
+ DEPTH,
+ TIMEOUT,
+ LOCK_TOKEN,
+ OWNER,
+ HREF
+};
+
+typedef struct lock_info_t {
+ apr_pool_t *pool;
+
+ const char *path;
+
+ svn_lock_t *lock;
+
+ svn_boolean_t force;
+ svn_revnum_t revision;
+
+ svn_boolean_t read_headers;
+
+ svn_ra_serf__handler_t *handler;
+
+ /* The expat handler. We wrap this to do a bit more work. */
+ svn_ra_serf__response_handler_t inner_handler;
+ void *inner_baton;
+
+} lock_info_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t locks_ttable[] = {
+ /* The INITIAL state can transition into D:prop (LOCK) or
+ to D:multistatus (PROPFIND) */
+ { INITIAL, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+ { INITIAL, D_, "multistatus", MULTISTATUS,
+ FALSE, { NULL }, FALSE },
+
+ { MULTISTATUS, D_, "response", RESPONSE,
+ FALSE, { NULL }, FALSE },
+
+ { RESPONSE, D_, "propstat", PROPSTAT,
+ FALSE, { NULL }, FALSE },
+
+ { PROPSTAT, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+
+ { PROP, D_, "lockdiscovery", LOCK_DISCOVERY,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK,
+ FALSE, { NULL }, FALSE },
+
+#if 0
+ /* ### we don't really need to parse locktype/lockscope. we know what
+ ### the values are going to be. we *could* validate that the only
+ ### possible children are D:write and D:exclusive. we'd need to
+ ### modify the state transition to tell us about all children
+ ### (ie. maybe support "*" for the name) and then validate. but it
+ ### just isn't important to validate, so disable this for now... */
+
+ { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_TYPE, D_, "write", WRITE,
+ FALSE, { NULL }, TRUE },
+
+ { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE,
+ FALSE, { NULL }, TRUE },
+#endif /* 0 */
+
+ { ACTIVE_LOCK, D_, "timeout", TIMEOUT,
+ TRUE, { NULL }, TRUE },
+
+ { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN,
+ FALSE, { NULL }, FALSE },
+
+ { LOCK_TOKEN, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { ACTIVE_LOCK, D_, "owner", OWNER,
+ TRUE, { NULL }, TRUE },
+
+ /* ACTIVE_LOCK has a D:depth child, but we can ignore that. */
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+locks_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ lock_info_t *lock_ctx = baton;
+
+ if (leaving_state == TIMEOUT)
+ {
+ if (strcmp(cdata->data, "Infinite") == 0)
+ lock_ctx->lock->expiration_date = 0;
+ else
+ SVN_ERR(svn_time_from_cstring(&lock_ctx->lock->creation_date,
+ cdata->data, lock_ctx->pool));
+ }
+ else if (leaving_state == HREF)
+ {
+ if (cdata->len)
+ {
+ char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len);
+
+ apr_collapse_spaces(buf, buf);
+ lock_ctx->lock->token = buf;
+ }
+ }
+ else if (leaving_state == OWNER)
+ {
+ if (cdata->len)
+ {
+ lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool,
+ cdata->data, cdata->len);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+set_lock_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ lock_info_t *lock_ctx = baton;
+
+ if (lock_ctx->force)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_LOCK_STEAL);
+ }
+
+ if (SVN_IS_VALID_REVNUM(lock_ctx->revision))
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
+ apr_ltoa(pool, lock_ctx->revision));
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Register an error within the session. If something is already there,
+ then it will take precedence. */
+static svn_error_t *
+determine_error(svn_ra_serf__handler_t *handler,
+ svn_error_t *err)
+{
+ {
+ apr_status_t errcode;
+
+ if (handler->sline.code == 423)
+ errcode = SVN_ERR_FS_PATH_ALREADY_LOCKED;
+ else if (handler->sline.code == 403)
+ errcode = SVN_ERR_RA_DAV_FORBIDDEN;
+ else
+ return err;
+
+ /* Client-side or server-side error already. Return it. */
+ if (err != NULL)
+ return err;
+
+ /* The server did not send us a detailed human-readable error.
+ Provide a generic error. */
+ err = svn_error_createf(errcode, NULL,
+ _("Lock request failed: %d %s"),
+ handler->sline.code,
+ handler->sline.reason);
+ }
+
+ return err;
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+handle_lock(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ lock_info_t *ctx = handler_baton;
+
+ /* 403 (Forbidden) when a lock doesn't exist.
+ 423 (Locked) when a lock already exists. */
+ if (ctx->handler->sline.code == 403
+ || ctx->handler->sline.code == 423)
+ {
+ /* Go look in the body for a server-provided error. This will
+ reset flags for the core handler to Do The Right Thing. We
+ won't be back to this handler again. */
+ return svn_error_trace(svn_ra_serf__expect_empty_body(
+ request, response, ctx->handler, pool));
+ }
+
+ if (!ctx->read_headers)
+ {
+ serf_bucket_t *headers;
+ const char *val;
+
+ headers = serf_bucket_response_get_headers(response);
+
+ val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
+ if (val)
+ {
+ ctx->lock->owner = apr_pstrdup(ctx->pool, val);
+ }
+
+ val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
+ if (val)
+ {
+ SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
+ ctx->pool));
+ }
+
+ ctx->read_headers = TRUE;
+ }
+
+ return ctx->inner_handler(request, response, ctx->inner_baton, pool);
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_getlock_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(buckets, alloc);
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "propfind",
+ "xmlns", "DAV:",
+ NULL);
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "prop", NULL);
+ svn_ra_serf__add_tag_buckets(buckets, "lockdiscovery", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "prop");
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "propfind");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t*
+setup_getlock_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_headers_setn(headers, "Depth", "0");
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_lock_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ lock_info_t *ctx = baton;
+ serf_bucket_t *buckets;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(buckets, alloc);
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo",
+ "xmlns", "DAV:",
+ NULL);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", NULL);
+ svn_ra_serf__add_tag_buckets(buckets, "exclusive", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope");
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", NULL);
+ svn_ra_serf__add_tag_buckets(buckets, "write", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype");
+
+ if (ctx->lock->comment)
+ {
+ svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment,
+ alloc);
+ }
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ lock_info_t *lock_ctx;
+ const char *req_url;
+ svn_error_t *err;
+
+ req_url = svn_path_url_add_component2(session->session_url.path, path, pool);
+
+ lock_ctx = apr_pcalloc(pool, sizeof(*lock_ctx));
+
+ lock_ctx->pool = pool;
+ lock_ctx->path = req_url;
+ lock_ctx->lock = svn_lock_create(pool);
+ lock_ctx->lock->path = apr_pstrdup(pool, path); /* be sure */
+
+ xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
+ NULL, locks_closed, NULL,
+ lock_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "PROPFIND";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->body_delegate = create_getlock_body;
+ handler->body_delegate_baton = lock_ctx;
+
+ handler->header_delegate = setup_getlock_headers;
+ handler->header_delegate_baton = lock_ctx;
+
+ lock_ctx->inner_handler = handler->response_handler;
+ lock_ctx->inner_baton = handler->response_baton;
+ handler->response_handler = handle_lock;
+ handler->response_baton = lock_ctx;
+
+ lock_ctx->handler = handler;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+ err = determine_error(handler, err);
+
+ if (handler->sline.code == 404)
+ {
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, err,
+ _("Malformed URL for repository"));
+ }
+ if (err)
+ {
+ /* TODO Shh. We're telling a white lie for now. */
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
+ _("Server does not support locking features"));
+ }
+
+ *lock = lock_ctx->lock;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__lock(svn_ra_session_t *ra_session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* ### TODO for issue 2263: Send all the locks over the wire at once. This
+ ### loop is just a temporary shim.
+ ### an alternative, which is backwards-compat with all servers is to
+ ### pipeline these requests. ie. stop using run_wait/run_one. */
+
+ for (hi = apr_hash_first(scratch_pool, path_revs);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+ lock_info_t *lock_ctx;
+ svn_error_t *err;
+ svn_error_t *new_err = NULL;
+
+ svn_pool_clear(iterpool);
+
+ lock_ctx = apr_pcalloc(iterpool, sizeof(*lock_ctx));
+
+ lock_ctx->pool = iterpool;
+ lock_ctx->path = svn__apr_hash_index_key(hi);
+ lock_ctx->revision = *((svn_revnum_t*)svn__apr_hash_index_val(hi));
+ lock_ctx->lock = svn_lock_create(iterpool);
+ lock_ctx->lock->path = lock_ctx->path;
+ lock_ctx->lock->comment = comment;
+
+ lock_ctx->force = force;
+ req_url = svn_path_url_add_component2(session->session_url.path,
+ lock_ctx->path, iterpool);
+
+ xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
+ NULL, locks_closed, NULL,
+ lock_ctx,
+ iterpool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, iterpool);
+
+ handler->method = "LOCK";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->header_delegate = set_lock_headers;
+ handler->header_delegate_baton = lock_ctx;
+
+ handler->body_delegate = create_lock_body;
+ handler->body_delegate_baton = lock_ctx;
+
+ lock_ctx->inner_handler = handler->response_handler;
+ lock_ctx->inner_baton = handler->response_baton;
+ handler->response_handler = handle_lock;
+ handler->response_baton = lock_ctx;
+
+ lock_ctx->handler = handler;
+
+ err = svn_ra_serf__context_run_one(handler, iterpool);
+ err = determine_error(handler, err);
+
+ if (lock_func)
+ new_err = lock_func(lock_baton, lock_ctx->path, TRUE, lock_ctx->lock,
+ err, iterpool);
+ svn_error_clear(err);
+
+ SVN_ERR(new_err);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+struct unlock_context_t {
+ const char *token;
+ svn_boolean_t force;
+};
+
+static svn_error_t *
+set_unlock_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ struct unlock_context_t *ctx = baton;
+
+ serf_bucket_headers_set(headers, "Lock-Token", ctx->token);
+ if (ctx->force)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_LOCK_BREAK);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__unlock(svn_ra_session_t *ra_session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* ### TODO for issue 2263: Send all the locks over the wire at once. This
+ ### loop is just a temporary shim.
+ ### an alternative, which is backwards-compat with all servers is to
+ ### pipeline these requests. ie. stop using run_wait/run_one. */
+
+ for (hi = apr_hash_first(scratch_pool, path_tokens);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_ra_serf__handler_t *handler;
+ const char *req_url, *path, *token;
+ svn_lock_t *existing_lock = NULL;
+ struct unlock_context_t unlock_ctx;
+ svn_error_t *err = NULL;
+ svn_error_t *new_err = NULL;
+
+
+ svn_pool_clear(iterpool);
+
+ path = svn__apr_hash_index_key(hi);
+ token = svn__apr_hash_index_val(hi);
+
+ if (force && (!token || token[0] == '\0'))
+ {
+ SVN_ERR(svn_ra_serf__get_lock(ra_session, &existing_lock, path,
+ iterpool));
+ token = existing_lock->token;
+ if (!token)
+ {
+ err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, NULL,
+ _("'%s' is not locked in the repository"),
+ path);
+
+ if (lock_func)
+ {
+ svn_error_t *err2;
+ err2 = lock_func(lock_baton, path, FALSE, NULL, err,
+ iterpool);
+ svn_error_clear(err);
+ err = NULL;
+ if (err2)
+ return svn_error_trace(err2);
+ }
+ else
+ {
+ svn_error_clear(err);
+ err = NULL;
+ }
+ continue;
+ }
+ }
+
+ unlock_ctx.force = force;
+ unlock_ctx.token = apr_pstrcat(iterpool, "<", token, ">", (char *)NULL);
+
+ req_url = svn_path_url_add_component2(session->session_url.path, path,
+ iterpool);
+
+ handler = apr_pcalloc(iterpool, sizeof(*handler));
+
+ handler->handler_pool = iterpool;
+ handler->method = "UNLOCK";
+ handler->path = req_url;
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ handler->header_delegate = set_unlock_headers;
+ handler->header_delegate_baton = &unlock_ctx;
+
+ handler->response_handler = svn_ra_serf__expect_empty_body;
+ handler->response_baton = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, iterpool));
+
+ switch (handler->sline.code)
+ {
+ case 204:
+ break; /* OK */
+ case 403:
+ /* Api users expect this specific error code to detect failures */
+ err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
+ _("Unlock request failed: %d %s"),
+ handler->sline.code,
+ handler->sline.reason);
+ break;
+ default:
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Unlock request failed: %d %s"),
+ handler->sline.code,
+ handler->sline.reason);
+ }
+
+ if (lock_func)
+ new_err = lock_func(lock_baton, path, FALSE, existing_lock, err,
+ iterpool);
+
+ svn_error_clear(err);
+ SVN_ERR(new_err);
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/log.c b/subversion/libsvn_ra_serf/log.c
new file mode 100644
index 0000000..e44753b
--- /dev/null
+++ b/subversion/libsvn_ra_serf/log.c
@@ -0,0 +1,604 @@
+/*
+ * log.c : entry point for log RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_base64.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_path.h"
+#include "svn_props.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum {
+ INITIAL = 0,
+ REPORT,
+ ITEM,
+ VERSION,
+ CREATOR,
+ DATE,
+ COMMENT,
+ REVPROP,
+ HAS_CHILDREN,
+ ADDED_PATH,
+ REPLACED_PATH,
+ DELETED_PATH,
+ MODIFIED_PATH,
+ SUBTRACTIVE_MERGE
+};
+
+typedef struct log_context_t {
+ apr_pool_t *pool;
+
+ /* parameters set by our caller */
+ const apr_array_header_t *paths;
+ svn_revnum_t start;
+ svn_revnum_t end;
+ int limit;
+ svn_boolean_t changed_paths;
+ svn_boolean_t strict_node_history;
+ svn_boolean_t include_merged_revisions;
+ const apr_array_header_t *revprops;
+ int nest_level; /* used to track mergeinfo nesting levels */
+ int count; /* only incremented when nest_level == 0 */
+
+ /* Collect information for storage into a log entry. Most of the entry
+ members are collected by individual states. revprops and paths are
+ N datapoints per entry. */
+ apr_hash_t *collect_revprops;
+ apr_hash_t *collect_paths;
+
+ /* log receiver function and baton */
+ svn_log_entry_receiver_t receiver;
+ void *receiver_baton;
+
+ /* pre-1.5 compatibility */
+ svn_boolean_t want_author;
+ svn_boolean_t want_date;
+ svn_boolean_t want_message;
+} log_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t log_ttable[] = {
+ { INITIAL, S_, "log-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ /* Note that we have an opener here. We need to construct a new LOG_ENTRY
+ to record multiple paths. */
+ { REPORT, S_, "log-item", ITEM,
+ FALSE, { NULL }, TRUE },
+
+ { ITEM, D_, SVN_DAV__VERSION_NAME, VERSION,
+ TRUE, { NULL }, TRUE },
+
+ { ITEM, D_, "creator-displayname", CREATOR,
+ TRUE, { "?encoding", NULL }, TRUE },
+
+ { ITEM, S_, "date", DATE,
+ TRUE, { "?encoding", NULL }, TRUE },
+
+ { ITEM, D_, "comment", COMMENT,
+ TRUE, { "?encoding", NULL }, TRUE },
+
+ { ITEM, S_, "revprop", REVPROP,
+ TRUE, { "name", "?encoding", NULL }, TRUE },
+
+ { ITEM, S_, "has-children", HAS_CHILDREN,
+ FALSE, { NULL }, TRUE },
+
+ { ITEM, S_, "subtractive-merge", SUBTRACTIVE_MERGE,
+ FALSE, { NULL }, TRUE },
+
+ { ITEM, S_, "added-path", ADDED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods",
+ "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ITEM, S_, "replaced-path", REPLACED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods",
+ "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ITEM, S_, "deleted-path", DELETED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
+
+ { ITEM, S_, "modified-path", MODIFIED_PATH,
+ TRUE, { "?node-kind", "?text-mods", "?prop-mods", NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Store CDATA into REVPROPS, associated with PROPNAME. If ENCODING is not
+ NULL, then it must base "base64" and CDATA will be decoded first.
+
+ NOTE: PROPNAME must live longer than REVPROPS. */
+static svn_error_t *
+collect_revprop(apr_hash_t *revprops,
+ const char *propname,
+ const svn_string_t *cdata,
+ const char *encoding)
+{
+ apr_pool_t *result_pool = apr_hash_pool_get(revprops);
+ const svn_string_t *decoded;
+
+ if (encoding)
+ {
+ /* Check for a known encoding type. This is easy -- there's
+ only one. */
+ if (strcmp(encoding, "base64") != 0)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unsupported encoding '%s'"),
+ encoding);
+ }
+
+ decoded = svn_base64_decode_string(cdata, result_pool);
+ }
+ else
+ {
+ decoded = svn_string_dup(cdata, result_pool);
+ }
+
+ /* Caller has ensured PROPNAME has sufficient lifetime. */
+ svn_hash_sets(revprops, propname, decoded);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Record ACTION on the path in CDATA into PATHS. Other properties about
+ the action are pulled from ATTRS. */
+static svn_error_t *
+collect_path(apr_hash_t *paths,
+ char action,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs)
+{
+ apr_pool_t *result_pool = apr_hash_pool_get(paths);
+ svn_log_changed_path2_t *lcp;
+ const char *copyfrom_path;
+ const char *copyfrom_rev;
+ const char *path;
+
+ lcp = svn_log_changed_path2_create(result_pool);
+ lcp->action = action;
+ lcp->copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* COPYFROM_* are only recorded for ADDED_PATH and REPLACED_PATH. */
+ copyfrom_path = svn_hash_gets(attrs, "copyfrom-path");
+ copyfrom_rev = svn_hash_gets(attrs, "copyfrom-rev");
+ if (copyfrom_path && copyfrom_rev)
+ {
+ svn_revnum_t rev = SVN_STR_TO_REV(copyfrom_rev);
+
+ if (SVN_IS_VALID_REVNUM(rev))
+ {
+ lcp->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
+ lcp->copyfrom_rev = rev;
+ }
+ }
+
+ lcp->node_kind = svn_node_kind_from_word(svn_hash_gets(attrs, "node-kind"));
+ lcp->text_modified = svn_tristate__from_word(svn_hash_gets(attrs,
+ "text-mods"));
+ lcp->props_modified = svn_tristate__from_word(svn_hash_gets(attrs,
+ "prop-mods"));
+
+ path = apr_pstrmemdup(result_pool, cdata->data, cdata->len);
+ svn_hash_sets(paths, path, lcp);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+log_opened(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool)
+{
+ log_context_t *log_ctx = baton;
+
+ if (entered_state == ITEM)
+ {
+ apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes);
+
+ log_ctx->collect_revprops = apr_hash_make(state_pool);
+ log_ctx->collect_paths = apr_hash_make(state_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+log_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ log_context_t *log_ctx = baton;
+
+ if (leaving_state == ITEM)
+ {
+ svn_log_entry_t *log_entry;
+ const char *rev_str;
+
+ if (log_ctx->limit && (log_ctx->nest_level == 0)
+ && (++log_ctx->count > log_ctx->limit))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ log_entry = svn_log_entry_create(scratch_pool);
+
+ /* Pick up the paths from the context. These have the same lifetime
+ as this state. That is long enough for us to pass the paths to
+ the receiver callback. */
+ if (apr_hash_count(log_ctx->collect_paths) > 0)
+ {
+ log_entry->changed_paths = log_ctx->collect_paths;
+ log_entry->changed_paths2 = log_ctx->collect_paths;
+ }
+
+ /* ... and same story for the collected revprops. */
+ log_entry->revprops = log_ctx->collect_revprops;
+
+ log_entry->has_children = svn_hash__get_bool(attrs,
+ "has-children",
+ FALSE);
+ log_entry->subtractive_merge = svn_hash__get_bool(attrs,
+ "subtractive-merge",
+ FALSE);
+
+ rev_str = svn_hash_gets(attrs, "revision");
+ if (rev_str)
+ log_entry->revision = SVN_STR_TO_REV(rev_str);
+ else
+ log_entry->revision = SVN_INVALID_REVNUM;
+
+ /* Give the info to the reporter */
+ SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
+ log_entry,
+ scratch_pool));
+
+ if (log_entry->has_children)
+ {
+ log_ctx->nest_level++;
+ }
+ if (! SVN_IS_VALID_REVNUM(log_entry->revision))
+ {
+ SVN_ERR_ASSERT(log_ctx->nest_level);
+ log_ctx->nest_level--;
+ }
+
+ /* These hash tables are going to be unusable once this state's
+ pool is destroyed. But let's not leave stale pointers in
+ structures that have a longer life. */
+ log_ctx->collect_revprops = NULL;
+ log_ctx->collect_paths = NULL;
+ }
+ else if (leaving_state == VERSION)
+ {
+ svn_ra_serf__xml_note(xes, ITEM, "revision", cdata->data);
+ }
+ else if (leaving_state == CREATOR)
+ {
+ if (log_ctx->want_author)
+ {
+ SVN_ERR(collect_revprop(log_ctx->collect_revprops,
+ SVN_PROP_REVISION_AUTHOR,
+ cdata,
+ svn_hash_gets(attrs, "encoding")));
+ }
+ }
+ else if (leaving_state == DATE)
+ {
+ if (log_ctx->want_date)
+ {
+ SVN_ERR(collect_revprop(log_ctx->collect_revprops,
+ SVN_PROP_REVISION_DATE,
+ cdata,
+ svn_hash_gets(attrs, "encoding")));
+ }
+ }
+ else if (leaving_state == COMMENT)
+ {
+ if (log_ctx->want_message)
+ {
+ SVN_ERR(collect_revprop(log_ctx->collect_revprops,
+ SVN_PROP_REVISION_LOG,
+ cdata,
+ svn_hash_gets(attrs, "encoding")));
+ }
+ }
+ else if (leaving_state == REVPROP)
+ {
+ apr_pool_t *result_pool = apr_hash_pool_get(log_ctx->collect_revprops);
+
+ SVN_ERR(collect_revprop(
+ log_ctx->collect_revprops,
+ apr_pstrdup(result_pool,
+ svn_hash_gets(attrs, "name")),
+ cdata,
+ svn_hash_gets(attrs, "encoding")
+ ));
+ }
+ else if (leaving_state == HAS_CHILDREN)
+ {
+ svn_ra_serf__xml_note(xes, ITEM, "has-children", "yes");
+ }
+ else if (leaving_state == SUBTRACTIVE_MERGE)
+ {
+ svn_ra_serf__xml_note(xes, ITEM, "subtractive-merge", "yes");
+ }
+ else
+ {
+ char action;
+
+ if (leaving_state == ADDED_PATH)
+ action = 'A';
+ else if (leaving_state == REPLACED_PATH)
+ action = 'R';
+ else if (leaving_state == DELETED_PATH)
+ action = 'D';
+ else
+ {
+ SVN_ERR_ASSERT(leaving_state == MODIFIED_PATH);
+ action = 'M';
+ }
+
+ SVN_ERR(collect_path(log_ctx->collect_paths, action, cdata, attrs));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+create_log_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *buckets;
+ log_context_t *log_ctx = baton;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:log-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:start-revision",
+ apr_ltoa(pool, log_ctx->start),
+ alloc);
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:end-revision",
+ apr_ltoa(pool, log_ctx->end),
+ alloc);
+
+ if (log_ctx->limit)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:limit", apr_ltoa(pool, log_ctx->limit),
+ alloc);
+ }
+
+ if (log_ctx->changed_paths)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:discover-changed-paths", NULL,
+ alloc);
+ }
+
+ if (log_ctx->strict_node_history)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:strict-node-history", NULL,
+ alloc);
+ }
+
+ if (log_ctx->include_merged_revisions)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:include-merged-revisions", NULL,
+ alloc);
+ }
+
+ if (log_ctx->revprops)
+ {
+ int i;
+ for (i = 0; i < log_ctx->revprops->nelts; i++)
+ {
+ char *name = APR_ARRAY_IDX(log_ctx->revprops, i, char *);
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:revprop", name,
+ alloc);
+ }
+ if (log_ctx->revprops->nelts == 0)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:no-revprops", NULL,
+ alloc);
+ }
+ }
+ else
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:all-revprops", NULL,
+ alloc);
+ }
+
+ if (log_ctx->paths)
+ {
+ int i;
+ for (i = 0; i < log_ctx->paths->nelts; i++)
+ {
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", APR_ARRAY_IDX(log_ctx->paths, i,
+ const char*),
+ alloc);
+ }
+ }
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:encode-binary-props", NULL,
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:log-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_log(svn_ra_session_t *ra_session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool)
+{
+ log_context_t *log_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ svn_boolean_t want_custom_revprops;
+ svn_revnum_t peg_rev;
+ svn_error_t *err;
+ const char *req_url;
+
+ log_ctx = apr_pcalloc(pool, sizeof(*log_ctx));
+ log_ctx->pool = pool;
+ log_ctx->receiver = receiver;
+ log_ctx->receiver_baton = receiver_baton;
+ log_ctx->paths = paths;
+ log_ctx->start = start;
+ log_ctx->end = end;
+ log_ctx->limit = limit;
+ log_ctx->changed_paths = discover_changed_paths;
+ log_ctx->strict_node_history = strict_node_history;
+ log_ctx->include_merged_revisions = include_merged_revisions;
+ log_ctx->revprops = revprops;
+ log_ctx->nest_level = 0;
+
+ want_custom_revprops = FALSE;
+ if (revprops)
+ {
+ int i;
+ for (i = 0; i < revprops->nelts; i++)
+ {
+ char *name = APR_ARRAY_IDX(revprops, i, char *);
+ if (strcmp(name, SVN_PROP_REVISION_AUTHOR) == 0)
+ log_ctx->want_author = TRUE;
+ else if (strcmp(name, SVN_PROP_REVISION_DATE) == 0)
+ log_ctx->want_date = TRUE;
+ else if (strcmp(name, SVN_PROP_REVISION_LOG) == 0)
+ log_ctx->want_message = TRUE;
+ else
+ want_custom_revprops = TRUE;
+ }
+ }
+ else
+ {
+ log_ctx->want_author = log_ctx->want_date = log_ctx->want_message = TRUE;
+ want_custom_revprops = TRUE;
+ }
+
+ if (want_custom_revprops)
+ {
+ svn_boolean_t has_log_revprops;
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &has_log_revprops,
+ SVN_RA_CAPABILITY_LOG_REVPROPS, pool));
+ if (!has_log_revprops)
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL,
+ _("Server does not support custom revprops"
+ " via log"));
+ }
+ /* At this point, we may have a deleted file. So, we'll match ra_neon's
+ * behavior and use the larger of start or end as our 'peg' rev.
+ */
+ peg_rev = (start > end) ? start : end;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, peg_rev,
+ pool, pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(log_ttable,
+ log_opened, log_closed, NULL,
+ log_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_delegate = create_log_body;
+ handler->body_delegate_baton = log_ctx;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ req_url,
+ handler->location),
+ err));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/merge.c b/subversion/libsvn_ra_serf/merge.c
new file mode 100644
index 0000000..670e421
--- /dev/null
+++ b/subversion/libsvn_ra_serf/merge.c
@@ -0,0 +1,430 @@
+/*
+ * merge.c : MERGE response parsing functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_dirent_uri.h"
+#include "svn_props.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_fspath.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a MERGE.
+ */
+typedef enum merge_state_e {
+ INITIAL = 0,
+ MERGE_RESPONSE,
+ UPDATED_SET,
+ RESPONSE,
+ HREF,
+ PROPSTAT,
+ PROP,
+ RESOURCE_TYPE,
+ BASELINE,
+ COLLECTION,
+ SKIP_HREF,
+ CHECKED_IN,
+ VERSION_NAME,
+ DATE,
+ AUTHOR,
+ POST_COMMIT_ERR,
+
+ PROP_VAL
+} merge_state_e;
+
+
+/* Structure associated with a MERGE request. */
+typedef struct merge_context_t
+{
+ apr_pool_t *pool;
+
+ svn_ra_serf__session_t *session;
+ svn_ra_serf__handler_t *handler;
+
+ apr_hash_t *lock_tokens;
+ svn_boolean_t keep_locks;
+
+ const char *merge_resource_url; /* URL of resource to be merged. */
+ const char *merge_url; /* URL at which the MERGE request is aimed. */
+
+ svn_commit_info_t *commit_info;
+
+} merge_context_t;
+
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t merge_ttable[] = {
+ { INITIAL, D_, "merge-response", MERGE_RESPONSE,
+ FALSE, { NULL }, FALSE },
+
+ { MERGE_RESPONSE, D_, "updated-set", UPDATED_SET,
+ FALSE, { NULL }, FALSE },
+
+ { UPDATED_SET, D_, "response", RESPONSE,
+ FALSE, { NULL }, TRUE },
+
+ { RESPONSE, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { RESPONSE, D_, "propstat", PROPSTAT,
+ FALSE, { NULL }, FALSE },
+
+#if 0
+ /* Not needed. */
+ { PROPSTAT, D_, "status", STATUS,
+ FALSE, { NULL }, FALSE },
+#endif
+
+ { PROPSTAT, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+
+ { PROP, D_, "resourcetype", RESOURCE_TYPE,
+ FALSE, { NULL }, FALSE },
+
+ { RESOURCE_TYPE, D_, "baseline", BASELINE,
+ FALSE, { NULL }, TRUE },
+
+ { RESOURCE_TYPE, D_, "collection", COLLECTION,
+ FALSE, { NULL }, TRUE },
+
+ { PROP, D_, "checked-in", SKIP_HREF,
+ FALSE, { NULL }, FALSE },
+
+ { SKIP_HREF, D_, "href", CHECKED_IN,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, D_, SVN_DAV__VERSION_NAME, VERSION_NAME,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, D_, SVN_DAV__CREATIONDATE, DATE,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, D_, "creator-displayname", AUTHOR,
+ TRUE, { NULL }, TRUE },
+
+ { PROP, S_, "post-commit-err", POST_COMMIT_ERR,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+merge_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ merge_context_t *merge_ctx = baton;
+
+ if (leaving_state == RESPONSE)
+ {
+ const char *rtype;
+
+ rtype = svn_hash_gets(attrs, "resourcetype");
+
+ /* rtype can only be "baseline" or "collection" (or NULL). We can
+ keep this check simple. */
+ if (rtype && *rtype == 'b')
+ {
+ const char *rev_str;
+
+ rev_str = svn_hash_gets(attrs, "revision");
+ if (rev_str)
+ merge_ctx->commit_info->revision = SVN_STR_TO_REV(rev_str);
+ else
+ merge_ctx->commit_info->revision = SVN_INVALID_REVNUM;
+
+ merge_ctx->commit_info->date =
+ apr_pstrdup(merge_ctx->pool,
+ svn_hash_gets(attrs, "date"));
+
+ merge_ctx->commit_info->author =
+ apr_pstrdup(merge_ctx->pool,
+ svn_hash_gets(attrs, "author"));
+
+ merge_ctx->commit_info->post_commit_err =
+ apr_pstrdup(merge_ctx->pool,
+ svn_hash_gets(attrs, "post-commit-err"));
+ }
+ else
+ {
+ const char *href;
+
+ href = svn_urlpath__skip_ancestor(
+ merge_ctx->merge_url,
+ svn_hash_gets(attrs, "href"));
+
+ if (href == NULL)
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("A MERGE response for '%s' is not "
+ "a child of the destination ('%s')"),
+ href, merge_ctx->merge_url);
+
+ /* We now need to dive all the way into the WC to update the
+ base VCC url. */
+ if (!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(merge_ctx->session)
+ && merge_ctx->session->wc_callbacks->push_wc_prop)
+ {
+ const char *checked_in;
+ svn_string_t checked_in_str;
+
+ checked_in = svn_hash_gets(attrs, "checked-in");
+ checked_in_str.data = checked_in;
+ checked_in_str.len = strlen(checked_in);
+
+ SVN_ERR(merge_ctx->session->wc_callbacks->push_wc_prop(
+ merge_ctx->session->wc_callback_baton,
+ href,
+ SVN_RA_SERF__WC_CHECKED_IN_URL,
+ &checked_in_str,
+ scratch_pool));
+ }
+ }
+ }
+ else if (leaving_state == BASELINE)
+ {
+ svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "baseline");
+ }
+ else if (leaving_state == COLLECTION)
+ {
+ svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "collection");
+ }
+ else
+ {
+ const char *name;
+ const char *value = cdata->data;
+
+ if (leaving_state == HREF)
+ {
+ name = "href";
+ value = svn_urlpath__canonicalize(value, scratch_pool);
+ }
+ else if (leaving_state == CHECKED_IN)
+ {
+ name = "checked-in";
+ value = svn_urlpath__canonicalize(value, scratch_pool);
+ }
+ else if (leaving_state == VERSION_NAME)
+ name = "revision";
+ else if (leaving_state == DATE)
+ name = "date";
+ else if (leaving_state == AUTHOR)
+ name = "author";
+ else if (leaving_state == POST_COMMIT_ERR)
+ name = "post-commit-err";
+ else
+ SVN_ERR_MALFUNCTION();
+
+ svn_ra_serf__xml_note(xes, RESPONSE, name, value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+setup_merge_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ merge_context_t *ctx = baton;
+
+ if (!ctx->keep_locks)
+ {
+ serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
+ SVN_DAV_OPTION_RELEASE_LOCKS);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens,
+ const char *parent,
+ serf_bucket_t *body,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *hi;
+
+ if (!lock_tokens || apr_hash_count(lock_tokens) == 0)
+ return;
+
+ svn_ra_serf__add_open_tag_buckets(body, alloc,
+ "S:lock-token-list",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ for (hi = apr_hash_first(pool, lock_tokens);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+ svn_string_t path;
+
+ apr_hash_this(hi, &key, &klen, &val);
+
+ path.data = key;
+ path.len = klen;
+
+ if (parent && !svn_relpath_skip_ancestor(parent, key))
+ continue;
+
+ svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock", NULL);
+
+ svn_ra_serf__add_open_tag_buckets(body, alloc, "lock-path", NULL);
+ svn_ra_serf__add_cdata_len_buckets(body, alloc, path.data, path.len);
+ svn_ra_serf__add_close_tag_buckets(body, alloc, "lock-path");
+
+ svn_ra_serf__add_tag_buckets(body, "lock-token", val, alloc);
+
+ svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock");
+ }
+
+ svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock-token-list");
+}
+
+static svn_error_t*
+create_merge_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ merge_context_t *ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_xml_header_buckets(body_bkt, alloc);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:merge",
+ "xmlns:D", "DAV:",
+ NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:source", NULL);
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL);
+
+ svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
+ ctx->merge_resource_url,
+ strlen(ctx->merge_resource_url));
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href");
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:source");
+
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:no-auto-merge", NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:no-checkout", NULL, alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:checked-in", NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:" SVN_DAV__VERSION_NAME, NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:resourcetype", NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:" SVN_DAV__CREATIONDATE, NULL, alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "D:creator-displayname", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop");
+
+ svn_ra_serf__merge_lock_token_list(ctx->lock_tokens, NULL, body_bkt, alloc,
+ pool);
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:merge");
+
+ *bkt = body_bkt;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__run_merge(const svn_commit_info_t **commit_info,
+ int *response_code,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *merge_resource_url,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ merge_context_t *merge_ctx;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+
+ merge_ctx = apr_pcalloc(scratch_pool, sizeof(*merge_ctx));
+
+ merge_ctx->pool = result_pool;
+ merge_ctx->session = session;
+
+ merge_ctx->merge_resource_url = merge_resource_url;
+
+ merge_ctx->lock_tokens = lock_tokens;
+ merge_ctx->keep_locks = keep_locks;
+
+ merge_ctx->commit_info = svn_create_commit_info(result_pool);
+
+ merge_ctx->merge_url = session->session_url.path;
+
+ xmlctx = svn_ra_serf__xml_context_create(merge_ttable,
+ NULL, merge_closed, NULL,
+ merge_ctx,
+ scratch_pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool);
+
+ handler->method = "MERGE";
+ handler->path = merge_ctx->merge_url;
+ handler->body_delegate = create_merge_body;
+ handler->body_delegate_baton = merge_ctx;
+ handler->conn = conn;
+ handler->session = session;
+
+ handler->header_delegate = setup_merge_headers;
+ handler->header_delegate_baton = merge_ctx;
+
+ merge_ctx->handler = handler;
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+
+ *commit_info = merge_ctx->commit_info;
+ *response_code = handler->sline.code;
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/mergeinfo.c b/subversion/libsvn_ra_serf/mergeinfo.c
new file mode 100644
index 0000000..b0bf833
--- /dev/null
+++ b/subversion/libsvn_ra_serf/mergeinfo.c
@@ -0,0 +1,246 @@
+/*
+ * mergeinfo.c : entry point for mergeinfo RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_tables.h>
+#include <apr_xml.h>
+
+#include "svn_hash.h"
+#include "svn_mergeinfo.h"
+#include "svn_path.h"
+#include "svn_ra.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+
+#include "private/svn_dav_protocol.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_private_config.h"
+#include "ra_serf.h"
+
+
+
+
+/* The current state of our XML parsing. */
+typedef enum mergeinfo_state_e {
+ INITIAL = 0,
+ MERGEINFO_REPORT,
+ MERGEINFO_ITEM,
+ MERGEINFO_PATH,
+ MERGEINFO_INFO
+} mergeinfo_state_e;
+
+/* Baton for accumulating mergeinfo. RESULT_CATALOG stores the final
+ mergeinfo catalog result we are going to hand back to the caller of
+ get_mergeinfo. */
+typedef struct mergeinfo_context_t {
+ apr_pool_t *pool;
+ svn_mergeinfo_t result_catalog;
+ const apr_array_header_t *paths;
+ svn_revnum_t revision;
+ svn_mergeinfo_inheritance_t inherit;
+ svn_boolean_t include_descendants;
+} mergeinfo_context_t;
+
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t mergeinfo_ttable[] = {
+ { INITIAL, S_, SVN_DAV__MERGEINFO_REPORT, MERGEINFO_REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { MERGEINFO_REPORT, S_, SVN_DAV__MERGEINFO_ITEM, MERGEINFO_ITEM,
+ FALSE, { NULL }, TRUE },
+
+ { MERGEINFO_ITEM, S_, SVN_DAV__MERGEINFO_PATH, MERGEINFO_PATH,
+ TRUE, { NULL }, TRUE },
+
+ { MERGEINFO_ITEM, S_, SVN_DAV__MERGEINFO_INFO, MERGEINFO_INFO,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+mergeinfo_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ mergeinfo_context_t *mergeinfo_ctx = baton;
+
+ if (leaving_state == MERGEINFO_ITEM)
+ {
+ /* Placed here from the child elements. */
+ const char *path = svn_hash_gets(attrs, "path");
+ const char *info = svn_hash_gets(attrs, "info");
+
+ if (path != NULL && info != NULL)
+ {
+ svn_mergeinfo_t path_mergeinfo;
+
+ /* Correct for naughty servers that send "relative" paths
+ with leading slashes! */
+ if (path[0] == '/')
+ ++path;
+
+ SVN_ERR(svn_mergeinfo_parse(&path_mergeinfo, info,
+ mergeinfo_ctx->pool));
+
+ svn_hash_sets(mergeinfo_ctx->result_catalog,
+ apr_pstrdup(mergeinfo_ctx->pool, path),
+ path_mergeinfo);
+ }
+ }
+ else
+ {
+ SVN_ERR_ASSERT(leaving_state == MERGEINFO_PATH
+ || leaving_state == MERGEINFO_INFO);
+
+ /* Stash the value onto the parent MERGEINFO_ITEM. */
+ svn_ra_serf__xml_note(xes, MERGEINFO_ITEM,
+ leaving_state == MERGEINFO_PATH
+ ? "path"
+ : "info",
+ cdata->data);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+create_mergeinfo_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ mergeinfo_context_t *mergeinfo_ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__MERGEINFO_REPORT,
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:" SVN_DAV__REVISION,
+ apr_ltoa(pool, mergeinfo_ctx->revision),
+ alloc);
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__INHERIT,
+ svn_inheritance_to_word(mergeinfo_ctx->inherit),
+ alloc);
+ if (mergeinfo_ctx->include_descendants)
+ {
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:"
+ SVN_DAV__INCLUDE_DESCENDANTS,
+ "yes", alloc);
+ }
+
+ if (mergeinfo_ctx->paths)
+ {
+ int i;
+
+ for (i = 0; i < mergeinfo_ctx->paths->nelts; i++)
+ {
+ const char *this_path = APR_ARRAY_IDX(mergeinfo_ctx->paths,
+ i, const char *);
+
+ svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__PATH,
+ this_path, alloc);
+ }
+ }
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc,
+ "S:" SVN_DAV__MERGEINFO_REPORT);
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session,
+ svn_mergeinfo_catalog_t *catalog,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool)
+{
+ svn_error_t *err, *err2;
+ mergeinfo_context_t *mergeinfo_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *path;
+
+ *catalog = NULL;
+
+ SVN_ERR(svn_ra_serf__get_stable_url(&path, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ NULL /* url */, revision,
+ pool, pool));
+
+ mergeinfo_ctx = apr_pcalloc(pool, sizeof(*mergeinfo_ctx));
+ mergeinfo_ctx->pool = pool;
+ mergeinfo_ctx->result_catalog = apr_hash_make(pool);
+ mergeinfo_ctx->paths = paths;
+ mergeinfo_ctx->revision = revision;
+ mergeinfo_ctx->inherit = inherit;
+ mergeinfo_ctx->include_descendants = include_descendants;
+
+ xmlctx = svn_ra_serf__xml_context_create(mergeinfo_ttable,
+ NULL, mergeinfo_closed, NULL,
+ mergeinfo_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "REPORT";
+ handler->path = path;
+ handler->conn = session->conns[0];
+ handler->session = session;
+ handler->body_delegate = create_mergeinfo_body;
+ handler->body_delegate_baton = mergeinfo_ctx;
+ handler->body_type = "text/xml";
+
+ err = svn_ra_serf__context_run_one(handler, pool);
+
+ err2 = svn_ra_serf__error_on_status(handler->sline.code, handler->path,
+ handler->location);
+ if (err2)
+ {
+ svn_error_clear(err);
+ return err2;
+ }
+
+ SVN_ERR(err);
+
+ if (handler->done && apr_hash_count(mergeinfo_ctx->result_catalog))
+ *catalog = mergeinfo_ctx->result_catalog;
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/options.c b/subversion/libsvn_ra_serf/options.c
new file mode 100644
index 0000000..0af0b15
--- /dev/null
+++ b/subversion/libsvn_ra_serf/options.c
@@ -0,0 +1,625 @@
+/*
+ * options.c : entry point for OPTIONS RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_private_config.h"
+#include "private/svn_fspath.h"
+
+#include "ra_serf.h"
+
+
+/* In a debug build, setting this environment variable to "yes" will force
+ the client to speak v1, even if the server is capable of speaking v2. */
+#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
+
+
+/*
+ * This enum represents the current state of our XML parsing for an OPTIONS.
+ */
+enum options_state_e {
+ INITIAL = 0,
+ OPTIONS,
+ ACTIVITY_COLLECTION,
+ HREF
+};
+
+typedef struct options_context_t {
+ /* pool to allocate memory from */
+ apr_pool_t *pool;
+
+ /* Have we extracted options values from the headers already? */
+ svn_boolean_t headers_processed;
+
+ svn_ra_serf__session_t *session;
+ svn_ra_serf__connection_t *conn;
+ svn_ra_serf__handler_t *handler;
+
+ svn_ra_serf__response_handler_t inner_handler;
+ void *inner_baton;
+
+ const char *activity_collection;
+ svn_revnum_t youngest_rev;
+
+} options_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t options_ttable[] = {
+ { INITIAL, D_, "options-response", OPTIONS,
+ FALSE, { NULL }, FALSE },
+
+ { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
+ FALSE, { NULL }, FALSE },
+
+ { ACTIVITY_COLLECTION, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+options_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ options_context_t *opt_ctx = baton;
+
+ SVN_ERR_ASSERT(leaving_state == HREF);
+ SVN_ERR_ASSERT(cdata != NULL);
+
+ opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
+ opt_ctx->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+create_options_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *body;
+ body = serf_bucket_aggregate_create(alloc);
+ svn_ra_serf__add_xml_header_buckets(body, alloc);
+ svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
+ "xmlns:D", "DAV:",
+ NULL);
+ svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
+ svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
+
+ *body_bkt = body;
+ return SVN_NO_ERROR;
+}
+
+
+/* We use these static pointers so we can employ pointer comparison
+ * of our capabilities hash members instead of strcmp()ing all over
+ * the place.
+ */
+/* Both server and repository support the capability. */
+static const char *const capability_yes = "yes";
+/* Either server or repository does not support the capability. */
+static const char *const capability_no = "no";
+/* Server supports the capability, but don't yet know if repository does. */
+static const char *const capability_server_yes = "server-yes";
+
+
+/* This implements serf_bucket_headers_do_callback_fn_t.
+ */
+static int
+capabilities_headers_iterator_callback(void *baton,
+ const char *key,
+ const char *val)
+{
+ options_context_t *opt_ctx = baton;
+ svn_ra_serf__session_t *session = opt_ctx->session;
+
+ if (svn_cstring_casecmp(key, "dav") == 0)
+ {
+ /* Each header may contain multiple values, separated by commas, e.g.:
+ DAV: version-control,checkout,working-resource
+ DAV: merge,baseline,activity,version-controlled-collection
+ DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
+ apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
+ opt_ctx->pool);
+
+ /* Right now we only have a few capabilities to detect, so just
+ seek for them directly. This could be written slightly more
+ efficiently, but that wouldn't be worth it until we have many
+ more capabilities. */
+
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_DEPTH, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
+ {
+ /* The server doesn't know what repository we're referring
+ to, so it can't just say capability_yes. */
+ if (!svn_hash_gets(session->capabilities,
+ SVN_RA_CAPABILITY_MERGEINFO))
+ {
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_server_yes);
+ }
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
+ vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
+ capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
+ {
+ session->supports_inline_props = TRUE;
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
+ {
+ session->supports_rev_rsrc_replay = TRUE;
+ }
+ }
+
+ /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
+ else if (strncmp(key, "SVN", 3) == 0)
+ {
+ /* If we've not yet seen any information about supported POST
+ requests, we'll initialize the list/hash with "create-txn"
+ (which we know is supported by virtue of the server speaking
+ HTTPv2 at all. */
+ if (! session->supported_posts)
+ {
+ session->supported_posts = apr_hash_make(session->pool);
+ apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
+ }
+
+ if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
+ {
+ session->repos_root = session->session_url;
+ session->repos_root.path =
+ (char *)svn_fspath__canonicalize(val, session->pool);
+ session->repos_root_str =
+ svn_urlpath__canonicalize(
+ apr_uri_unparse(session->pool, &session->repos_root, 0),
+ session->pool);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
+ {
+#ifdef SVN_DEBUG
+ char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
+
+ if (!(ignore_v2_env_var
+ && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
+ session->me_resource = apr_pstrdup(session->pool, val);
+#else
+ session->me_resource = apr_pstrdup(session->pool, val);
+#endif
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
+ {
+ session->rev_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
+ {
+ session->rev_root_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
+ {
+ session->txn_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
+ {
+ session->txn_root_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
+ {
+ session->vtxn_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
+ {
+ session->vtxn_root_stub = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
+ {
+ session->uuid = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
+ {
+ opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
+ {
+ session->server_allows_bulk = apr_pstrdup(session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
+ {
+ /* May contain multiple values, separated by commas. */
+ int i;
+ apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
+ opt_ctx->pool);
+
+ for (i = 0; i < vals->nelts; i++)
+ {
+ const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
+
+ svn_hash_sets(session->supported_posts, post_val, (void *)1);
+ }
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
+ {
+ if (svn_cstring_casecmp(val, "yes") == 0)
+ {
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_yes);
+ }
+ else if (svn_cstring_casecmp(val, "no") == 0)
+ {
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_no);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/* A custom serf_response_handler_t which is mostly a wrapper around
+ the expat-based response handler -- it just notices OPTIONS response
+ headers first, before handing off to the xml parser.
+ Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+options_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ options_context_t *opt_ctx = baton;
+
+ if (!opt_ctx->headers_processed)
+ {
+ svn_ra_serf__session_t *session = opt_ctx->session;
+ serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
+
+ /* Start out assuming all capabilities are unsupported. */
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ NULL);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
+ capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
+ capability_no);
+
+ /* Then see which ones we can discover. */
+ serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
+ opt_ctx);
+
+ /* Assume mergeinfo capability unsupported, if didn't recieve information
+ about server or repository mergeinfo capability. */
+ if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ capability_no);
+
+ opt_ctx->headers_processed = TRUE;
+ }
+
+ /* Execute the 'real' response handler to XML-parse the response body. */
+ return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
+}
+
+
+static svn_error_t *
+create_options_req(options_context_t **opt_ctx,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ options_context_t *new_ctx;
+ svn_ra_serf__xml_context_t *xmlctx;
+ svn_ra_serf__handler_t *handler;
+
+ new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
+ new_ctx->pool = pool;
+ new_ctx->session = session;
+ new_ctx->conn = conn;
+
+ new_ctx->youngest_rev = SVN_INVALID_REVNUM;
+
+ xmlctx = svn_ra_serf__xml_context_create(options_ttable,
+ NULL, options_closed, NULL,
+ new_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "OPTIONS";
+ handler->path = session->session_url.path;
+ handler->body_delegate = create_options_body;
+ handler->body_type = "text/xml";
+ handler->conn = conn;
+ handler->session = session;
+
+ new_ctx->handler = handler;
+
+ new_ctx->inner_handler = handler->response_handler;
+ new_ctx->inner_baton = handler->response_baton;
+ handler->response_handler = options_response_handler;
+ handler->response_baton = new_ctx;
+
+ *opt_ctx = new_ctx;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = conn->session;
+ options_context_t *opt_ctx;
+
+ SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
+ SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
+ SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline.code,
+ opt_ctx->handler->path,
+ opt_ctx->handler->location));
+
+ *youngest = opt_ctx->youngest_rev;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__v1_get_activity_collection(const char **activity_url,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = conn->session;
+ options_context_t *opt_ctx;
+
+ SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
+ SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
+
+ SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline.code,
+ opt_ctx->handler->path,
+ opt_ctx->handler->location));
+
+ *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
+
+ return SVN_NO_ERROR;
+
+}
+
+
+
+/** Capabilities exchange. */
+
+svn_error_t *
+svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
+ const char **corrected_url,
+ apr_pool_t *pool)
+{
+ options_context_t *opt_ctx;
+ svn_error_t *err;
+
+ /* This routine automatically fills in serf_sess->capabilities */
+ SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool));
+
+ err = svn_ra_serf__context_run_one(opt_ctx->handler, pool);
+
+ /* If our caller cares about server redirections, and our response
+ carries such a thing, report as much. We'll disregard ERR --
+ it's most likely just a complaint about the response body not
+ successfully parsing as XML or somesuch. */
+ if (corrected_url && (opt_ctx->handler->sline.code == 301))
+ {
+ svn_error_clear(err);
+ *corrected_url = opt_ctx->handler->location;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(opt_ctx->handler->sline.code,
+ serf_sess->session_url.path,
+ opt_ctx->handler->location),
+ err));
+
+ /* Opportunistically cache any reported activity URL. (We don't
+ want to have to ask for this again later, potentially against an
+ unreadable commit anchor URL.) */
+ if (opt_ctx->activity_collection)
+ {
+ serf_sess->activity_collection_url =
+ apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *serf_sess = ra_session->priv;
+ const char *cap_result;
+
+ /* This capability doesn't rely on anything server side. */
+ if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
+ {
+ *has = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ cap_result = svn_hash_gets(serf_sess->capabilities, capability);
+
+ /* If any capability is unknown, they're all unknown, so ask. */
+ if (cap_result == NULL)
+ SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool));
+
+ /* Try again, now that we've fetched the capabilities. */
+ cap_result = svn_hash_gets(serf_sess->capabilities, capability);
+
+ /* Some capabilities depend on the repository as well as the server. */
+ if (cap_result == capability_server_yes)
+ {
+ if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
+ {
+ /* Handle mergeinfo specially. Mergeinfo depends on the
+ repository as well as the server, but the server routine
+ that answered our svn_ra_serf__exchange_capabilities() call above
+ didn't even know which repository we were interested in
+ -- it just told us whether the server supports mergeinfo.
+ If the answer was 'no', there's no point checking the
+ particular repository; but if it was 'yes', we still must
+ change it to 'no' iff the repository itself doesn't
+ support mergeinfo. */
+ svn_mergeinfo_catalog_t ignored;
+ svn_error_t *err;
+ apr_array_header_t *paths = apr_array_make(pool, 1,
+ sizeof(char *));
+ APR_ARRAY_PUSH(paths, const char *) = "";
+
+ err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
+ FALSE, FALSE, pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
+ {
+ svn_error_clear(err);
+ cap_result = capability_no;
+ }
+ else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* Mergeinfo requests use relative paths, and
+ anyway we're in r0, so this is a likely error,
+ but it means the repository supports mergeinfo! */
+ svn_error_clear(err);
+ cap_result = capability_yes;
+ }
+ else
+ return err;
+ }
+ else
+ cap_result = capability_yes;
+
+ svn_hash_sets(serf_sess->capabilities,
+ SVN_RA_CAPABILITY_MERGEINFO, cap_result);
+ }
+ else
+ {
+ return svn_error_createf
+ (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know how to handle '%s' for capability '%s'"),
+ capability_server_yes, capability);
+ }
+ }
+
+ if (cap_result == capability_yes)
+ {
+ *has = TRUE;
+ }
+ else if (cap_result == capability_no)
+ {
+ *has = FALSE;
+ }
+ else if (cap_result == NULL)
+ {
+ return svn_error_createf
+ (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
+ _("Don't know anything about capability '%s'"), capability);
+ }
+ else /* "can't happen" */
+ {
+ /* Well, let's hope it's a string. */
+ return svn_error_createf
+ (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("Attempt to fetch capability '%s' resulted in '%s'"),
+ capability, cap_result);
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/property.c b/subversion/libsvn_ra_serf/property.c
new file mode 100644
index 0000000..63972e8
--- /dev/null
+++ b/subversion/libsvn_ra_serf/property.c
@@ -0,0 +1,1263 @@
+/*
+ * property.c : property routines for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_base64.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_dirent_uri.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_fspath.h"
+#include "private/svn_string_private.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+/* Our current parsing state we're in for the PROPFIND response. */
+typedef enum prop_state_e {
+ INITIAL = 0,
+ MULTISTATUS,
+ RESPONSE,
+ HREF,
+ PROPSTAT,
+ STATUS,
+ PROP,
+ PROPVAL,
+ COLLECTION,
+ HREF_VALUE
+} prop_state_e;
+
+
+/*
+ * This structure represents a pending PROPFIND response.
+ */
+typedef struct propfind_context_t {
+ /* pool to issue allocations from */
+ apr_pool_t *pool;
+
+ svn_ra_serf__handler_t *handler;
+
+ /* associated serf session */
+ svn_ra_serf__session_t *sess;
+ svn_ra_serf__connection_t *conn;
+
+ /* the requested path */
+ const char *path;
+
+ /* the requested version (number and string form) */
+ svn_revnum_t rev;
+ const char *label;
+
+ /* the request depth */
+ const char *depth;
+
+ /* the list of requested properties */
+ const svn_ra_serf__dav_props_t *find_props;
+
+ /* hash table that will be updated with the properties
+ *
+ * This can be shared between multiple propfind_context_t
+ * structures
+ */
+ apr_hash_t *ret_props;
+
+ /* hash table containing all the properties associated with the
+ * "current" <propstat> tag. These will get copied into RET_PROPS
+ * if the status code similarly associated indicates that they are
+ * "good"; otherwise, they'll get discarded.
+ */
+ apr_hash_t *ps_props;
+
+ /* If not-NULL, add us to this list when we're done. */
+ svn_ra_serf__list_t **done_list;
+
+ svn_ra_serf__list_t done_item;
+
+} propfind_context_t;
+
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t propfind_ttable[] = {
+ { INITIAL, D_, "multistatus", MULTISTATUS,
+ FALSE, { NULL }, TRUE },
+
+ { MULTISTATUS, D_, "response", RESPONSE,
+ FALSE, { NULL }, FALSE },
+
+ { RESPONSE, D_, "href", HREF,
+ TRUE, { NULL }, TRUE },
+
+ { RESPONSE, D_, "propstat", PROPSTAT,
+ FALSE, { NULL }, TRUE },
+
+ { PROPSTAT, D_, "status", STATUS,
+ TRUE, { NULL }, TRUE },
+
+ { PROPSTAT, D_, "prop", PROP,
+ FALSE, { NULL }, FALSE },
+
+ { PROP, "*", "*", PROPVAL,
+ TRUE, { "?V:encoding", NULL }, TRUE },
+
+ { PROPVAL, D_, "collection", COLLECTION,
+ FALSE, { NULL }, TRUE },
+
+ { PROPVAL, D_, "href", HREF_VALUE,
+ TRUE, { NULL }, TRUE },
+
+ { 0 }
+};
+
+
+/* Return the HTTP status code contained in STATUS_LINE, or 0 if
+ there's a problem parsing it. */
+static int parse_status_code(const char *status_line)
+{
+ /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */
+ if (status_line[0] == 'H' &&
+ status_line[1] == 'T' &&
+ status_line[2] == 'T' &&
+ status_line[3] == 'P' &&
+ status_line[4] == '/' &&
+ (status_line[5] >= '0' && status_line[5] <= '9') &&
+ status_line[6] == '.' &&
+ (status_line[7] >= '0' && status_line[7] <= '9') &&
+ status_line[8] == ' ')
+ {
+ char *reason;
+
+ return apr_strtoi64(status_line + 8, &reason, 10);
+ }
+ return 0;
+}
+
+
+/* Conforms to svn_ra_serf__path_rev_walker_t */
+static svn_error_t *
+copy_into_ret_props(void *baton,
+ const char *path, apr_ssize_t path_len,
+ const char *ns, apr_ssize_t ns_len,
+ const char *name, apr_ssize_t name_len,
+ const svn_string_t *val,
+ apr_pool_t *pool)
+{
+ propfind_context_t *ctx = baton;
+
+ svn_ra_serf__set_ver_prop(ctx->ret_props, path, ctx->rev, ns, name,
+ val, ctx->pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+propfind_opened(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool)
+{
+ propfind_context_t *ctx = baton;
+
+ if (entered_state == PROPVAL)
+ {
+ svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->namespace);
+ svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
+ }
+ else if (entered_state == PROPSTAT)
+ {
+ ctx->ps_props = apr_hash_make(ctx->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+propfind_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ propfind_context_t *ctx = baton;
+
+ if (leaving_state == MULTISTATUS)
+ {
+ /* We've gathered all the data from the reponse. Add this item
+ onto the "done list". External callers will then know this
+ request has been completed (tho stray response bytes may still
+ arrive). */
+ if (ctx->done_list)
+ {
+ ctx->done_item.data = ctx->handler;
+ ctx->done_item.next = *ctx->done_list;
+ *ctx->done_list = &ctx->done_item;
+ }
+ }
+ else if (leaving_state == HREF)
+ {
+ const char *path;
+ const svn_string_t *val_str;
+
+ if (strcmp(ctx->depth, "1") == 0)
+ path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
+ else
+ path = ctx->path;
+
+ svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
+
+ /* Copy the value into the right pool, then save the HREF. */
+ val_str = svn_string_dup(cdata, ctx->pool);
+ svn_ra_serf__set_ver_prop(ctx->ret_props,
+ path, ctx->rev, D_, "href", val_str,
+ ctx->pool);
+ }
+ else if (leaving_state == COLLECTION)
+ {
+ svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
+ }
+ else if (leaving_state == HREF_VALUE)
+ {
+ svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
+ }
+ else if (leaving_state == STATUS)
+ {
+ /* Parse the status field, and remember if this is a property
+ that we wish to ignore. (Typically, if it's not a 200, the
+ status will be 404 to indicate that a property we
+ specifically requested from the server doesn't exist.) */
+ int status = parse_status_code(cdata->data);
+ if (status != 200)
+ svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
+ }
+ else if (leaving_state == PROPVAL)
+ {
+ const char *encoding = svn_hash_gets(attrs, "V:encoding");
+ const svn_string_t *val_str;
+ apr_hash_t *gathered;
+ const char *path;
+ const char *ns;
+ const char *name;
+ const char *altvalue;
+
+ if (encoding)
+ {
+ if (strcmp(encoding, "base64") != 0)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ NULL,
+ _("Got unrecognized encoding '%s'"),
+ encoding);
+
+ /* Decode into the right pool. */
+ val_str = svn_base64_decode_string(cdata, ctx->pool);
+ }
+ else
+ {
+ /* Copy into the right pool. */
+ val_str = svn_string_dup(cdata, ctx->pool);
+ }
+
+ /* The current path sits on the RESPONSE state. Gather up all the
+ state from this PROPVAL to the (grandparent) RESPONSE state,
+ and grab the path from there.
+
+ Now, it would be nice if we could, at this point, know that
+ the status code for this property indicated a problem -- then
+ we could simply bail out here and ignore the property.
+ Sadly, though, we might get the status code *after* we get
+ the property value. So we'll carry on with our processing
+ here, setting the property and value as expected. Once we
+ know for sure the status code associate with the property,
+ we'll decide its fate. */
+ gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE);
+
+ /* These will be dup'd into CTX->POOL, as necessary. */
+ path = svn_hash_gets(gathered, "path");
+ if (path == NULL)
+ path = ctx->path;
+
+ ns = svn_hash_gets(attrs, "ns");
+ name = apr_pstrdup(ctx->pool,
+ svn_hash_gets(attrs, "name"));
+
+ altvalue = svn_hash_gets(attrs, "altvalue");
+ if (altvalue != NULL)
+ val_str = svn_string_create(altvalue, ctx->pool);
+
+ svn_ra_serf__set_ver_prop(ctx->ps_props,
+ path, ctx->rev, ns, name, val_str,
+ ctx->pool);
+ }
+ else
+ {
+ apr_hash_t *gathered;
+
+ SVN_ERR_ASSERT(leaving_state == PROPSTAT);
+
+ gathered = svn_ra_serf__xml_gather_since(xes, PROPSTAT);
+
+ /* If we've squirreled away a note that says we want to ignore
+ these properties, we'll do so. Otherwise, we need to copy
+ them from the temporary hash into the ctx->ret_props hash. */
+ if (! svn_hash_gets(gathered, "ignore-prop"))
+ {
+ SVN_ERR(svn_ra_serf__walk_all_paths(ctx->ps_props, ctx->rev,
+ copy_into_ret_props, ctx,
+ scratch_pool));
+ }
+
+ ctx->ps_props = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+const svn_string_t *
+svn_ra_serf__get_ver_prop_string(apr_hash_t *props,
+ const char *path,
+ svn_revnum_t rev,
+ const char *ns,
+ const char *name)
+{
+ apr_hash_t *ver_props, *path_props, *ns_props;
+ void *val = NULL;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+ if (ver_props)
+ {
+ path_props = svn_hash_gets(ver_props, path);
+
+ if (path_props)
+ {
+ ns_props = svn_hash_gets(path_props, ns);
+ if (ns_props)
+ {
+ val = svn_hash_gets(ns_props, name);
+ }
+ }
+ }
+
+ return val;
+}
+
+const char *
+svn_ra_serf__get_ver_prop(apr_hash_t *props,
+ const char *path,
+ svn_revnum_t rev,
+ const char *ns,
+ const char *name)
+{
+ const svn_string_t *val;
+
+ val = svn_ra_serf__get_ver_prop_string(props, path, rev, ns, name);
+
+ if (val)
+ {
+ return val->data;
+ }
+
+ return NULL;
+}
+
+const svn_string_t *
+svn_ra_serf__get_prop_string(apr_hash_t *props,
+ const char *path,
+ const char *ns,
+ const char *name)
+{
+ return svn_ra_serf__get_ver_prop_string(props, path, SVN_INVALID_REVNUM,
+ ns, name);
+}
+
+const char *
+svn_ra_serf__get_prop(apr_hash_t *props,
+ const char *path,
+ const char *ns,
+ const char *name)
+{
+ return svn_ra_serf__get_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name);
+}
+
+void
+svn_ra_serf__set_ver_prop(apr_hash_t *props,
+ const char *path, svn_revnum_t rev,
+ const char *ns, const char *name,
+ const svn_string_t *val, apr_pool_t *pool)
+{
+ apr_hash_t *ver_props, *path_props, *ns_props;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+ if (!ver_props)
+ {
+ ver_props = apr_hash_make(pool);
+ apr_hash_set(props, apr_pmemdup(pool, &rev, sizeof(rev)), sizeof(rev),
+ ver_props);
+ }
+
+ path_props = svn_hash_gets(ver_props, path);
+
+ if (!path_props)
+ {
+ path_props = apr_hash_make(pool);
+ path = apr_pstrdup(pool, path);
+ svn_hash_sets(ver_props, path, path_props);
+
+ /* todo: we know that we'll fail the next check, but fall through
+ * for now for simplicity's sake.
+ */
+ }
+
+ ns_props = svn_hash_gets(path_props, ns);
+ if (!ns_props)
+ {
+ ns_props = apr_hash_make(pool);
+ ns = apr_pstrdup(pool, ns);
+ svn_hash_sets(path_props, ns, ns_props);
+ }
+
+ svn_hash_sets(ns_props, name, val);
+}
+
+void
+svn_ra_serf__set_prop(apr_hash_t *props,
+ const char *path,
+ const char *ns, const char *name,
+ const svn_string_t *val, apr_pool_t *pool)
+{
+ svn_ra_serf__set_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name,
+ val, pool);
+}
+
+
+static svn_error_t *
+setup_propfind_headers(serf_bucket_t *headers,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ propfind_context_t *ctx = setup_baton;
+
+ serf_bucket_headers_setn(headers, "Depth", ctx->depth);
+ if (ctx->label)
+ {
+ serf_bucket_headers_setn(headers, "Label", ctx->label);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+#define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">"
+#define PROPFIND_TRAILER "</propfind>"
+
+static svn_error_t *
+create_propfind_body(serf_bucket_t **bkt,
+ void *setup_baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ propfind_context_t *ctx = setup_baton;
+
+ serf_bucket_t *body_bkt, *tmp;
+ const svn_ra_serf__dav_props_t *prop;
+ svn_boolean_t requested_allprop = FALSE;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ prop = ctx->find_props;
+ while (prop && prop->namespace)
+ {
+ /* special case the allprop case. */
+ if (strcmp(prop->name, "allprop") == 0)
+ {
+ requested_allprop = TRUE;
+ }
+
+ /* <*propname* xmlns="*propns*" /> */
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
+ sizeof(" xmlns=\"")-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(prop->namespace, alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ prop++;
+ }
+
+ /* If we're not doing an allprop, add <prop> tags. */
+ if (!requested_allprop)
+ {
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
+ sizeof("<prop>")-1,
+ alloc);
+ serf_bucket_aggregate_prepend(body_bkt, tmp);
+ }
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
+ sizeof(PROPFIND_HEADER)-1,
+ alloc);
+
+ serf_bucket_aggregate_prepend(body_bkt, tmp);
+
+ if (!requested_allprop)
+ {
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
+ sizeof("</prop>")-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+ }
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
+ sizeof(PROPFIND_TRAILER)-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler,
+ apr_hash_t *ret_props,
+ svn_ra_serf__session_t *sess,
+ svn_ra_serf__connection_t *conn,
+ const char *path,
+ svn_revnum_t rev,
+ const char *depth,
+ const svn_ra_serf__dav_props_t *find_props,
+ svn_ra_serf__list_t **done_list,
+ apr_pool_t *pool)
+{
+ propfind_context_t *new_prop_ctx;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+
+ new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
+
+ new_prop_ctx->pool = apr_hash_pool_get(ret_props);
+ new_prop_ctx->path = path;
+ new_prop_ctx->find_props = find_props;
+ new_prop_ctx->ret_props = ret_props;
+ new_prop_ctx->depth = depth;
+ new_prop_ctx->sess = sess;
+ new_prop_ctx->conn = conn;
+ new_prop_ctx->rev = rev;
+ new_prop_ctx->done_list = done_list;
+
+ if (SVN_IS_VALID_REVNUM(rev))
+ {
+ new_prop_ctx->label = apr_ltoa(pool, rev);
+ }
+ else
+ {
+ new_prop_ctx->label = NULL;
+ }
+
+ xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
+ propfind_opened,
+ propfind_closed,
+ NULL,
+ new_prop_ctx,
+ pool);
+ handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
+
+ handler->method = "PROPFIND";
+ handler->path = path;
+ handler->body_delegate = create_propfind_body;
+ handler->body_type = "text/xml";
+ handler->body_delegate_baton = new_prop_ctx;
+ handler->header_delegate = setup_propfind_headers;
+ handler->header_delegate_baton = new_prop_ctx;
+
+ handler->session = new_prop_ctx->sess;
+ handler->conn = new_prop_ctx->conn;
+
+ new_prop_ctx->handler = handler;
+
+ *propfind_handler = handler;
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * This helper function will block until the PROP_CTX indicates that is done
+ * or another error is returned.
+ */
+svn_error_t *
+svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_error_t *err2;
+
+ err = svn_ra_serf__context_run_one(handler, scratch_pool);
+
+ err2 = svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location);
+
+ return svn_error_compose_create(err2, err);
+}
+
+/*
+ * This is a blocking version of deliver_props.
+ */
+svn_error_t *
+svn_ra_serf__retrieve_props(apr_hash_t **results,
+ svn_ra_serf__session_t *sess,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t rev,
+ const char *depth,
+ const svn_ra_serf__dav_props_t *props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler;
+
+ *results = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_ra_serf__deliver_props(&handler, *results, sess, conn, url,
+ rev, depth, props, NULL, result_pool));
+ SVN_ERR(svn_ra_serf__wait_for_props(handler, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__fetch_node_props(apr_hash_t **results,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *which_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *multiprops;
+ apr_hash_t *ver_props;
+
+ /* Note: a couple extra hash tables and whatnot get into RESULT_POOL.
+ Not a big deal at this point. Theoretically, we could fetch all
+ props into SCRATCH_POOL, then copy just the REVISION/URL props
+ into RESULT_POOL. Too much work for too little gain... */
+ SVN_ERR(svn_ra_serf__retrieve_props(&multiprops, conn->session, conn,
+ url, revision, "0", which_props,
+ result_pool, scratch_pool));
+
+ ver_props = apr_hash_get(multiprops, &revision, sizeof(revision));
+ if (ver_props != NULL)
+ {
+ *results = svn_hash_gets(ver_props, url);
+ if (*results != NULL)
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested properties"));
+}
+
+
+svn_error_t *
+svn_ra_serf__walk_node_props(apr_hash_t *props,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ apr_hash_index_t *ns_hi;
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (ns_hi = apr_hash_first(scratch_pool, props); ns_hi;
+ ns_hi = apr_hash_next(ns_hi))
+ {
+ void *ns_val;
+ const void *ns_name;
+ apr_hash_index_t *name_hi;
+
+ /* NOTE: We do not clear ITERPOOL in this loop. Generally, there are
+ very few namespaces, so this loop will not have many iterations.
+ Instead, ITERPOOL is used for the inner loop. */
+
+ apr_hash_this(ns_hi, &ns_name, NULL, &ns_val);
+
+ for (name_hi = apr_hash_first(scratch_pool, ns_val); name_hi;
+ name_hi = apr_hash_next(name_hi))
+ {
+ void *prop_val;
+ const void *prop_name;
+
+ /* See note above, regarding clearing of this pool. */
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(name_hi, &prop_name, NULL, &prop_val);
+
+ SVN_ERR(walker(baton, ns_name, prop_name, prop_val, iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__walk_all_props(apr_hash_t *props,
+ const char *name,
+ svn_revnum_t rev,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *ver_props;
+ apr_hash_t *path_props;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+ if (!ver_props)
+ return SVN_NO_ERROR;
+
+ path_props = svn_hash_gets(ver_props, name);
+ if (!path_props)
+ return SVN_NO_ERROR;
+
+ return svn_error_trace(svn_ra_serf__walk_node_props(path_props,
+ walker, baton,
+ scratch_pool));
+}
+
+
+svn_error_t *
+svn_ra_serf__walk_all_paths(apr_hash_t *props,
+ svn_revnum_t rev,
+ svn_ra_serf__path_rev_walker_t walker,
+ void *baton,
+ apr_pool_t *pool)
+{
+ apr_hash_index_t *path_hi;
+ apr_hash_t *ver_props;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+
+ if (!ver_props)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ for (path_hi = apr_hash_first(pool, ver_props); path_hi;
+ path_hi = apr_hash_next(path_hi))
+ {
+ void *path_props;
+ const void *path_name;
+ apr_ssize_t path_len;
+ apr_hash_index_t *ns_hi;
+
+ apr_hash_this(path_hi, &path_name, &path_len, &path_props);
+ for (ns_hi = apr_hash_first(pool, path_props); ns_hi;
+ ns_hi = apr_hash_next(ns_hi))
+ {
+ void *ns_val;
+ const void *ns_name;
+ apr_ssize_t ns_len;
+ apr_hash_index_t *name_hi;
+ apr_hash_this(ns_hi, &ns_name, &ns_len, &ns_val);
+ for (name_hi = apr_hash_first(pool, ns_val); name_hi;
+ name_hi = apr_hash_next(name_hi))
+ {
+ void *prop_val;
+ const void *prop_name;
+ apr_ssize_t prop_len;
+
+ apr_hash_this(name_hi, &prop_name, &prop_len, &prop_val);
+ /* use a subpool? */
+ SVN_ERR(walker(baton, path_name, path_len, ns_name, ns_len,
+ prop_name, prop_len, prop_val, pool));
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+const char *
+svn_ra_serf__svnname_from_wirename(const char *ns,
+ const char *name,
+ apr_pool_t *result_pool)
+{
+ if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ return apr_pstrdup(result_pool, name);
+
+ if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
+
+ if (strcmp(ns, SVN_PROP_PREFIX) == 0)
+ return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
+
+ if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
+ return SVN_PROP_ENTRY_COMMITTED_REV;
+
+ if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
+ return SVN_PROP_ENTRY_COMMITTED_DATE;
+
+ if (strcmp(name, "creator-displayname") == 0)
+ return SVN_PROP_ENTRY_LAST_AUTHOR;
+
+ if (strcmp(name, "repository-uuid") == 0)
+ return SVN_PROP_ENTRY_UUID;
+
+ if (strcmp(name, "lock-token") == 0)
+ return SVN_PROP_ENTRY_LOCK_TOKEN;
+
+ if (strcmp(name, "checked-in") == 0)
+ return SVN_RA_SERF__WC_CHECKED_IN_URL;
+
+ if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
+ {
+ /* Here DAV: properties not yet converted to svn: properties should be
+ ignored. */
+ return NULL;
+ }
+
+ /* An unknown namespace, must be a custom property. */
+ return apr_pstrcat(result_pool, ns, name, (char *)NULL);
+}
+
+
+/* Conforms to svn_ra_serf__walker_visitor_t */
+static svn_error_t *
+set_flat_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ apr_hash_t *props = baton;
+ apr_pool_t *result_pool = apr_hash_pool_get(props);
+ const char *prop_name;
+
+ /* ### is VAL in the proper pool? */
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
+ if (prop_name != NULL)
+ svn_hash_sets(props, prop_name, value);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__flatten_props(apr_hash_t **flat_props,
+ apr_hash_t *props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *flat_props = apr_hash_make(result_pool);
+
+ return svn_error_trace(svn_ra_serf__walk_node_props(
+ props,
+ set_flat_props,
+ *flat_props /* baton */,
+ scratch_pool));
+}
+
+
+static svn_error_t *
+select_revprops(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *revprops = baton;
+ apr_pool_t *result_pool = apr_hash_pool_get(revprops);
+ const char *prop_name;
+
+ /* ### copy NAME into the RESULT_POOL? */
+ /* ### copy VAL into the RESULT_POOL? */
+
+ if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ prop_name = name;
+ else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
+ else if (strcmp(ns, SVN_PROP_PREFIX) == 0)
+ prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
+ else if (strcmp(ns, "") == 0)
+ prop_name = name;
+ else
+ {
+ /* do nothing for now? */
+ return SVN_NO_ERROR;
+ }
+
+ svn_hash_sets(revprops, prop_name, val);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__select_revprops(apr_hash_t **revprops,
+ const char *name,
+ svn_revnum_t rev,
+ apr_hash_t *all_revprops,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *revprops = apr_hash_make(result_pool);
+
+ return svn_error_trace(svn_ra_serf__walk_all_props(
+ all_revprops, name, rev,
+ select_revprops, *revprops,
+ scratch_pool));
+}
+
+
+/*
+ * Contact the server (using CONN) to calculate baseline
+ * information for BASELINE_URL at REVISION (which may be
+ * SVN_INVALID_REVNUM to query the HEAD revision).
+ *
+ * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
+ * retrieved from the server as part of this process (which should
+ * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the
+ * baseline collection URL.
+ */
+static svn_error_t *
+retrieve_baseline_info(svn_revnum_t *actual_revision,
+ const char **basecoll_url_p,
+ svn_ra_serf__connection_t *conn,
+ const char *baseline_url,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props;
+ apr_hash_t *dav_props;
+ const char *basecoll_url;
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn,
+ baseline_url, revision,
+ baseline_props,
+ scratch_pool, scratch_pool));
+ dav_props = apr_hash_get(props, "DAV:", 4);
+ /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */
+
+ basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
+ if (!basecoll_url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested baseline-collection value"));
+ }
+ *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
+
+ if (actual_revision)
+ {
+ const char *version_name;
+
+ version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
+ if (!version_name)
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested version-name value"));
+
+ *actual_revision = SVN_STR_TO_REV(version_name);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
+ revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
+ collection URL is also returned.
+
+ Do the work over CONN.
+
+ *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
+ temporary allocations will be made in SCRATCH_POOL. */
+static svn_error_t *
+v1_get_youngest_revnum(svn_revnum_t *youngest,
+ const char **basecoll_url,
+ svn_ra_serf__connection_t *conn,
+ const char *vcc_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *baseline_url;
+ const char *bc_url;
+
+ /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
+ revision) will return the latest Baseline resource's URL. */
+ SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, conn, vcc_url,
+ SVN_INVALID_REVNUM,
+ "checked-in",
+ scratch_pool, scratch_pool));
+ if (!baseline_url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include "
+ "the requested checked-in value"));
+ }
+ baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
+
+ /* From the Baseline resource, we can fetch the DAV:baseline-collection
+ and DAV:version-name properties. The latter is the revision number,
+ which is formally the name used in Label: headers. */
+
+ /* First check baseline information cache. */
+ SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
+ youngest,
+ conn->session->blncache,
+ baseline_url,
+ scratch_pool));
+ if (!bc_url)
+ {
+ SVN_ERR(retrieve_baseline_info(youngest, &bc_url, conn,
+ baseline_url, SVN_INVALID_REVNUM,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_ra_serf__blncache_set(conn->session->blncache,
+ baseline_url, *youngest,
+ bc_url, scratch_pool));
+ }
+
+ if (basecoll_url != NULL)
+ *basecoll_url = apr_pstrdup(result_pool, bc_url);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *scratch_pool)
+{
+ const char *vcc_url;
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
+ youngest, session->conns[0], scratch_pool));
+
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, scratch_pool));
+
+ return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
+ session->conns[0], vcc_url,
+ scratch_pool, scratch_pool));
+}
+
+
+/* Set *BC_URL to the baseline collection url for REVISION. If REVISION
+ is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
+
+ *REVNUM_USED will be set to the revision used.
+
+ Uses the specified CONN, which is part of SESSION.
+
+ All allocations (results and temporary) are performed in POOL. */
+static svn_error_t *
+get_baseline_info(const char **bc_url,
+ svn_revnum_t *revnum_used,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ /* If we detected HTTP v2 support on the server, we can construct
+ the baseline collection URL ourselves, and fetch the latest
+ revision (if needed) with an OPTIONS request. */
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ *revnum_used = revision;
+ }
+ else
+ {
+ SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
+ revnum_used, conn, pool));
+ if (! SVN_IS_VALID_REVNUM(*revnum_used))
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include "
+ "the youngest revision"));
+ }
+
+ *bc_url = apr_psprintf(pool, "%s/%ld",
+ session->rev_root_stub, *revnum_used);
+ }
+
+ /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */
+ else
+ {
+ const char *vcc_url;
+
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, conn, pool));
+
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ /* First check baseline information cache. */
+ SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
+ session->blncache,
+ revision, pool));
+ if (!*bc_url)
+ {
+ SVN_ERR(retrieve_baseline_info(NULL, bc_url, conn,
+ vcc_url, revision, pool, pool));
+ SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
+ revision, *bc_url, pool));
+ }
+
+ *revnum_used = revision;
+ }
+ else
+ {
+ SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
+ conn, vcc_url,
+ pool, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__get_stable_url(const char **stable_url,
+ svn_revnum_t *latest_revnum,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *basecoll_url;
+ const char *repos_relpath;
+ svn_revnum_t revnum_used;
+
+ /* No URL? No sweat. We'll use the session URL. */
+ if (! url)
+ url = session->session_url.path;
+
+ /* If the caller didn't provide a specific connection for us to use,
+ we'll use the default connection. */
+ if (! conn)
+ conn = session->conns[0];
+
+ SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
+ session, conn, revision, scratch_pool));
+ SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
+ session, conn, scratch_pool));
+
+ *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
+ result_pool);
+ if (latest_revnum)
+ *latest_revnum = revnum_used;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
+ apr_hash_t *props)
+{
+ apr_hash_t *dav_props;
+ const char *res_type;
+
+ dav_props = apr_hash_get(props, "DAV:", 4);
+ res_type = svn_prop_get_value(dav_props, "resourcetype");
+ if (!res_type)
+ {
+ /* How did this happen? */
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include the "
+ "requested resourcetype value"));
+ }
+
+ if (strcmp(res_type, "collection") == 0)
+ {
+ *kind = svn_node_dir;
+ }
+ else
+ {
+ *kind = svn_node_file;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__fetch_dav_prop(const char **value,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props;
+ apr_hash_t *dav_props;
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, url, revision,
+ checked_in_props,
+ scratch_pool, scratch_pool));
+ dav_props = apr_hash_get(props, "DAV:", 4);
+ if (dav_props == NULL)
+ return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
+ _("The PROPFIND response did not include "
+ "the requested 'DAV:' properties"));
+
+ /* We wouldn't get here if the resource was not found (404), so the
+ property should be present.
+
+ Note: it is okay to call apr_pstrdup() with NULL. */
+ *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/ra_serf.h b/subversion/libsvn_ra_serf/ra_serf.h
new file mode 100644
index 0000000..3f3f3de
--- /dev/null
+++ b/subversion/libsvn_ra_serf/ra_serf.h
@@ -0,0 +1,1785 @@
+/*
+ * ra_serf.h : Private declarations for the Serf-based DAV RA module.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_RA_SERF_RA_SERF_H
+#define SVN_LIBSVN_RA_SERF_RA_SERF_H
+
+
+#include <serf.h>
+#include <expat.h> /* for XML_Parser */
+#include <apr_uri.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_delta.h"
+#include "svn_version.h"
+#include "svn_dav.h"
+#include "svn_dirent_uri.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_editor.h"
+
+#include "blncache.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Enforce the minimum version of serf. */
+#if !SERF_VERSION_AT_LEAST(1, 2, 1)
+#error Please update your version of serf to at least 1.2.1.
+#endif
+
+/** Use this to silence compiler warnings about unused parameters. */
+#define UNUSED_CTX(x) ((void)(x))
+
+/** Our User-Agent string. */
+#define USER_AGENT "SVN/" SVN_VER_NUMBER " (" SVN_BUILD_TARGET ")" \
+ " serf/" \
+ APR_STRINGIFY(SERF_MAJOR_VERSION) "." \
+ APR_STRINGIFY(SERF_MINOR_VERSION) "." \
+ APR_STRINGIFY(SERF_PATCH_VERSION)
+
+/** Wait duration (in microseconds) used in calls to serf_context_run() */
+#define SVN_RA_SERF__CONTEXT_RUN_DURATION 500000
+
+
+
+/* Forward declarations. */
+typedef struct svn_ra_serf__session_t svn_ra_serf__session_t;
+
+/* A serf connection and optionally associated SSL context. */
+typedef struct svn_ra_serf__connection_t {
+ /* Our connection to a server. */
+ serf_connection_t *conn;
+
+ /* Bucket allocator for this connection. */
+ serf_bucket_alloc_t *bkt_alloc;
+
+ /* Collected cert failures in chain. */
+ int server_cert_failures;
+
+ /* What was the last HTTP status code we got on this connection? */
+ int last_status_code;
+
+ /* Optional SSL context for this connection. */
+ serf_ssl_context_t *ssl_context;
+ svn_auth_iterstate_t *ssl_client_auth_state;
+ svn_auth_iterstate_t *ssl_client_pw_auth_state;
+
+ svn_ra_serf__session_t *session;
+
+} svn_ra_serf__connection_t;
+
+/** Maximum value we'll allow for the http-max-connections config option.
+ *
+ * Note: minimum 2 connections are required for ra_serf to function
+ * correctly!
+ */
+#define SVN_RA_SERF__MAX_CONNECTIONS_LIMIT 8
+
+/*
+ * The master serf RA session.
+ *
+ * This is stored in the ra session ->priv field.
+ */
+struct svn_ra_serf__session_t {
+ /* Pool for allocations during this session */
+ apr_pool_t *pool;
+
+ /* The current context */
+ serf_context_t *context;
+
+ /* The maximum number of connections we'll use for parallelized
+ fetch operations (updates, etc.) */
+ apr_int64_t max_connections;
+
+ /* Are we using ssl */
+ svn_boolean_t using_ssl;
+
+ /* Should we ask for compressed responses? */
+ svn_boolean_t using_compression;
+
+ /* The user agent string */
+ const char *useragent;
+
+ /* The current connection */
+ svn_ra_serf__connection_t *conns[SVN_RA_SERF__MAX_CONNECTIONS_LIMIT];
+ int num_conns;
+ int cur_conn;
+
+ /* The URL that was passed into _open() */
+ apr_uri_t session_url;
+ const char *session_url_str;
+
+ /* The actual discovered root; may be NULL until we know it. */
+ apr_uri_t repos_root;
+ const char *repos_root_str;
+
+ /* The server is not Apache/mod_dav_svn (directly) and only supports
+ HTTP/1.0. Thus, we cannot send chunked requests. */
+ svn_boolean_t http10;
+
+ /* Our Version-Controlled-Configuration; may be NULL until we know it. */
+ const char *vcc_url;
+
+ /* Authentication related properties. */
+ svn_auth_iterstate_t *auth_state;
+ int auth_attempts;
+
+ /* Callback functions to get info from WC */
+ const svn_ra_callbacks2_t *wc_callbacks;
+ void *wc_callback_baton;
+
+ /* Callback function to send progress info to the client */
+ svn_ra_progress_notify_func_t progress_func;
+ void *progress_baton;
+
+ /* Callback function to handle cancellation */
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+
+ /* Ev2 shim callbacks */
+ svn_delta_shim_callbacks_t *shim_callbacks;
+
+ /* Error that we've received but not yet returned upstream. */
+ svn_error_t *pending_error;
+
+ /* List of authn types supported by the client.*/
+ int authn_types;
+
+ /* Maps SVN_RA_CAPABILITY_foo keys to "yes" or "no" values.
+ If a capability is not yet discovered, it is absent from the table.
+ The table itself is allocated in the svn_ra_serf__session_t's pool;
+ keys and values must have at least that lifetime. Most likely
+ the keys and values are constants anyway (and sufficiently
+ well-informed internal code may just compare against those
+ constants' addresses, therefore). */
+ apr_hash_t *capabilities;
+
+ /* Activity collection URL. (Cached from the initial OPTIONS
+ request when run against HTTPv1 servers.) */
+ const char *activity_collection_url;
+
+ /* Are we using a proxy? */
+ int using_proxy;
+
+ const char *proxy_username;
+ const char *proxy_password;
+ int proxy_auth_attempts;
+
+ /* SSL server certificates */
+ svn_boolean_t trust_default_ca;
+ const char *ssl_authorities;
+
+ /* Repository UUID */
+ const char *uuid;
+
+ /* Connection timeout value */
+ apr_interval_time_t timeout;
+
+ /* HTTPv1 flags */
+ svn_tristate_t supports_deadprop_count;
+
+ /*** HTTP v2 protocol stuff. ***
+ *
+ * We assume that if mod_dav_svn sends one of the special v2 OPTIONs
+ * response headers, it has sent all of them. Specifically, we'll
+ * be looking at the presence of the "me resource" as a flag that
+ * the server supports v2 of our HTTP protocol.
+ */
+
+ /* The "me resource". Typically used as a target for REPORTs that
+ are path-agnostic. If we have this, we can speak HTTP v2 to the
+ server. */
+ const char *me_resource;
+
+ /* Opaque URL "stubs". If the OPTIONS response returns these, then
+ we know we're using HTTP protocol v2. */
+ const char *rev_stub; /* for accessing revisions (i.e. revprops) */
+ const char *rev_root_stub; /* for accessing REV/PATH pairs */
+ const char *txn_stub; /* for accessing transactions (i.e. txnprops) */
+ const char *txn_root_stub; /* for accessing TXN/PATH pairs */
+ const char *vtxn_stub; /* for accessing transactions (i.e. txnprops) */
+ const char *vtxn_root_stub; /* for accessing TXN/PATH pairs */
+
+ /* Hash mapping const char * server-supported POST types to
+ disinteresting-but-non-null values. */
+ apr_hash_t *supported_posts;
+
+ /*** End HTTP v2 stuff ***/
+
+ svn_ra_serf__blncache_t *blncache;
+
+ /* Trisate flag that indicates user preference for using bulk updates
+ (svn_tristate_true) with all the properties and content in the
+ update-report response. If svn_tristate_false, request a skelta
+ update-report with inlined properties. If svn_tristate_unknown then use
+ server preference. */
+ svn_tristate_t bulk_updates;
+
+ /* Indicates if the server wants bulk update requests (Prefer) or only
+ accepts skelta requests (Off). If this value is On both options are
+ allowed. */
+ const char *server_allows_bulk;
+
+ /* Indicates if the server supports sending inlined props in update editor
+ * in skelta mode (send-all == 'false'). */
+ svn_boolean_t supports_inline_props;
+
+ /* Indicates whether the server supports issuing replay REPORTs
+ against rev resources (children of `rev_stub', elsestruct). */
+ svn_boolean_t supports_rev_rsrc_replay;
+};
+
+#define SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(sess) ((sess)->me_resource != NULL)
+
+/*
+ * Structure which represents a DAV element with a NAMESPACE and NAME.
+ */
+typedef struct svn_ra_serf__dav_props_t {
+ /* Element namespace */
+ const char *namespace;
+ /* Element name */
+ const char *name;
+} svn_ra_serf__dav_props_t;
+
+/*
+ * Structure which represents an XML namespace.
+ */
+typedef struct ns_t {
+ /* The assigned name. */
+ const char *namespace;
+ /* The full URL for this namespace. */
+ const char *url;
+ /* The next namespace in our list. */
+ struct ns_t *next;
+} svn_ra_serf__ns_t;
+
+/*
+ * An incredibly simple list.
+ */
+typedef struct ra_serf_list_t {
+ void *data;
+ struct ra_serf_list_t *next;
+} svn_ra_serf__list_t;
+
+/** DAV property sets **/
+
+static const svn_ra_serf__dav_props_t base_props[] =
+{
+ { "DAV:", "version-controlled-configuration" },
+ { "DAV:", "resourcetype" },
+ { SVN_DAV_PROP_NS_DAV, "baseline-relative-path" },
+ { SVN_DAV_PROP_NS_DAV, "repository-uuid" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t checked_in_props[] =
+{
+ { "DAV:", "checked-in" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t baseline_props[] =
+{
+ { "DAV:", "baseline-collection" },
+ { "DAV:", SVN_DAV__VERSION_NAME },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t all_props[] =
+{
+ { "DAV:", "allprop" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t check_path_props[] =
+{
+ { "DAV:", "resourcetype" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t type_and_checksum_props[] =
+{
+ { "DAV:", "resourcetype" },
+ { SVN_DAV_PROP_NS_DAV, "sha1-checksum" },
+ { NULL }
+};
+
+/* WC props compatibility with ra_neon. */
+#define SVN_RA_SERF__WC_CHECKED_IN_URL SVN_PROP_WC_PREFIX "ra_dav:version-url"
+
+/** Serf utility functions **/
+
+apr_status_t
+svn_ra_serf__conn_setup(apr_socket_t *sock,
+ serf_bucket_t **read_bkt,
+ serf_bucket_t **write_bkt,
+ void *baton,
+ apr_pool_t *pool);
+
+void
+svn_ra_serf__conn_closed(serf_connection_t *conn,
+ void *closed_baton,
+ apr_status_t why,
+ apr_pool_t *pool);
+
+
+/* Helper function to provide SSL client certificates.
+ *
+ * NOTE: This function sets the session's 'pending_error' member when
+ * returning an non-success status.
+ */
+apr_status_t
+svn_ra_serf__handle_client_cert(void *data,
+ const char **cert_path);
+
+/* Helper function to provide SSL client certificate passwords.
+ *
+ * NOTE: This function sets the session's 'pending_error' member when
+ * returning an non-success status.
+ */
+apr_status_t
+svn_ra_serf__handle_client_cert_pw(void *data,
+ const char *cert_path,
+ const char **password);
+
+
+/*
+ * This function will run the serf context in SESS until *DONE is TRUE.
+ */
+svn_error_t *
+svn_ra_serf__context_run_wait(svn_boolean_t *done,
+ svn_ra_serf__session_t *sess,
+ apr_pool_t *scratch_pool);
+
+/* Callback for response handlers */
+typedef svn_error_t *
+(*svn_ra_serf__response_handler_t)(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *scratch_pool);
+
+/* Callback for when a request body is needed. */
+/* ### should pass a scratch_pool */
+typedef svn_error_t *
+(*svn_ra_serf__request_body_delegate_t)(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *request_pool);
+
+/* Callback for when request headers are needed. */
+/* ### should pass a scratch_pool */
+typedef svn_error_t *
+(*svn_ra_serf__request_header_delegate_t)(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *request_pool);
+
+/* Callback for when a response has an error. */
+typedef svn_error_t *
+(*svn_ra_serf__response_error_t)(serf_request_t *request,
+ serf_bucket_t *response,
+ int status_code,
+ void *baton);
+
+/* ### we should reorder the types in this file. */
+typedef struct svn_ra_serf__server_error_t svn_ra_serf__server_error_t;
+
+/*
+ * Structure that can be passed to our default handler to guide the
+ * execution of the request through its lifecycle.
+ */
+typedef struct svn_ra_serf__handler_t {
+ /* The HTTP method string of the request */
+ const char *method;
+
+ /* The resource to the execute the method on. */
+ const char *path;
+
+ /* The content-type of the request body. */
+ const char *body_type;
+
+ /* If TRUE then default Accept-Encoding request header is not configured for
+ request. If FALSE then 'gzip' accept encoding will be used if compression
+ enabled. */
+ svn_boolean_t custom_accept_encoding;
+
+ /* Has the request/response been completed? */
+ svn_boolean_t done;
+
+ /* If we captured an error from the server, then this will be non-NULL.
+ It will be allocated from HANDLER_POOL. */
+ svn_ra_serf__server_error_t *server_error;
+
+ /* The handler and baton pair for our handler. */
+ svn_ra_serf__response_handler_t response_handler;
+ void *response_baton;
+
+ /* When REPONSE_HANDLER is invoked, the following fields will be set
+ based on the response header. HANDLER_POOL must be non-NULL for these
+ values to be filled in. SLINE.REASON and LOCATION will be allocated
+ within HANDLER_POOL. */
+ serf_status_line sline; /* The parsed Status-Line */
+ const char *location; /* The Location: header, if any */
+
+ /* The handler and baton pair to be executed when a non-recoverable error
+ * is detected. If it is NULL in the presence of an error, an abort() may
+ * be triggered.
+ */
+ svn_ra_serf__response_error_t response_error;
+ void *response_error_baton;
+
+ /* This function and baton pair allows for custom request headers to
+ * be set.
+ *
+ * It will be executed after the request has been set up but before it is
+ * delivered.
+ */
+ svn_ra_serf__request_header_delegate_t header_delegate;
+ void *header_delegate_baton;
+
+ /* This function and baton pair allows a body to be created right before
+ * delivery.
+ *
+ * It will be executed after the request has been set up but before it is
+ * delivered.
+ *
+ * May be NULL if there is no body to send.
+ *
+ */
+ svn_ra_serf__request_body_delegate_t body_delegate;
+ void *body_delegate_baton;
+
+ /* The connection and session to be used for this request. */
+ svn_ra_serf__connection_t *conn;
+ svn_ra_serf__session_t *session;
+
+ /* Internal flag to indicate we've parsed the headers. */
+ svn_boolean_t reading_body;
+
+ /* When this flag will be set, the core handler will discard any unread
+ portion of the response body. The registered response handler will
+ no longer be called. */
+ svn_boolean_t discard_body;
+
+ /* Pool for allocating SLINE.REASON and LOCATION. If this pool is NULL,
+ then the requestor does not care about SLINE and LOCATION. */
+ apr_pool_t *handler_pool;
+
+} svn_ra_serf__handler_t;
+
+
+/* Run one request and process the response.
+
+ Similar to context_run_wait(), but this creates the request for HANDLER
+ and then waits for it to complete.
+
+ WARNING: context_run_wait() does NOT create a request, whereas this
+ function DOES. Avoid a double-create. */
+svn_error_t *
+svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool);
+
+
+/*
+ * Helper function to queue a request in the @a handler's connection.
+ */
+void svn_ra_serf__request_create(svn_ra_serf__handler_t *handler);
+
+/* XML helper callbacks. */
+
+typedef struct svn_ra_serf__xml_state_t {
+ /* A numeric value that represents the current state in parsing.
+ *
+ * Value 0 is reserved for use as the default state.
+ */
+ int current_state;
+
+ /* Private pointer set by the parsing code. */
+ void *private;
+
+ /* Allocations should be made in this pool to match the lifetime of the
+ * state.
+ */
+ apr_pool_t *pool;
+
+ /* The currently-declared namespace for this state. */
+ svn_ra_serf__ns_t *ns_list;
+
+ /* Our previous states. */
+ struct svn_ra_serf__xml_state_t *prev;
+} svn_ra_serf__xml_state_t;
+
+/* Forward declaration of the XML parser structure. */
+typedef struct svn_ra_serf__xml_parser_t svn_ra_serf__xml_parser_t;
+
+/* Callback invoked with @a baton by our XML @a parser when an element with
+ * the @a name containing @a attrs is opened.
+ */
+typedef svn_error_t *
+(*svn_ra_serf__xml_start_element_t)(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool);
+
+/* Callback invoked with @a baton by our XML @a parser when an element with
+ * the @a name is closed.
+ */
+typedef svn_error_t *
+(*svn_ra_serf__xml_end_element_t)(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool);
+
+/* Callback invoked with @a baton by our XML @a parser when a CDATA portion
+ * of @a data with size @a len is encountered.
+ *
+ * This may be invoked multiple times for the same tag.
+ */
+typedef svn_error_t *
+(*svn_ra_serf__xml_cdata_chunk_handler_t)(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
+
+/*
+ * Helper structure associated with handle_xml_parser handler that will
+ * specify how an XML response will be processed.
+ */
+struct svn_ra_serf__xml_parser_t {
+ /* Temporary allocations should be made in this pool. */
+ apr_pool_t *pool;
+
+ /* What kind of response are we parsing? If set, this should typically
+ define the report name. */
+ const char *response_type;
+
+ /* Caller-specific data passed to the start, end, cdata callbacks. */
+ void *user_data;
+
+ /* Callback invoked when a tag is opened. */
+ svn_ra_serf__xml_start_element_t start;
+
+ /* Callback invoked when a tag is closed. */
+ svn_ra_serf__xml_end_element_t end;
+
+ /* Callback invoked when a cdata chunk is received. */
+ svn_ra_serf__xml_cdata_chunk_handler_t cdata;
+
+ /* Our associated expat-based XML parser. */
+ XML_Parser xmlp;
+
+ /* Our current state. */
+ svn_ra_serf__xml_state_t *state;
+
+ /* Our previously used states (will be reused). */
+ svn_ra_serf__xml_state_t *free_state;
+
+ /* If non-NULL, this value will be set to TRUE when the response is
+ * completed.
+ */
+ svn_boolean_t *done;
+
+ /* If non-NULL, when this parser completes, it will add done_item to
+ * the list.
+ */
+ svn_ra_serf__list_t **done_list;
+
+ /* A pointer to the item that will be inserted into the list upon
+ * completeion.
+ */
+ svn_ra_serf__list_t *done_item;
+
+ /* If this flag is TRUE, errors during parsing will be ignored.
+ *
+ * This is mainly used when we are processing an error XML response to
+ * avoid infinite loops.
+ */
+ svn_boolean_t ignore_errors;
+
+ /* If an error occurred, this value will be non-NULL. */
+ svn_error_t *error;
+
+ /* Deciding whether to pause, or not, is performed within the parsing
+ callbacks. If a callback decides to set this flag, then the loop
+ driving the parse (generally, a series of calls to serf_context_run())
+ is going to need to coordinate the un-pausing of the parser by
+ processing pending content. Thus, deciding to pause the parser is a
+ coordinate effort rather than merely setting this flag.
+
+ When an XML parsing callback sets this flag, note that additional
+ elements may be parsed (as the current buffer is consumed). At some
+ point, the flag will be recognized and arriving network content will
+ be stashed away in the PENDING structure (see below).
+
+ At some point, the controlling loop should clear this value. The
+ underlying network processing will note the change and begin passing
+ content into the XML callbacks.
+
+ Note that the controlling loop should also process pending content
+ since the arriving network content will typically finish first. */
+ svn_boolean_t paused;
+
+ /* While the XML parser is paused, content arriving from the server
+ must be saved locally. We cannot stop reading, or the server may
+ decide to drop the connection. The content will be stored in memory
+ up to a certain limit, and will then be spilled over to disk.
+
+ See libsvn_ra_serf/util.c */
+ struct svn_ra_serf__pending_t *pending;
+
+ /* Response restart support */
+ const void *headers_baton; /* Last pointer to headers */
+ apr_off_t skip_size; /* Number of bytes to skip */
+ apr_off_t read_size; /* Number of bytes read from response */
+};
+
+
+/* v2 of the XML parsing functions */
+
+/* The XML parsing context. */
+typedef struct svn_ra_serf__xml_context_t svn_ra_serf__xml_context_t;
+
+
+/* An opaque structure for the XML parse element/state. */
+typedef struct svn_ra_serf__xml_estate_t svn_ra_serf__xml_estate_t;
+
+/* Called just after the parser moves into ENTERED_STATE. The tag causing
+ the transition is passed in TAG.
+
+ This callback is applied to a parsing context by using the
+ svn_ra_serf__xml_context_customize() function.
+
+ NOTE: this callback, when set, will be invoked on *every* transition.
+ The callback must examine ENTERED_STATE to determine if any action
+ must be taken. The original state is not provided, but must be derived
+ from ENTERED_STATE and/or the TAG causing the transition (if needed). */
+typedef svn_error_t *
+(*svn_ra_serf__xml_opened_t)(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool);
+
+
+/* Called just before the parser leaves LEAVING_STATE.
+
+ If cdata collection was enabled for this state, then CDATA will be
+ non-NULL and contain the collected cdata.
+
+ If attribute collection was enabled for this state, then ATTRS will
+ contain the attributes collected for this element only, along with
+ any values stored via svn_ra_serf__xml_note().
+
+ Use svn_ra_serf__xml_gather_since() to gather up data from outer states.
+
+ ATTRS is char* -> char*.
+
+ Temporary allocations may be made in SCRATCH_POOL. */
+typedef svn_error_t *
+(*svn_ra_serf__xml_closed_t)(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool);
+
+
+/* Called for all states that are not using the builtin cdata collection.
+ This callback is (only) appropriate for unbounded-size cdata content.
+
+ CURRENT_STATE may be used to decide what to do with the data.
+
+ Temporary allocations may be made in SCRATCH_POOL. */
+typedef svn_error_t *
+(*svn_ra_serf__xml_cdata_t)(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool);
+
+
+/* State transition table.
+
+ When the XML Context is constructed, it is in state 0. User states are
+ positive integers.
+
+ In a list of transitions, use { 0 } to indicate the end. Specifically,
+ the code looks for NS == NULL.
+
+ ### more docco
+*/
+typedef struct svn_ra_serf__xml_transition_t {
+ /* This transition applies when in this state */
+ int from_state;
+
+ /* And when this tag is observed */
+ const char *ns;
+ const char *name;
+
+ /* Moving to this state */
+ int to_state;
+
+ /* Should the cdata of NAME be collected? Note that CUSTOM_CLOSE should
+ be TRUE in order to capture this cdata. */
+ svn_boolean_t collect_cdata;
+
+ /* Which attributes of NAME should be collected? Terminate with NULL.
+ Maximum of 10 attributes may be collected. Note that attribute
+ namespaces are ignored at this time.
+
+ Attribute names beginning with "?" are optional. Other names must
+ exist on the element, or SVN_ERR_XML_ATTRIB_NOT_FOUND will be raised. */
+ const char *collect_attrs[11];
+
+ /* When NAME is closed, should the callback be invoked? */
+ svn_boolean_t custom_close;
+
+} svn_ra_serf__xml_transition_t;
+
+
+/* Construct an XML parsing context, based on the TTABLE transition table.
+ As content is parsed, the CLOSED_CB callback will be invoked according
+ to the definition in the table.
+
+ If OPENED_CB is not NULL, then it will be invoked for *every* tag-open
+ event. The callback will need to use the ENTERED_STATE and TAG parameters
+ to decide what it would like to do.
+
+ If CDATA_CB is not NULL, then it will be called for all cdata that is
+ not be automatically collected (based on the transition table record's
+ COLLECT_CDATA flag). It will be called in every state, so the callback
+ must examine the CURRENT_STATE parameter to decide what to do.
+
+ The same BATON value will be passed to all three callbacks.
+
+ The context will be created within RESULT_POOL. */
+svn_ra_serf__xml_context_t *
+svn_ra_serf__xml_context_create(
+ const svn_ra_serf__xml_transition_t *ttable,
+ svn_ra_serf__xml_opened_t opened_cb,
+ svn_ra_serf__xml_closed_t closed_cb,
+ svn_ra_serf__xml_cdata_t cdata_cb,
+ void *baton,
+ apr_pool_t *result_pool);
+
+/* Destroy all subpools for this structure. */
+void
+svn_ra_serf__xml_context_destroy(
+ svn_ra_serf__xml_context_t *xmlctx);
+
+/* Construct a handler with the response function/baton set up to parse
+ a response body using the given XML context. The handler and its
+ internal structures are allocated in RESULT_POOL.
+
+ This also initializes HANDLER_POOL to the given RESULT_POOL. */
+svn_ra_serf__handler_t *
+svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
+ apr_pool_t *result_pool);
+
+
+/* Allocated within XES->STATE_POOL. Changes are not allowd (callers
+ should make a deep copy if they need to make changes).
+
+ The resulting hash maps char* names to char* values. */
+apr_hash_t *
+svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t *xes,
+ int stop_state);
+
+
+/* Attach the NAME/VALUE pair onto this/parent state identified by STATE.
+ The name and value will be copied into the target state's pool.
+
+ These values will be available to the CLOSED_CB for the target state,
+ or part of the gathered state via xml_gather_since().
+
+ Typically, this function is used by a child state's close callback,
+ or within an opening callback to store additional data.
+
+ Note: if the state is not found, then a programmer error has occurred,
+ so the function will invoke SVN_ERR_MALFUNCTION(). */
+void
+svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes,
+ int state,
+ const char *name,
+ const char *value);
+
+
+/* Returns XES->STATE_POOL for allocating structures that should live
+ as long as the state identified by XES.
+
+ Note: a state pool is created upon demand, so only use this function
+ when memory is required for a given state. */
+apr_pool_t *
+svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes);
+
+
+/* Any XML parser may be used. When an opening tag is seen, call this
+ function to feed the information into XMLCTX. */
+svn_error_t *
+svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name,
+ const char *const *attrs);
+
+
+/* When a close tag is seen, call this function to feed the information
+ into XMLCTX. */
+svn_error_t *
+svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name);
+
+
+/* When cdata is parsed by the wrapping XML parser, call this function to
+ feed the cdata into the XMLCTX. */
+svn_error_t *
+svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
+ const char *data,
+ apr_size_t len);
+
+
+/*
+ * Parses a server-side error message into a local Subversion error.
+ */
+struct svn_ra_serf__server_error_t {
+ /* Our local representation of the error. */
+ svn_error_t *error;
+
+ /* Are we done with the response? */
+ svn_boolean_t done;
+
+ /* Have we seen an error tag? */
+ svn_boolean_t in_error;
+
+ /* Have we seen a HTTP "412 Precondition Failed" error? */
+ svn_boolean_t contains_precondition_error;
+
+ /* Should we be collecting the XML cdata? */
+ svn_boolean_t collect_cdata;
+
+ /* Collected cdata. NULL if cdata not needed. */
+ svn_stringbuf_t *cdata;
+
+ /* XML parser and namespace used to parse the remote response */
+ svn_ra_serf__xml_parser_t parser;
+};
+
+
+/*
+ * Handler that discards the entire @a response body associated with a
+ * @a request. Implements svn_ra_serf__response_handler_t.
+ *
+ * If @a baton is a svn_ra_serf__server_error_t (i.e. non-NULL) and an
+ * error is detected, it will be populated for later detection.
+ *
+ * All temporary allocations will be made in a @a pool.
+ */
+svn_error_t *
+svn_ra_serf__handle_discard_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/*
+ * Handler that retrieves the embedded XML multistatus response from the
+ * the @a RESPONSE body associated with a @a REQUEST.
+ *
+ * Implements svn_ra_serf__response_handler_t.
+ *
+ * The @a BATON should be of type svn_ra_serf__handler_t. When the request
+ * is complete, the handler's DONE flag will be set to TRUE.
+ *
+ * All temporary allocations will be made in a @a scratch_pool.
+ */
+svn_error_t *
+svn_ra_serf__handle_multistatus_only(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+
+/* Handler that expects an empty body.
+
+ If a body IS present, and it is text/xml, then it will be parsed for
+ a server-side error.
+
+ BATON should be the svn_ra_serf__handler_t running REQUEST.
+
+ Status line information will be in HANDLER->SLINE.
+
+ Any parsed errors will be left in HANDLER->SERVER_ERROR. That member
+ may be NULL if no body was present, or a problem occurred trying to
+ parse the body.
+
+ All temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__expect_empty_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+
+/*
+ * This function will feed the RESPONSE body into XMLP. When parsing is
+ * completed (i.e. an EOF is received), *DONE is set to TRUE.
+ * Implements svn_ra_serf__response_handler_t.
+ *
+ * If an error occurs during processing RESP_ERR is invoked with the
+ * RESP_ERR_BATON.
+ *
+ * Temporary allocations are made in POOL.
+ */
+svn_error_t *
+svn_ra_serf__handle_xml_parser(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/* serf_response_handler_t implementation that completely discards
+ * the response.
+ *
+ * All temporary allocations will be made in @a pool.
+ */
+apr_status_t
+svn_ra_serf__response_discard_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/** XML helper functions. **/
+
+/*
+ * Advance the internal XML @a parser to the @a state.
+ */
+void
+svn_ra_serf__xml_push_state(svn_ra_serf__xml_parser_t *parser,
+ int state);
+
+/*
+ * Return to the previous internal XML @a parser state.
+ */
+void
+svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser);
+
+
+svn_error_t *
+svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
+ svn_boolean_t *network_eof,
+ apr_pool_t *scratch_pool);
+
+
+/*
+ * Add the appropriate serf buckets to @a agg_bucket represented by
+ * the XML * @a tag and @a value.
+ *
+ * The bucket will be allocated from @a bkt_alloc.
+ */
+void
+svn_ra_serf__add_tag_buckets(serf_bucket_t *agg_bucket,
+ const char *tag,
+ const char *value,
+ serf_bucket_alloc_t *bkt_alloc);
+
+/*
+ * Add the appropriate serf buckets to AGG_BUCKET with standard XML header:
+ * <?xml version="1.0" encoding="utf-8"?>
+ *
+ * The bucket will be allocated from BKT_ALLOC.
+ */
+void
+svn_ra_serf__add_xml_header_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc);
+
+/*
+ * Add the appropriate serf buckets to AGG_BUCKET representing the XML
+ * open tag with name TAG.
+ *
+ * Take the tag's attributes from varargs, a NULL-terminated list of
+ * alternating <tt>char *</tt> key and <tt>char *</tt> val. Attribute
+ * will be ignored if it's value is NULL.
+ *
+ * NOTE: Callers are responsible for XML-escaping attribute values as
+ * necessary.
+ *
+ * The bucket will be allocated from BKT_ALLOC.
+ */
+void
+svn_ra_serf__add_open_tag_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *tag,
+ ...);
+
+/*
+ * Add the appropriate serf buckets to AGG_BUCKET representing xml tag close
+ * with name TAG.
+ *
+ * The bucket will be allocated from BKT_ALLOC.
+ */
+void
+svn_ra_serf__add_close_tag_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *tag);
+
+/*
+ * Add the appropriate serf buckets to AGG_BUCKET with xml-escaped
+ * version of DATA.
+ *
+ * The bucket will be allocated from BKT_ALLOC.
+ */
+void
+svn_ra_serf__add_cdata_len_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *data, apr_size_t len);
+/*
+ * Look up the @a attrs array for namespace definitions and add each one
+ * to the @a ns_list of namespaces.
+ *
+ * New namespaces will be allocated in RESULT_POOL.
+ */
+void
+svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *result_pool);
+
+/*
+ * Look up @a name in the @a ns_list list for previously declared namespace
+ * definitions.
+ *
+ * Return (in @a *returned_prop_name) a #svn_ra_serf__dav_props_t tuple
+ * representing the expanded name.
+ */
+void
+svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
+ const svn_ra_serf__ns_t *ns_list,
+ const char *name);
+
+
+/** PROPFIND-related functions **/
+
+/*
+ * This function will deliver a PROP_CTX PROPFIND request in the SESS
+ * serf context for the properties listed in LOOKUP_PROPS at URL for
+ * DEPTH ("0","1","infinity").
+ *
+ * This function will not block waiting for the response. Callers are
+ * expected to call svn_ra_serf__wait_for_props().
+ */
+svn_error_t *
+svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler,
+ apr_hash_t *prop_vals,
+ svn_ra_serf__session_t *sess,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t rev,
+ const char *depth,
+ const svn_ra_serf__dav_props_t *lookup_props,
+ svn_ra_serf__list_t **done_list,
+ apr_pool_t *pool);
+
+/*
+ * This helper function will block until PROPFIND_HANDLER indicates that is
+ * done or another error is returned.
+ */
+svn_error_t *
+svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool);
+
+/* This is a blocking version of deliver_props.
+
+ The properties are fetched and placed into RESULTS, allocated in
+ RESULT_POOL.
+
+ ### more docco about the other params.
+
+ Temporary allocations are made in SCRATCH_POOL.
+*/
+svn_error_t *
+svn_ra_serf__retrieve_props(apr_hash_t **results,
+ svn_ra_serf__session_t *sess,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t rev,
+ const char *depth,
+ const svn_ra_serf__dav_props_t *props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Using CONN, fetch the properties specified by WHICH_PROPS using CONN
+ for URL at REVISION. The resulting properties are placed into a 2-level
+ hash in RESULTS, mapping NAMESPACE -> hash<PROPNAME, PROPVALUE>, which
+ is allocated in RESULT_POOL.
+
+ If REVISION is SVN_INVALID_REVNUM, then the properties are fetched
+ from HEAD for URL.
+
+ This function performs the request synchronously.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__fetch_node_props(apr_hash_t **results,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *which_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Using CONN, fetch a DAV: property from the resource identified by URL
+ within REVISION. The PROPNAME may be one of:
+
+ "checked-in"
+ "href"
+
+ The resulting value will be allocated in RESULT_POOL, and may be NULL
+ if the property does not exist (note: "href" always exists).
+
+ This function performs the request synchronously.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__fetch_dav_prop(const char **value,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set PROPS for PATH at REV revision with a NS:NAME VAL.
+ *
+ * The POOL governs allocation.
+ */
+void
+svn_ra_serf__set_ver_prop(apr_hash_t *props,
+ const char *path, svn_revnum_t rev,
+ const char *ns, const char *name,
+ const svn_string_t *val, apr_pool_t *pool);
+#define svn_ra_serf__set_rev_prop svn_ra_serf__set_ver_prop
+
+/** Property walker functions **/
+
+typedef svn_error_t *
+(*svn_ra_serf__walker_visitor_t)(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *pool);
+
+svn_error_t *
+svn_ra_serf__walk_all_props(apr_hash_t *props,
+ const char *name,
+ svn_revnum_t rev,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/* Like walk_all_props(), but a 2-level hash. */
+svn_error_t *
+svn_ra_serf__walk_node_props(apr_hash_t *props,
+ svn_ra_serf__walker_visitor_t walker,
+ void *baton,
+ apr_pool_t *scratch_pool);
+
+
+typedef svn_error_t *
+(*svn_ra_serf__path_rev_walker_t)(void *baton,
+ const char *path, apr_ssize_t path_len,
+ const char *ns, apr_ssize_t ns_len,
+ const char *name, apr_ssize_t name_len,
+ const svn_string_t *val,
+ apr_pool_t *pool);
+svn_error_t *
+svn_ra_serf__walk_all_paths(apr_hash_t *props,
+ svn_revnum_t rev,
+ svn_ra_serf__path_rev_walker_t walker,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/* Map a property name, as passed over the wire, into its corresponding
+ Subversion-internal name. The returned name will be a static value,
+ or allocated within RESULT_POOL.
+
+ If the property should be ignored (eg. some DAV properties), then NULL
+ will be returned. */
+const char *
+svn_ra_serf__svnname_from_wirename(const char *ns,
+ const char *name,
+ apr_pool_t *result_pool);
+
+
+/* Select the basic revision properties from the set of "all" properties.
+ Return these in *REVPROPS, allocated from RESULT_POOL. */
+svn_error_t *
+svn_ra_serf__select_revprops(apr_hash_t **revprops,
+ const char *name,
+ svn_revnum_t rev,
+ apr_hash_t *all_revprops,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* PROPS is nested hash tables mapping NS -> NAME -> VALUE.
+ This function takes the NS:NAME:VALUE hashes and flattens them into a set of
+ names to VALUE. The names are composed of NS:NAME, with specific
+ rewrite from wire names (DAV) to SVN names. This mapping is managed
+ by the svn_ra_serf__set_baton_props() function.
+
+ FLAT_PROPS is allocated in RESULT_POOL.
+ ### right now, we do a shallow copy from PROPS to FLAT_PROPS. therefore,
+ ### the names and values in PROPS must be in the proper pool.
+
+ Temporary allocations are made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__flatten_props(apr_hash_t **flat_props,
+ apr_hash_t *props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Return the property value for PATH at REV revision with a NS:NAME.
+ * PROPS is a four-level nested hash: (svn_revnum_t => char *path =>
+ * char *ns => char *name => svn_string_t *). */
+const svn_string_t *
+svn_ra_serf__get_ver_prop_string(apr_hash_t *props,
+ const char *path, svn_revnum_t rev,
+ const char *ns, const char *name);
+
+/* Same as svn_ra_serf__get_ver_prop_string(), but returns a C string. */
+const char *
+svn_ra_serf__get_ver_prop(apr_hash_t *props,
+ const char *path, svn_revnum_t rev,
+ const char *ns, const char *name);
+
+/* Same as svn_ra_serf__get_ver_prop_string(), but for the unknown revision. */
+const svn_string_t *
+svn_ra_serf__get_prop_string(apr_hash_t *props,
+ const char *path,
+ const char *ns,
+ const char *name);
+
+/* Same as svn_ra_serf__get_ver_prop(), but for the unknown revision. */
+const char *
+svn_ra_serf__get_prop(apr_hash_t *props,
+ const char *path,
+ const char *ns,
+ const char *name);
+
+/* Same as svn_ra_serf__set_rev_prop(), but for the unknown revision. */
+void
+svn_ra_serf__set_prop(apr_hash_t *props, const char *path,
+ const char *ns, const char *name,
+ const svn_string_t *val, apr_pool_t *pool);
+
+svn_error_t *
+svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
+ apr_hash_t *props);
+
+
+/** MERGE-related functions **/
+
+void
+svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens,
+ const char *parent,
+ serf_bucket_t *body,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool);
+
+/* Create an MERGE request aimed at the SESSION url, requesting the
+ merge of the resource identified by MERGE_RESOURCE_URL.
+ LOCK_TOKENS is a hash mapping paths to lock tokens owned by the
+ client. If KEEP_LOCKS is set, instruct the server to not release
+ locks set on the paths included in this commit. */
+svn_error_t *
+svn_ra_serf__run_merge(const svn_commit_info_t **commit_info,
+ int *response_code,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *merge_resource_url,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** OPTIONS-related functions **/
+
+/* On HTTPv2 connections, run an OPTIONS request over CONN to fetch the
+ current youngest revnum, returning it in *YOUNGEST.
+
+ (the revnum is headers of the OPTIONS response)
+
+ This function performs the request synchronously.
+
+ All temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *scratch_pool);
+
+
+/* On HTTPv1 connections, run an OPTIONS request over CONN to fetch the
+ activity collection set and return it in *ACTIVITY_URL, allocated
+ from RESULT_POOL.
+
+ (the activity-collection-set is in the body of the OPTIONS response)
+
+ This function performs the request synchronously.
+
+ All temporary allocations will be made in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__v1_get_activity_collection(const char **activity_url,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/* Set @a VCC_URL to the default VCC for our repository based on @a
+ * ORIG_PATH for the session @a SESSION, ensuring that the VCC URL and
+ * repository root URLs are cached in @a SESSION. Use @a CONN for any
+ * required network communications if it is non-NULL; otherwise use the
+ * default connection.
+ *
+ * All temporary allocations will be made in @a POOL. */
+svn_error_t *
+svn_ra_serf__discover_vcc(const char **vcc_url,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool);
+
+/* Set @a REPORT_TARGET to the URI of the resource at which generic
+ * (path-agnostic) REPORTs should be aimed for @a SESSION. Use @a
+ * CONN for any required network communications if it is non-NULL;
+ * otherwise use the default connection.
+ *
+ * All temporary allocations will be made in @a POOL.
+ */
+svn_error_t *
+svn_ra_serf__report_resource(const char **report_target,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool);
+
+/* Set @a REL_PATH to a path (not URI-encoded) relative to the root of
+ * the repository pointed to by @a SESSION, based on original path
+ * (URI-encoded) @a ORIG_PATH. Use @a CONN for any required network
+ * communications if it is non-NULL; otherwise use the default
+ * connection. Use POOL for allocations. */
+svn_error_t *
+svn_ra_serf__get_relative_path(const char **rel_path,
+ const char *orig_path,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool);
+
+
+/* Using the default connection in SESSION (conns[0]), get the youngest
+ revnum from the server, returning it in *YOUNGEST.
+
+ This function operates synchronously.
+
+ All temporary allocations are performed in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *scratch_pool);
+
+
+/* Generate a revision-stable URL.
+
+ The RA APIs all refer to user/public URLs that float along with the
+ youngest revision. In many cases, we do NOT want to work with that URL
+ since it can change from one moment to the next. Especially if we
+ attempt to operation against multiple floating URLs -- we could end up
+ referring to two separate revisions.
+
+ The DAV RA provider(s) solve this by generating a URL that is specific
+ to a revision by using a URL into a "baseline collection".
+
+ For a specified SESSION, with an optional CONN (if NULL, then the
+ session's default connection will be used; specifically SESSION->conns[0]),
+ generate a revision-stable URL for URL at REVISION. If REVISION is
+ SVN_INVALID_REVNUM, then the stable URL will refer to the youngest
+ revision at the time this function was called.
+
+ If URL is NULL, then the session root will be used.
+
+ The stable URL will be placed into *STABLE_URL, allocated from RESULT_POOL.
+
+ If LATEST_REVNUM is not NULL, then the revision used will be placed into
+ *LATEST_REVNUM. That will be equal to youngest, or the given REVISION.
+
+ This function operates synchronously, if any communication to the server
+ is required. Communication is needed if REVISION is SVN_INVALID_REVNUM
+ (to get the current youngest revnum), or if the specified REVISION is not
+ (yet) in our cache of baseline collections.
+
+ All temporary allocations are performed in SCRATCH_POOL. */
+svn_error_t *
+svn_ra_serf__get_stable_url(const char **stable_url,
+ svn_revnum_t *latest_revnum,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+
+/** RA functions **/
+
+/* Implements svn_ra__vtable_t.get_log(). */
+svn_error_t *
+svn_ra_serf__get_log(svn_ra_session_t *session,
+ const apr_array_header_t *paths,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ int limit,
+ svn_boolean_t discover_changed_paths,
+ svn_boolean_t strict_node_history,
+ svn_boolean_t include_merged_revisions,
+ const apr_array_header_t *revprops,
+ svn_log_entry_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_locations(). */
+svn_error_t *
+svn_ra_serf__get_locations(svn_ra_session_t *session,
+ apr_hash_t **locations,
+ const char *path,
+ svn_revnum_t peg_revision,
+ const apr_array_header_t *location_revisions,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_location_segments(). */
+svn_error_t *
+svn_ra_serf__get_location_segments(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t start_rev,
+ svn_revnum_t end_rev,
+ svn_location_segment_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.do_diff(). */
+svn_error_t *
+svn_ra_serf__do_diff(svn_ra_session_t *session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.do_status(). */
+svn_error_t *
+svn_ra_serf__do_status(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.do_update(). */
+svn_error_t *
+svn_ra_serf__do_update(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Implements svn_ra__vtable_t.do_switch(). */
+svn_error_t *
+svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Implements svn_ra__vtable_t.get_file_revs(). */
+svn_error_t *
+svn_ra_serf__get_file_revs(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_boolean_t include_merged_revisions,
+ svn_file_rev_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_dated_revision(). */
+svn_error_t *
+svn_ra_serf__get_dated_revision(svn_ra_session_t *session,
+ svn_revnum_t *revision,
+ apr_time_t tm,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_commit_editor(). */
+svn_error_t *
+svn_ra_serf__get_commit_editor(svn_ra_session_t *session,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *revprop_table,
+ svn_commit_callback2_t callback,
+ void *callback_baton,
+ apr_hash_t *lock_tokens,
+ svn_boolean_t keep_locks,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_file(). */
+svn_error_t *
+svn_ra_serf__get_file(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.change_rev_prop(). */
+svn_error_t *
+svn_ra_serf__change_rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.replay(). */
+svn_error_t *
+svn_ra_serf__replay(svn_ra_session_t *ra_session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t text_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.replay_range(). */
+svn_error_t *
+svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.lock(). */
+svn_error_t *
+svn_ra_serf__lock(svn_ra_session_t *ra_session,
+ apr_hash_t *path_revs,
+ const char *comment,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.unlock(). */
+svn_error_t *
+svn_ra_serf__unlock(svn_ra_session_t *ra_session,
+ apr_hash_t *path_tokens,
+ svn_boolean_t force,
+ svn_ra_lock_callback_t lock_func,
+ void *lock_baton,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_lock(). */
+svn_error_t *
+svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
+ svn_lock_t **lock,
+ const char *path,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_locks(). */
+svn_error_t *
+svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
+ apr_hash_t **locks,
+ const char *path,
+ svn_depth_t depth,
+ apr_pool_t *pool);
+
+/* Request a mergeinfo-report from the URL attached to SESSION,
+ and fill in the MERGEINFO hash with the results.
+
+ Implements svn_ra__vtable_t.get_mergeinfo().
+ */
+svn_error_t *
+svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session,
+ apr_hash_t **mergeinfo,
+ const apr_array_header_t *paths,
+ svn_revnum_t revision,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ apr_pool_t *pool);
+
+/* Exchange capabilities with the server, by sending an OPTIONS
+ * request announcing the client's capabilities, and by filling
+ * SERF_SESS->capabilities with the server's capabilities as read from
+ * the response headers. Use POOL only for temporary allocation.
+ *
+ * If the CORRECTED_URL is non-NULL, allow the OPTIONS response to
+ * report a server-dictated redirect or relocation (HTTP 301 or 302
+ * error codes), setting *CORRECTED_URL to the value of the corrected
+ * repository URL. Otherwise, such responses from the server will
+ * generate an error. (In either case, no capabilities are exchanged
+ * if there is, in fact, such a response from the server.)
+ */
+svn_error_t *
+svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
+ const char **corrected_url,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.has_capability(). */
+svn_error_t *
+svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
+ svn_boolean_t *has,
+ const char *capability,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.get_deleted_rev(). */
+svn_error_t *
+svn_ra_serf__get_deleted_rev(svn_ra_session_t *session,
+ const char *path,
+ svn_revnum_t peg_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t *revision_deleted,
+ apr_pool_t *pool);
+
+/* Implements the get_inherited_props RA layer function. */
+svn_error_t * svn_ra_serf__get_inherited_props(svn_ra_session_t *session,
+ apr_array_header_t **iprops,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Implements svn_ra__vtable_t.get_repos_root(). */
+svn_error_t *
+svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool);
+
+/* Implements svn_ra__vtable_t.register_editor_shim_callbacks(). */
+svn_error_t *
+svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *session,
+ svn_delta_shim_callbacks_t *callbacks);
+
+/*** Authentication handler declarations ***/
+
+/**
+ * Callback function that loads the credentials for Basic and Digest
+ * authentications, both for server and proxy authentication.
+ */
+apr_status_t
+svn_ra_serf__credentials_callback(char **username, char **password,
+ serf_request_t *request, void *baton,
+ int code, const char *authn_type,
+ const char *realm,
+ apr_pool_t *pool);
+
+
+/*** General utility functions ***/
+
+/**
+ * Convert an HTTP STATUS_CODE resulting from a WebDAV request against
+ * PATH to the relevant error code. Use the response-supplied LOCATION
+ * where it necessary.
+ */
+svn_error_t *
+svn_ra_serf__error_on_status(int status_code,
+ const char *path,
+ const char *location);
+
+/* ###? */
+svn_error_t *
+svn_ra_serf__copy_into_spillbuf(svn_spillbuf_t **spillbuf,
+ serf_bucket_t *bkt,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* ###? */
+serf_bucket_t *
+svn_ra_serf__create_sb_bucket(svn_spillbuf_t *spillbuf,
+ serf_bucket_alloc_t *allocator,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/** Wrap STATUS from an serf function. If STATUS is not serf error code,
+ * this is equivalent to svn_error_wrap_apr().
+ */
+svn_error_t *
+svn_ra_serf__wrap_err(apr_status_t status,
+ const char *fmt,
+ ...);
+
+
+#if defined(SVN_DEBUG)
+/* Wrapper macros to collect file and line information */
+#define svn_ra_serf__wrap_err \
+ (svn_error__locate(__FILE__,__LINE__), (svn_ra_serf__wrap_err))
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_RA_SERF_RA_SERF_H */
diff --git a/subversion/libsvn_ra_serf/replay.c b/subversion/libsvn_ra_serf/replay.c
new file mode 100644
index 0000000..1fcecf4
--- /dev/null
+++ b/subversion/libsvn_ra_serf/replay.c
@@ -0,0 +1,920 @@
+/*
+ * replay.c : entry point for replay RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_base64.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+
+#include "private/svn_string_private.h"
+
+#include "ra_serf.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing.
+ */
+typedef enum replay_state_e {
+ NONE = 0,
+ REPORT,
+ OPEN_DIR,
+ ADD_DIR,
+ OPEN_FILE,
+ ADD_FILE,
+ DELETE_ENTRY,
+ APPLY_TEXTDELTA,
+ CHANGE_PROP
+} replay_state_e;
+
+typedef struct replay_info_t replay_info_t;
+
+struct replay_info_t {
+ apr_pool_t *pool;
+
+ void *baton;
+ svn_stream_t *stream;
+
+ replay_info_t *parent;
+};
+
+typedef svn_error_t *
+(*change_prop_t)(void *baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool);
+
+typedef struct prop_info_t {
+ apr_pool_t *pool;
+
+ change_prop_t change;
+
+ const char *name;
+ svn_boolean_t del_prop;
+
+ svn_stringbuf_t *prop_value;
+
+ replay_info_t *parent;
+} prop_info_t;
+
+typedef struct replay_context_t {
+ apr_pool_t *src_rev_pool;
+ apr_pool_t *dst_rev_pool;
+ /*file_pool is cleared after completion of each file. */
+ apr_pool_t *file_pool;
+
+ /* Are we done fetching this file? */
+ svn_boolean_t done;
+ svn_ra_serf__list_t **done_list;
+ svn_ra_serf__list_t done_item;
+
+ /* callback to get an editor */
+ svn_ra_replay_revstart_callback_t revstart_func;
+ svn_ra_replay_revfinish_callback_t revfinish_func;
+ void *replay_baton;
+
+ /* replay receiver function and baton */
+ const svn_delta_editor_t *editor;
+ void *editor_baton;
+
+ /* Path and revision used to filter replayed changes. If
+ INCLUDE_PATH is non-NULL, REVISION is unnecessary and will not be
+ included in the replay REPORT. (Because the REPORT is being
+ aimed an HTTP v2 revision resource.) */
+ const char *include_path;
+ svn_revnum_t revision;
+
+ /* Information needed to create the replay report body */
+ svn_revnum_t low_water_mark;
+ svn_boolean_t send_deltas;
+
+ /* Target and revision to fetch revision properties on */
+ const char *revprop_target;
+ svn_revnum_t revprop_rev;
+
+ /* Revision properties for this revision. */
+ apr_hash_t *revs_props;
+ apr_hash_t *props;
+
+ /* Keep a reference to the XML parser ctx to report any errors. */
+ svn_ra_serf__xml_parser_t *parser_ctx;
+
+ /* Handlers for the PROPFIND and REPORT for the current revision. */
+ svn_ra_serf__handler_t *propfind_handler;
+ svn_ra_serf__handler_t *report_handler;
+
+} replay_context_t;
+
+
+static void *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ replay_context_t *replay_ctx,
+ replay_state_e state)
+{
+ svn_ra_serf__xml_push_state(parser, state);
+
+ if (state == OPEN_DIR || state == ADD_DIR ||
+ state == OPEN_FILE || state == ADD_FILE)
+ {
+ replay_info_t *info;
+
+ info = apr_palloc(replay_ctx->dst_rev_pool, sizeof(*info));
+
+ info->pool = replay_ctx->dst_rev_pool;
+ info->parent = parser->state->private;
+ info->baton = NULL;
+ info->stream = NULL;
+
+ parser->state->private = info;
+ }
+ else if (state == CHANGE_PROP)
+ {
+ prop_info_t *info;
+
+ info = apr_pcalloc(replay_ctx->dst_rev_pool, sizeof(*info));
+
+ info->pool = replay_ctx->dst_rev_pool;
+ info->parent = parser->state->private;
+ info->prop_value = svn_stringbuf_create_empty(info->pool);
+
+ parser->state->private = info;
+ }
+
+ return parser->state->private;
+}
+
+static svn_error_t *
+start_replay(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ replay_context_t *ctx = parser->user_data;
+ replay_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE &&
+ strcmp(name.name, "editor-report") == 0)
+ {
+ push_state(parser, ctx, REPORT);
+
+ /* Before we can continue, we need the revision properties. */
+ SVN_ERR_ASSERT(!ctx->propfind_handler || ctx->propfind_handler->done);
+
+ /* Create a pool for the commit editor. */
+ ctx->dst_rev_pool = svn_pool_create(ctx->src_rev_pool);
+ ctx->file_pool = svn_pool_create(ctx->dst_rev_pool);
+
+ SVN_ERR(svn_ra_serf__select_revprops(&ctx->props,
+ ctx->revprop_target,
+ ctx->revprop_rev,
+ ctx->revs_props,
+ ctx->dst_rev_pool,
+ scratch_pool));
+
+ if (ctx->revstart_func)
+ {
+ SVN_ERR(ctx->revstart_func(ctx->revision, ctx->replay_baton,
+ &ctx->editor, &ctx->editor_baton,
+ ctx->props,
+ ctx->dst_rev_pool));
+ }
+ }
+ else if (state == REPORT &&
+ strcmp(name.name, "target-revision") == 0)
+ {
+ const char *rev;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in target-revision element"));
+ }
+
+ SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton,
+ SVN_STR_TO_REV(rev),
+ scratch_pool));
+ }
+ else if (state == REPORT &&
+ strcmp(name.name, "open-root") == 0)
+ {
+ const char *rev;
+ replay_info_t *info;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-root element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_DIR);
+
+ SVN_ERR(ctx->editor->open_root(ctx->editor_baton,
+ SVN_STR_TO_REV(rev),
+ ctx->dst_rev_pool,
+ &info->baton));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "delete-entry") == 0)
+ {
+ const char *file_name, *rev;
+ replay_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+ if (!file_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in delete-entry element"));
+ }
+ rev = svn_xml_get_attr_value("rev", attrs);
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in delete-entry element"));
+ }
+
+ info = push_state(parser, ctx, DELETE_ENTRY);
+
+ SVN_ERR(ctx->editor->delete_entry(file_name, SVN_STR_TO_REV(rev),
+ info->baton, scratch_pool));
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "open-directory") == 0)
+ {
+ const char *rev, *dir_name;
+ replay_info_t *info;
+
+ dir_name = svn_xml_get_attr_value("name", attrs);
+ if (!dir_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in open-directory element"));
+ }
+ rev = svn_xml_get_attr_value("rev", attrs);
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-directory element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_DIR);
+
+ SVN_ERR(ctx->editor->open_directory(dir_name, info->parent->baton,
+ SVN_STR_TO_REV(rev),
+ ctx->dst_rev_pool, &info->baton));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "add-directory") == 0)
+ {
+ const char *dir_name, *copyfrom, *copyrev;
+ svn_revnum_t rev;
+ replay_info_t *info;
+
+ dir_name = svn_xml_get_attr_value("name", attrs);
+ if (!dir_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in add-directory element"));
+ }
+ copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs);
+ copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs);
+
+ if (copyrev)
+ rev = SVN_STR_TO_REV(copyrev);
+ else
+ rev = SVN_INVALID_REVNUM;
+
+ info = push_state(parser, ctx, ADD_DIR);
+
+ SVN_ERR(ctx->editor->add_directory(dir_name, info->parent->baton,
+ copyfrom, rev,
+ ctx->dst_rev_pool, &info->baton));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "close-directory") == 0)
+ {
+ replay_info_t *info = parser->state->private;
+
+ SVN_ERR(ctx->editor->close_directory(info->baton, scratch_pool));
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "open-file") == 0)
+ {
+ const char *file_name, *rev;
+ replay_info_t *info;
+
+ svn_pool_clear(ctx->file_pool);
+ file_name = svn_xml_get_attr_value("name", attrs);
+ if (!file_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in open-file element"));
+ }
+ rev = svn_xml_get_attr_value("rev", attrs);
+ if (!rev)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-file element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_FILE);
+
+ SVN_ERR(ctx->editor->open_file(file_name, info->parent->baton,
+ SVN_STR_TO_REV(rev),
+ ctx->file_pool, &info->baton));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "add-file") == 0)
+ {
+ const char *file_name, *copyfrom, *copyrev;
+ svn_revnum_t rev;
+ replay_info_t *info;
+
+ svn_pool_clear(ctx->file_pool);
+ file_name = svn_xml_get_attr_value("name", attrs);
+ if (!file_name)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in add-file element"));
+ }
+ copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs);
+ copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs);
+
+ info = push_state(parser, ctx, ADD_FILE);
+
+ if (copyrev)
+ rev = SVN_STR_TO_REV(copyrev);
+ else
+ rev = SVN_INVALID_REVNUM;
+
+ SVN_ERR(ctx->editor->add_file(file_name, info->parent->baton,
+ copyfrom, rev,
+ ctx->file_pool, &info->baton));
+ }
+ else if ((state == OPEN_FILE || state == ADD_FILE) &&
+ strcmp(name.name, "apply-textdelta") == 0)
+ {
+ const char *checksum;
+ replay_info_t *info;
+ svn_txdelta_window_handler_t textdelta;
+ void *textdelta_baton;
+ svn_stream_t *delta_stream;
+
+ info = push_state(parser, ctx, APPLY_TEXTDELTA);
+
+ checksum = svn_xml_get_attr_value("checksum", attrs);
+ if (checksum)
+ {
+ checksum = apr_pstrdup(info->pool, checksum);
+ }
+
+ SVN_ERR(ctx->editor->apply_textdelta(info->baton, checksum,
+ ctx->file_pool,
+ &textdelta,
+ &textdelta_baton));
+
+ delta_stream = svn_txdelta_parse_svndiff(textdelta, textdelta_baton,
+ TRUE, info->pool);
+ info->stream = svn_base64_decode(delta_stream, info->pool);
+ }
+ else if ((state == OPEN_FILE || state == ADD_FILE) &&
+ strcmp(name.name, "close-file") == 0)
+ {
+ replay_info_t *info = parser->state->private;
+ const char *checksum;
+
+ checksum = svn_xml_get_attr_value("checksum", attrs);
+
+ SVN_ERR(ctx->editor->close_file(info->baton, checksum, scratch_pool));
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (((state == OPEN_FILE || state == ADD_FILE) &&
+ strcmp(name.name, "change-file-prop") == 0) ||
+ ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "change-dir-prop") == 0))
+ {
+ const char *prop_name;
+ prop_info_t *info;
+
+ prop_name = svn_xml_get_attr_value("name", attrs);
+ if (!prop_name)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in %s element"),
+ name.name);
+ }
+
+ info = push_state(parser, ctx, CHANGE_PROP);
+
+
+ if (svn_xml_get_attr_value("del", attrs))
+ info->del_prop = TRUE;
+ else
+ info->del_prop = FALSE;
+
+ if (state == OPEN_FILE || state == ADD_FILE)
+ {
+ info->name = apr_pstrdup(ctx->file_pool, prop_name);
+ info->change = ctx->editor->change_file_prop;
+ }
+ else
+ {
+ info->name = apr_pstrdup(ctx->dst_rev_pool, prop_name);
+ info->change = ctx->editor->change_dir_prop;
+ }
+
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_replay(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ replay_context_t *ctx = parser->user_data;
+ replay_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == REPORT &&
+ strcmp(name.name, "editor-report") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ if (ctx->revfinish_func)
+ {
+ SVN_ERR(ctx->revfinish_func(ctx->revision, ctx->replay_baton,
+ ctx->editor, ctx->editor_baton,
+ ctx->props,
+ ctx->dst_rev_pool));
+ }
+ svn_pool_destroy(ctx->dst_rev_pool);
+ }
+ else if (state == OPEN_DIR && strcmp(name.name, "open-directory") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if (state == ADD_DIR && strcmp(name.name, "add-directory") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if ((state == OPEN_FILE || state == ADD_FILE) &&
+ strcmp(name.name, "close-file") == 0)
+ {
+ /* Don't do anything. */
+ }
+ else if ((state == APPLY_TEXTDELTA) &&
+ strcmp(name.name, "apply-textdelta") == 0)
+ {
+ replay_info_t *info = parser->state->private;
+ SVN_ERR(svn_stream_close(info->stream));
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == CHANGE_PROP &&
+ (strcmp(name.name, "change-file-prop") == 0 ||
+ strcmp(name.name, "change-dir-prop") == 0))
+ {
+ prop_info_t *info = parser->state->private;
+ const svn_string_t *prop_val;
+
+ if (info->del_prop)
+ {
+ prop_val = NULL;
+ }
+ else
+ {
+ const svn_string_t *morph;
+
+ morph = svn_stringbuf__morph_into_string(info->prop_value);
+#ifdef SVN_DEBUG
+ info->prop_value = NULL; /* morph killed the stringbuf. */
+#endif
+
+ if (strcmp(name.name, "change-file-prop") == 0)
+ prop_val = svn_base64_decode_string(morph, ctx->file_pool);
+ else
+ prop_val = svn_base64_decode_string(morph, ctx->dst_rev_pool);
+ }
+
+ SVN_ERR(info->change(info->parent->baton, info->name, prop_val,
+ info->parent->pool));
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_replay(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ replay_context_t *replay_ctx = parser->user_data;
+ replay_state_e state;
+
+ UNUSED_CTX(replay_ctx);
+
+ state = parser->state->current_state;
+
+ if (state == APPLY_TEXTDELTA)
+ {
+ replay_info_t *info = parser->state->private;
+ apr_size_t written;
+
+ written = len;
+
+ SVN_ERR(svn_stream_write(info->stream, data, &written));
+
+ if (written != len)
+ return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Error writing stream: unexpected EOF"));
+ }
+ else if (state == CHANGE_PROP)
+ {
+ prop_info_t *info = parser->state->private;
+
+ svn_stringbuf_appendbytes(info->prop_value, data, len);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+create_replay_body(serf_bucket_t **bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ replay_context_t *ctx = baton;
+ serf_bucket_t *body_bkt;
+
+ body_bkt = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(body_bkt, alloc,
+ "S:replay-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ /* If we have a non-NULL include path, we add it to the body and
+ omit the revision; otherwise, the reverse. */
+ if (ctx->include_path)
+ {
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:include-path",
+ ctx->include_path,
+ alloc);
+ }
+ else
+ {
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:revision",
+ apr_ltoa(ctx->src_rev_pool, ctx->revision),
+ alloc);
+ }
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:low-water-mark",
+ apr_ltoa(ctx->src_rev_pool, ctx->low_water_mark),
+ alloc);
+
+ svn_ra_serf__add_tag_buckets(body_bkt,
+ "S:send-deltas",
+ apr_ltoa(ctx->src_rev_pool, ctx->send_deltas),
+ alloc);
+
+ svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "S:replay-report");
+
+ *bkt = body_bkt;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__replay(svn_ra_session_t *ra_session,
+ svn_revnum_t revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_pool_t *pool)
+{
+ replay_context_t *replay_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ svn_error_t *err;
+ const char *report_target;
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
+
+ replay_ctx = apr_pcalloc(pool, sizeof(*replay_ctx));
+ replay_ctx->src_rev_pool = pool;
+ replay_ctx->editor = editor;
+ replay_ctx->editor_baton = edit_baton;
+ replay_ctx->done = FALSE;
+ replay_ctx->revision = revision;
+ replay_ctx->low_water_mark = low_water_mark;
+ replay_ctx->send_deltas = send_deltas;
+ replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->handler_pool = pool;
+ handler->method = "REPORT";
+ handler->path = session->session_url.path;
+ handler->body_delegate = create_replay_body;
+ handler->body_delegate_baton = replay_ctx;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = replay_ctx;
+ parser_ctx->start = start_replay;
+ parser_ctx->end = end_replay;
+ parser_ctx->cdata = cdata_replay;
+ parser_ctx->done = &replay_ctx->done;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ /* This is only needed to handle errors during XML parsing. */
+ replay_ctx->parser_ctx = parser_ctx;
+ replay_ctx->report_handler = handler; /* unused */
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool);
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err));
+
+ return SVN_NO_ERROR;
+}
+
+/* The maximum number of outstanding requests at any time. When this
+ * number is reached, ra_serf will stop sending requests until
+ * responses on the previous requests are received and handled.
+ *
+ * Some observations about serf which lead us to the current value.
+ * ----------------------------------------------------------------
+ *
+ * We aim to keep serf's outgoing queue filled with enough requests so
+ * the network bandwidth and server capacity is used
+ * optimally. Originally we used 5 as the max. number of outstanding
+ * requests, but this turned out to be too low.
+ *
+ * Serf doesn't exit out of the svn_ra_serf__context_run_wait loop as long as
+ * it has data to send or receive. With small responses (revs of a few
+ * kB), serf doesn't come out of this loop at all. So with
+ * MAX_OUTSTANDING_REQUESTS set to a low number, there's a big chance
+ * that serf handles those requests completely in its internal loop,
+ * and only then gives us a chance to create new requests. This
+ * results in hiccups, slowing down the whole process.
+ *
+ * With a larger MAX_OUTSTANDING_REQUESTS, like 100 or more, there's
+ * more chance that serf can come out of its internal loop so we can
+ * replenish the outgoing request queue. There's no real disadvantage
+ * of using a large number here, besides the memory used to store the
+ * message, parser and handler objects (approx. 250 bytes).
+ *
+ * In my test setup peak performance was reached at max. 30-35
+ * requests. So I added a small margin and chose 50.
+ */
+#define MAX_OUTSTANDING_REQUESTS 50
+
+svn_error_t *
+svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_revnum_t low_water_mark,
+ svn_boolean_t send_deltas,
+ svn_ra_replay_revstart_callback_t revstart_func,
+ svn_ra_replay_revfinish_callback_t revfinish_func,
+ void *replay_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_revnum_t rev = start_revision;
+ const char *report_target;
+ int active_reports = 0;
+ const char *include_path;
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
+
+ /* Prior to 1.8, mod_dav_svn expect to get replay REPORT requests
+ aimed at the session URL. But that's incorrect -- these reports
+ aren't about specific resources -- they are above revisions. The
+ path-based filtering offered by this API is just that: a filter
+ applied to the full set of changes made in the revision. As
+ such, the correct target for these REPORT requests is the "me
+ resource" (or, pre-http-v2, the default VCC).
+
+ Our server should have told us if it supported this protocol
+ correction. If so, we aimed our report at the correct resource
+ and include the filtering path as metadata within the report
+ body. Otherwise, we fall back to the pre-1.8 behavior and just
+ wish for the best.
+
+ See issue #4287:
+ http://subversion.tigris.org/issues/show_bug.cgi?id=4287
+ */
+ if (session->supports_rev_rsrc_replay)
+ {
+ SVN_ERR(svn_ra_serf__get_relative_path(&include_path,
+ session->session_url.path,
+ session, session->conns[0],
+ pool));
+ }
+ else
+ {
+ include_path = NULL;
+ }
+
+ while (active_reports || rev <= end_revision)
+ {
+ svn_ra_serf__list_t *done_list;
+ svn_ra_serf__list_t *done_reports = NULL;
+ replay_context_t *replay_ctx;
+
+ if (session->cancel_func)
+ SVN_ERR(session->cancel_func(session->cancel_baton));
+
+ /* Send pending requests, if any. Limit the number of outstanding
+ requests to MAX_OUTSTANDING_REQUESTS. */
+ if (rev <= end_revision && active_reports < MAX_OUTSTANDING_REQUESTS)
+ {
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ apr_pool_t *ctx_pool = svn_pool_create(pool);
+ const char *replay_target;
+
+ replay_ctx = apr_pcalloc(ctx_pool, sizeof(*replay_ctx));
+ replay_ctx->src_rev_pool = ctx_pool;
+ replay_ctx->revstart_func = revstart_func;
+ replay_ctx->revfinish_func = revfinish_func;
+ replay_ctx->replay_baton = replay_baton;
+ replay_ctx->done = FALSE;
+ replay_ctx->include_path = include_path;
+ replay_ctx->revision = rev;
+ replay_ctx->low_water_mark = low_water_mark;
+ replay_ctx->send_deltas = send_deltas;
+ replay_ctx->done_item.data = replay_ctx;
+
+ /* Request all properties of a certain revision. */
+ replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ replay_ctx->revprop_target = apr_psprintf(pool, "%s/%ld",
+ session->rev_stub, rev);
+ replay_ctx->revprop_rev = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ replay_ctx->revprop_target = report_target;
+ replay_ctx->revprop_rev = rev;
+ }
+
+ SVN_ERR(svn_ra_serf__deliver_props(&replay_ctx->propfind_handler,
+ replay_ctx->revs_props, session,
+ session->conns[0],
+ replay_ctx->revprop_target,
+ replay_ctx->revprop_rev,
+ "0", all_props,
+ NULL,
+ replay_ctx->src_rev_pool));
+
+ /* Spin up the serf request for the PROPFIND. */
+ svn_ra_serf__request_create(replay_ctx->propfind_handler);
+
+ /* Send the replay REPORT request. */
+ if (session->supports_rev_rsrc_replay)
+ {
+ replay_target = apr_psprintf(pool, "%s/%ld",
+ session->rev_stub, rev);
+ }
+ else
+ {
+ replay_target = session->session_url.path;
+ }
+
+ handler = apr_pcalloc(replay_ctx->src_rev_pool, sizeof(*handler));
+
+ handler->handler_pool = replay_ctx->src_rev_pool;
+ handler->method = "REPORT";
+ handler->path = replay_target;
+ handler->body_delegate = create_replay_body;
+ handler->body_delegate_baton = replay_ctx;
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ parser_ctx = apr_pcalloc(replay_ctx->src_rev_pool,
+ sizeof(*parser_ctx));
+
+ /* Setup the XML parser context.
+ Because we have not one but a list of requests, the 'done' property
+ on the replay_ctx is not of much use. Instead, use 'done_list'.
+ On each handled response (succesfully or not), the parser will add
+ done_item to done_list, so by keeping track of the state of
+ done_list we know how many requests have been handled completely.
+ */
+ parser_ctx->pool = replay_ctx->src_rev_pool;
+ parser_ctx->user_data = replay_ctx;
+ parser_ctx->start = start_replay;
+ parser_ctx->end = end_replay;
+ parser_ctx->cdata = cdata_replay;
+ parser_ctx->done = &replay_ctx->done;
+ parser_ctx->done_list = &done_reports;
+ parser_ctx->done_item = &replay_ctx->done_item;
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+ replay_ctx->report_handler = handler;
+
+ /* This is only needed to handle errors during XML parsing. */
+ replay_ctx->parser_ctx = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ rev++;
+ active_reports++;
+ }
+
+ /* Run the serf loop. */
+ SVN_ERR(svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool));
+
+ /* Substract the number of completely handled responses from our
+ total nr. of open requests', so we'll know when to stop this loop.
+ Since the message is completely handled, we can destroy its pool. */
+ done_list = done_reports;
+ while (done_list)
+ {
+ replay_context_t *ctx = (replay_context_t *)done_list->data;
+ svn_ra_serf__handler_t *done_handler = ctx->report_handler;
+
+ done_list = done_list->next;
+ SVN_ERR(svn_ra_serf__error_on_status(done_handler->sline.code,
+ done_handler->path,
+ done_handler->location));
+ svn_pool_destroy(ctx->src_rev_pool);
+ active_reports--;
+ }
+
+ done_reports = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+#undef MAX_OUTSTANDING_REQUESTS
diff --git a/subversion/libsvn_ra_serf/sb_bucket.c b/subversion/libsvn_ra_serf/sb_bucket.c
new file mode 100644
index 0000000..df0541f
--- /dev/null
+++ b/subversion/libsvn_ra_serf/sb_bucket.c
@@ -0,0 +1,185 @@
+/*
+ * sb_bucket.c : a serf bucket that wraps a spillbuf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <serf.h>
+#include <serf_bucket_util.h>
+
+#include "svn_private_config.h"
+#include "private/svn_subr_private.h"
+
+#include "ra_serf.h"
+
+#define SB_BLOCKSIZE 1024
+#define SB_MAXSIZE 32768
+
+
+struct sbb_baton
+{
+ svn_spillbuf_t *spillbuf;
+
+ const char *holding;
+ apr_size_t hold_len;
+
+ apr_pool_t *scratch_pool;
+};
+
+
+svn_error_t *
+svn_ra_serf__copy_into_spillbuf(svn_spillbuf_t **spillbuf,
+ serf_bucket_t *bkt,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *spillbuf = svn_spillbuf__create(SB_BLOCKSIZE, SB_MAXSIZE, result_pool);
+
+ /* Copy all data from the bucket into the spillbuf. */
+ while (TRUE)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(bkt, SERF_READ_ALL_AVAIL, &data, &len);
+
+ if (status != APR_SUCCESS && status != APR_EOF)
+ return svn_ra_serf__wrap_err(status, _("Failed to read the request"));
+
+ SVN_ERR(svn_spillbuf__write(*spillbuf, data, len, scratch_pool));
+
+ if (status == APR_EOF)
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static apr_status_t
+sb_bucket_read(serf_bucket_t *bucket, apr_size_t requested,
+ const char **data, apr_size_t *len)
+{
+ struct sbb_baton *sbb = bucket->data;
+ svn_error_t *err;
+
+ if (sbb->holding)
+ {
+ *data = sbb->holding;
+
+ if (requested < sbb->hold_len)
+ {
+ *len = requested;
+ sbb->holding += requested;
+ sbb->hold_len -= requested;
+ return APR_SUCCESS;
+ }
+
+ /* Return whatever we're holding, and then forget (consume) it. */
+ *len = sbb->hold_len;
+ sbb->holding = NULL;
+ return APR_SUCCESS;
+ }
+
+ err = svn_spillbuf__read(data, len, sbb->spillbuf, sbb->scratch_pool);
+ svn_pool_clear(sbb->scratch_pool);
+
+ /* ### do something with this */
+ svn_error_clear(err);
+
+ /* The spillbuf may have returned more than requested. Stash any extra
+ into our holding area. */
+ if (requested < *len)
+ {
+ sbb->holding = *data + requested;
+ sbb->hold_len = *len - requested;
+ *len = requested;
+ }
+
+ return *data == NULL ? APR_EOF : APR_SUCCESS;
+}
+
+
+static apr_status_t
+sb_bucket_readline(serf_bucket_t *bucket, int acceptable,
+ int *found,
+ const char **data, apr_size_t *len)
+{
+ /* ### for now, we know callers won't use this function. */
+ svn_error_clear(svn_error__malfunction(TRUE, __FILE__, __LINE__,
+ "Not implemented."));
+ return APR_ENOTIMPL;
+}
+
+
+static apr_status_t
+sb_bucket_peek(serf_bucket_t *bucket,
+ const char **data, apr_size_t *len)
+{
+ struct sbb_baton *sbb = bucket->data;
+ svn_error_t *err;
+
+ /* If we're not holding any data, then fill it. */
+ if (sbb->holding == NULL)
+ {
+ err = svn_spillbuf__read(&sbb->holding, &sbb->hold_len, sbb->spillbuf,
+ sbb->scratch_pool);
+ svn_pool_clear(sbb->scratch_pool);
+
+ /* ### do something with this */
+ svn_error_clear(err);
+ }
+
+ /* Return the data we are (now) holding. */
+ *data = sbb->holding;
+ *len = sbb->hold_len;
+
+ return *data == NULL ? APR_EOF : APR_SUCCESS;
+}
+
+
+static const serf_bucket_type_t sb_bucket_vtable = {
+ "SPILLBUF",
+ sb_bucket_read,
+ sb_bucket_readline,
+ serf_default_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ sb_bucket_peek,
+ serf_default_destroy_and_data,
+};
+
+
+serf_bucket_t *
+svn_ra_serf__create_sb_bucket(svn_spillbuf_t *spillbuf,
+ serf_bucket_alloc_t *allocator,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct sbb_baton *sbb;
+
+ sbb = serf_bucket_mem_alloc(allocator, sizeof(*sbb));
+ sbb->spillbuf = spillbuf;
+ sbb->holding = NULL;
+ sbb->scratch_pool = svn_pool_create(result_pool);
+
+ return serf_bucket_create(&sb_bucket_vtable, allocator, sbb);
+}
diff --git a/subversion/libsvn_ra_serf/serf.c b/subversion/libsvn_ra_serf/serf.c
new file mode 100644
index 0000000..6016157
--- /dev/null
+++ b/subversion/libsvn_ra_serf/serf.c
@@ -0,0 +1,1246 @@
+/*
+ * serf.c : entry point for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_time.h"
+#include "svn_version.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+/* Implements svn_ra__vtable_t.get_version(). */
+static const svn_version_t *
+ra_serf_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+#define RA_SERF_DESCRIPTION \
+ N_("Module for accessing a repository via WebDAV protocol using serf.")
+
+/* Implements svn_ra__vtable_t.get_description(). */
+static const char *
+ra_serf_get_description(void)
+{
+ return _(RA_SERF_DESCRIPTION);
+}
+
+/* Implements svn_ra__vtable_t.get_schemes(). */
+static const char * const *
+ra_serf_get_schemes(apr_pool_t *pool)
+{
+ static const char *serf_ssl[] = { "http", "https", NULL };
+#if 0
+ /* ### Temporary: to shut up a warning. */
+ static const char *serf_no_ssl[] = { "http", NULL };
+#endif
+
+ /* TODO: Runtime detection. */
+ return serf_ssl;
+}
+
+/* Load the setting http-auth-types from the global or server specific
+ section, parse its value and set the types of authentication we should
+ accept from the server. */
+static svn_error_t *
+load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
+ const char *server_group,
+ int *authn_types)
+{
+ const char *http_auth_types = NULL;
+ *authn_types = SERF_AUTHN_NONE;
+
+ svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
+
+ if (server_group)
+ {
+ svn_config_get(config, &http_auth_types, server_group,
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
+ }
+
+ if (http_auth_types)
+ {
+ char *token;
+ char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
+ apr_collapse_spaces(auth_types_list, http_auth_types);
+ while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
+ {
+ if (svn_cstring_casecmp("basic", token) == 0)
+ *authn_types |= SERF_AUTHN_BASIC;
+ else if (svn_cstring_casecmp("digest", token) == 0)
+ *authn_types |= SERF_AUTHN_DIGEST;
+ else if (svn_cstring_casecmp("ntlm", token) == 0)
+ *authn_types |= SERF_AUTHN_NTLM;
+ else if (svn_cstring_casecmp("negotiate", token) == 0)
+ *authn_types |= SERF_AUTHN_NEGOTIATE;
+ else
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: unknown %s "
+ "'%s'"),
+ SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
+ }
+ }
+ else
+ {
+ /* Nothing specified by the user, so accept all types. */
+ *authn_types = SERF_AUTHN_ALL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
+ runtime configuration variable. */
+#define DEFAULT_HTTP_TIMEOUT 600
+
+static svn_error_t *
+load_config(svn_ra_serf__session_t *session,
+ apr_hash_t *config_hash,
+ apr_pool_t *pool)
+{
+ svn_config_t *config, *config_client;
+ const char *server_group;
+ const char *proxy_host = NULL;
+ const char *port_str = NULL;
+ const char *timeout_str = NULL;
+ const char *exceptions;
+ apr_port_t proxy_port;
+
+ if (config_hash)
+ {
+ config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
+ config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
+ }
+ else
+ {
+ config = NULL;
+ config_client = NULL;
+ }
+
+ SVN_ERR(svn_config_get_bool(config, &session->using_compression,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
+ svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
+
+ if (session->wc_callbacks->auth_baton)
+ {
+ if (config_client)
+ {
+ svn_auth_set_parameter(session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
+ config_client);
+ }
+ if (config)
+ {
+ svn_auth_set_parameter(session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS,
+ config);
+ }
+ }
+
+ /* Use the default proxy-specific settings if and only if
+ "http-proxy-exceptions" is not set to exclude this host. */
+ svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
+ if (! svn_cstring_match_glob_list(session->session_url.hostname,
+ svn_cstring_split(exceptions, ",",
+ TRUE, pool)))
+ {
+ svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
+ svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
+ svn_config_get(config, &session->proxy_username,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
+ svn_config_get(config, &session->proxy_password,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
+ }
+
+ /* Load the global ssl settings, if set. */
+ SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
+ TRUE));
+ svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
+
+ /* If set, read the flag that tells us to do bulk updates or not. Defaults
+ to skelta updates. */
+ SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
+ "auto",
+ svn_tristate_unknown));
+
+ /* Load the maximum number of parallel session connections. */
+ SVN_ERR(svn_config_get_int64(config, &session->max_connections,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
+ SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
+
+ if (config)
+ server_group = svn_config_find_group(config,
+ session->session_url.hostname,
+ SVN_CONFIG_SECTION_GROUPS, pool);
+ else
+ server_group = NULL;
+
+ if (server_group)
+ {
+ SVN_ERR(svn_config_get_bool(config, &session->using_compression,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_COMPRESSION,
+ session->using_compression));
+ svn_config_get(config, &timeout_str, server_group,
+ SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
+
+ svn_auth_set_parameter(session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SERVER_GROUP, server_group);
+
+ /* Load the group proxy server settings, overriding global
+ settings. We intentionally ignore 'http-proxy-exceptions'
+ here because, well, if this site was an exception, why is
+ there a per-server proxy configuration for it? */
+ svn_config_get(config, &proxy_host, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
+ svn_config_get(config, &port_str, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
+ svn_config_get(config, &session->proxy_username, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
+ session->proxy_username);
+ svn_config_get(config, &session->proxy_password, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
+ session->proxy_password);
+
+ /* Load the group ssl settings. */
+ SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
+ server_group,
+ SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
+ session->trust_default_ca));
+ svn_config_get(config, &session->ssl_authorities, server_group,
+ SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
+ session->ssl_authorities);
+
+ /* Load the group bulk updates flag. */
+ SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
+ "auto",
+ session->bulk_updates));
+
+ /* Load the maximum number of parallel session connections,
+ overriding global values. */
+ SVN_ERR(svn_config_get_int64(config, &session->max_connections,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
+ session->max_connections));
+ }
+
+ /* Don't allow the http-max-connections value to be larger than our
+ compiled-in limit, or to be too small to operate. Broken
+ functionality and angry administrators are equally undesirable. */
+ if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
+ session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
+ if (session->max_connections < 2)
+ session->max_connections = 2;
+
+ /* Parse the connection timeout value, if any. */
+ session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
+ if (timeout_str)
+ {
+ char *endstr;
+ const long int timeout = strtol(timeout_str, &endstr, 10);
+
+ if (*endstr)
+ return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: illegal character in "
+ "timeout value"));
+ if (timeout < 0)
+ return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: negative timeout value"));
+ session->timeout = apr_time_from_sec(timeout);
+ }
+ SVN_ERR_ASSERT(session->timeout >= 0);
+
+ /* Convert the proxy port value, if any. */
+ if (port_str)
+ {
+ char *endstr;
+ const long int port = strtol(port_str, &endstr, 10);
+
+ if (*endstr)
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Invalid URL: illegal character in proxy "
+ "port number"));
+ if (port < 0)
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Invalid URL: negative proxy port number"));
+ if (port > 65535)
+ return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Invalid URL: proxy port number greater "
+ "than maximum TCP port number 65535"));
+ proxy_port = (apr_port_t) port;
+ }
+ else
+ {
+ proxy_port = 80;
+ }
+
+ if (proxy_host)
+ {
+ apr_sockaddr_t *proxy_addr;
+ apr_status_t status;
+
+ status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
+ APR_UNSPEC, proxy_port, 0,
+ session->pool);
+ if (status)
+ {
+ return svn_ra_serf__wrap_err(
+ status, _("Could not resolve proxy server '%s'"),
+ proxy_host);
+ }
+ session->using_proxy = TRUE;
+ serf_config_proxy(session->context, proxy_addr);
+ }
+ else
+ {
+ session->using_proxy = FALSE;
+ }
+
+ /* Setup authentication. */
+ SVN_ERR(load_http_auth_types(pool, config, server_group,
+ &session->authn_types));
+ serf_config_authn_types(session->context, session->authn_types);
+ serf_config_credentials_callback(session->context,
+ svn_ra_serf__credentials_callback);
+
+ return SVN_NO_ERROR;
+}
+#undef DEFAULT_HTTP_TIMEOUT
+
+static void
+svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
+{
+ const svn_ra_serf__session_t *serf_sess = progress_baton;
+ if (serf_sess->progress_func)
+ {
+ serf_sess->progress_func(read + written, -1,
+ serf_sess->progress_baton,
+ serf_sess->pool);
+ }
+}
+
+/* Implements svn_ra__vtable_t.open_session(). */
+static svn_error_t *
+svn_ra_serf__open(svn_ra_session_t *session,
+ const char **corrected_url,
+ const char *session_URL,
+ const svn_ra_callbacks2_t *callbacks,
+ void *callback_baton,
+ apr_hash_t *config,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ svn_ra_serf__session_t *serf_sess;
+ apr_uri_t url;
+ const char *client_string = NULL;
+
+ if (corrected_url)
+ *corrected_url = NULL;
+
+ serf_sess = apr_pcalloc(pool, sizeof(*serf_sess));
+ serf_sess->pool = svn_pool_create(pool);
+ serf_sess->wc_callbacks = callbacks;
+ serf_sess->wc_callback_baton = callback_baton;
+ serf_sess->progress_func = callbacks->progress_func;
+ serf_sess->progress_baton = callbacks->progress_baton;
+ serf_sess->cancel_func = callbacks->cancel_func;
+ serf_sess->cancel_baton = callback_baton;
+
+ /* todo: reuse serf context across sessions */
+ serf_sess->context = serf_context_create(serf_sess->pool);
+
+ SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
+ serf_sess->pool));
+
+
+ status = apr_uri_parse(serf_sess->pool, session_URL, &url);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal URL '%s'"),
+ session_URL);
+ }
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
+ if (url.path == NULL || url.path[0] == '\0')
+ {
+ url.path = apr_pstrdup(serf_sess->pool, "/");
+ }
+ if (!url.port)
+ {
+ url.port = apr_uri_port_of_scheme(url.scheme);
+ }
+ serf_sess->session_url = url;
+ serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
+ serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
+
+ serf_sess->supports_deadprop_count = svn_tristate_unknown;
+
+ serf_sess->capabilities = apr_hash_make(serf_sess->pool);
+
+ /* We have to assume that the server only supports HTTP/1.0. Once it's clear
+ HTTP/1.1 is supported, we can upgrade. */
+ serf_sess->http10 = TRUE;
+
+ SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
+
+ serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
+ sizeof(*serf_sess->conns[0]));
+ serf_sess->conns[0]->bkt_alloc =
+ serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
+ serf_sess->conns[0]->session = serf_sess;
+ serf_sess->conns[0]->last_status_code = -1;
+
+ /* create the user agent string */
+ if (callbacks->get_client_string)
+ SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool));
+
+ if (client_string)
+ serf_sess->useragent = apr_pstrcat(pool, USER_AGENT, " ",
+ client_string, (char *)NULL);
+ else
+ serf_sess->useragent = USER_AGENT;
+
+ /* go ahead and tell serf about the connection. */
+ status =
+ serf_connection_create2(&serf_sess->conns[0]->conn,
+ serf_sess->context,
+ url,
+ svn_ra_serf__conn_setup, serf_sess->conns[0],
+ svn_ra_serf__conn_closed, serf_sess->conns[0],
+ serf_sess->pool);
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+
+ /* Set the progress callback. */
+ serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
+ serf_sess);
+
+ serf_sess->num_conns = 1;
+
+ session->priv = serf_sess;
+
+ return svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
+}
+
+/* Implements svn_ra__vtable_t.reparent(). */
+static svn_error_t *
+svn_ra_serf__reparent(svn_ra_session_t *ra_session,
+ const char *url,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_uri_t new_url;
+ apr_status_t status;
+
+ /* If it's the URL we already have, wave our hands and do nothing. */
+ if (strcmp(session->session_url_str, url) == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (!session->repos_root_str)
+ {
+ const char *vcc_url;
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
+ }
+
+ if (!svn_uri__is_ancestor(session->repos_root_str, url))
+ {
+ return svn_error_createf(
+ SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("URL '%s' is not a child of the session's repository root "
+ "URL '%s'"), url, session->repos_root_str);
+ }
+
+ status = apr_uri_parse(pool, url, &new_url);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal repository URL '%s'"), url);
+ }
+
+ /* Depending the version of apr-util in use, for root paths url.path
+ will be NULL or "", where serf requires "/". */
+ /* ### Maybe we should use a string buffer for these strings so we
+ ### don't allocate memory in the session on every reparent? */
+ if (new_url.path == NULL || new_url.path[0] == '\0')
+ {
+ session->session_url.path = apr_pstrdup(session->pool, "/");
+ }
+ else
+ {
+ session->session_url.path = apr_pstrdup(session->pool, new_url.path);
+ }
+ session->session_url_str = apr_pstrdup(session->pool, url);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.get_session_url(). */
+static svn_error_t *
+svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ *url = apr_pstrdup(pool, session->session_url_str);
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.get_latest_revnum(). */
+static svn_error_t *
+svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ return svn_error_trace(svn_ra_serf__get_youngest_revnum(
+ latest_revnum, session, pool));
+}
+
+/* Implements svn_ra__vtable_t.rev_proplist(). */
+static svn_error_t *
+svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
+ svn_revnum_t rev,
+ apr_hash_t **ret_props,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_t *props;
+ const char *propfind_path;
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ {
+ propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
+
+ /* svn_ra_serf__retrieve_props() wants to added the revision as
+ a Label to the PROPFIND, which isn't really necessary when
+ querying a rev-stub URI. *Shrug* Probably okay to leave the
+ Label, but whatever. */
+ rev = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ /* Use the VCC as the propfind target path. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool));
+ }
+
+ /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
+ propfind_path, rev, "0", all_props,
+ pool, pool));
+
+ SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props,
+ pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.rev_prop(). */
+static svn_error_t *
+svn_ra_serf__rev_prop(svn_ra_session_t *session,
+ svn_revnum_t rev,
+ const char *name,
+ svn_string_t **value,
+ apr_pool_t *pool)
+{
+ apr_hash_t *props;
+
+ SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
+
+ *value = svn_hash_gets(props, name);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_path_props(apr_hash_t **props,
+ svn_ra_serf__session_t *session,
+ const char *session_relpath,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *desired_props,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *url;
+
+ url = session->session_url.path;
+
+ /* If we have a relative path, append it. */
+ if (session_relpath)
+ url = svn_path_url_add_component2(url, session_relpath, scratch_pool);
+
+ /* If we were given a specific revision, get a URL that refers to that
+ specific revision (rather than floating with HEAD). */
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
+ session, NULL /* conn */,
+ url, revision,
+ scratch_pool, scratch_pool));
+ }
+
+ /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
+ Or we started with SVN_INVALID_REVNUM and URL may be floating. */
+ SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0],
+ url, SVN_INVALID_REVNUM,
+ desired_props,
+ result_pool, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.check_path(). */
+static svn_error_t *
+svn_ra_serf__check_path(svn_ra_session_t *ra_session,
+ const char *rel_path,
+ svn_revnum_t revision,
+ svn_node_kind_t *kind,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_t *props;
+
+ svn_error_t *err = fetch_path_props(&props, session, rel_path,
+ revision, check_path_props,
+ pool, pool);
+
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *kind = svn_node_none;
+ }
+ else
+ {
+ /* Any other error, raise to caller. */
+ if (err)
+ return svn_error_trace(err);
+
+ SVN_ERR(svn_ra_serf__get_resource_type(kind, props));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct dirent_walker_baton_t {
+ /* Update the fields in this entry. */
+ svn_dirent_t *entry;
+
+ svn_tristate_t *supports_deadprop_count;
+
+ /* If allocations are necessary, then use this pool. */
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+dirent_walker(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ struct dirent_walker_baton_t *dwb = baton;
+
+ if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
+ {
+ dwb->entry->has_props = TRUE;
+ }
+ else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
+ {
+ dwb->entry->has_props = TRUE;
+ }
+ else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
+ {
+ if(strcmp(name, "deadprop-count") == 0)
+ {
+ if (*val->data)
+ {
+ apr_int64_t deadprop_count;
+ SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
+ dwb->entry->has_props = deadprop_count > 0;
+ if (dwb->supports_deadprop_count)
+ *dwb->supports_deadprop_count = svn_tristate_true;
+ }
+ else if (dwb->supports_deadprop_count)
+ *dwb->supports_deadprop_count = svn_tristate_false;
+ }
+ }
+ else if (strcmp(ns, "DAV:") == 0)
+ {
+ if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
+ {
+ dwb->entry->created_rev = SVN_STR_TO_REV(val->data);
+ }
+ else if (strcmp(name, "creator-displayname") == 0)
+ {
+ dwb->entry->last_author = val->data;
+ }
+ else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
+ {
+ SVN_ERR(svn_time_from_cstring(&dwb->entry->time,
+ val->data,
+ dwb->result_pool));
+ }
+ else if (strcmp(name, "getcontentlength") == 0)
+ {
+ /* 'getcontentlength' property is empty for directories. */
+ if (val->len)
+ {
+ SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data));
+ }
+ }
+ else if (strcmp(name, "resourcetype") == 0)
+ {
+ if (strcmp(val->data, "collection") == 0)
+ {
+ dwb->entry->kind = svn_node_dir;
+ }
+ else
+ {
+ dwb->entry->kind = svn_node_file;
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct path_dirent_visitor_t {
+ apr_hash_t *full_paths;
+ apr_hash_t *base_paths;
+ const char *orig_path;
+ svn_tristate_t supports_deadprop_count;
+ apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+path_dirent_walker(void *baton,
+ const char *path, apr_ssize_t path_len,
+ const char *ns, apr_ssize_t ns_len,
+ const char *name, apr_ssize_t name_len,
+ const svn_string_t *val,
+ apr_pool_t *pool)
+{
+ struct path_dirent_visitor_t *dirents = baton;
+ struct dirent_walker_baton_t dwb;
+ svn_dirent_t *entry;
+
+ /* Skip our original path. */
+ if (strcmp(path, dirents->orig_path) == 0)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ entry = apr_hash_get(dirents->full_paths, path, path_len);
+
+ if (!entry)
+ {
+ const char *base_name;
+
+ entry = svn_dirent_create(pool);
+
+ apr_hash_set(dirents->full_paths, path, path_len, entry);
+
+ base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool),
+ pool);
+
+ svn_hash_sets(dirents->base_paths, base_name, entry);
+ }
+
+ dwb.entry = entry;
+ dwb.supports_deadprop_count = &dirents->supports_deadprop_count;
+ dwb.result_pool = dirents->result_pool;
+ return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool));
+}
+
+static const svn_ra_serf__dav_props_t *
+get_dirent_props(apr_uint32_t dirent_fields,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__dav_props_t *prop;
+ apr_array_header_t *props = apr_array_make
+ (pool, 7, sizeof(svn_ra_serf__dav_props_t));
+
+ if (session->supports_deadprop_count != svn_tristate_false
+ || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
+ {
+ if (dirent_fields & SVN_DIRENT_KIND)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "resourcetype";
+ }
+
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "getcontentlength";
+ }
+
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = SVN_DAV_PROP_NS_DAV;
+ prop->name = "deadprop-count";
+ }
+
+ if (dirent_fields & SVN_DIRENT_CREATED_REV)
+ {
+ svn_ra_serf__dav_props_t *p = apr_array_push(props);
+ p->namespace = "DAV:";
+ p->name = SVN_DAV__VERSION_NAME;
+ }
+
+ if (dirent_fields & SVN_DIRENT_TIME)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = SVN_DAV__CREATIONDATE;
+ }
+
+ if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ {
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "creator-displayname";
+ }
+ }
+ else
+ {
+ /* We found an old subversion server that can't handle
+ the deadprop-count property in the way we expect.
+
+ The neon behavior is to retrieve all properties in this case */
+ prop = apr_array_push(props);
+ prop->namespace = "DAV:";
+ prop->name = "allprop";
+ }
+
+ prop = apr_array_push(props);
+ prop->namespace = NULL;
+ prop->name = NULL;
+
+ return (svn_ra_serf__dav_props_t *) props->elts;
+}
+
+/* Implements svn_ra__vtable_t.stat(). */
+static svn_error_t *
+svn_ra_serf__stat(svn_ra_session_t *ra_session,
+ const char *rel_path,
+ svn_revnum_t revision,
+ svn_dirent_t **dirent,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_t *props;
+ svn_error_t *err;
+ struct dirent_walker_baton_t dwb;
+ svn_tristate_t deadprop_count = svn_tristate_unknown;
+
+ err = fetch_path_props(&props,
+ session, rel_path, revision,
+ get_dirent_props(SVN_DIRENT_ALL, session, pool),
+ pool, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *dirent = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ dwb.entry = svn_dirent_create(pool);
+ dwb.supports_deadprop_count = &deadprop_count;
+ dwb.result_pool = pool;
+ SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
+
+ if (deadprop_count == svn_tristate_false
+ && session->supports_deadprop_count == svn_tristate_unknown
+ && !dwb.entry->has_props)
+ {
+ /* We have to requery as the server didn't give us the right
+ information */
+ session->supports_deadprop_count = svn_tristate_false;
+
+ SVN_ERR(fetch_path_props(&props,
+ session, rel_path, SVN_INVALID_REVNUM,
+ get_dirent_props(SVN_DIRENT_ALL, session, pool),
+ pool, pool));
+
+ SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
+ }
+
+ if (deadprop_count != svn_tristate_unknown)
+ session->supports_deadprop_count = deadprop_count;
+
+ *dirent = dwb.entry;
+
+ return SVN_NO_ERROR;
+}
+
+/* Reads the 'resourcetype' property from the list PROPS and checks if the
+ * resource at PATH@REVISION really is a directory. Returns
+ * SVN_ERR_FS_NOT_DIRECTORY if not.
+ */
+static svn_error_t *
+resource_is_directory(apr_hash_t *props)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_ra_serf__get_resource_type(&kind, props));
+
+ if (kind != svn_node_dir)
+ {
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra__vtable_t.get_dir(). */
+static svn_error_t *
+svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
+ apr_hash_t **dirents,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **ret_props,
+ const char *rel_path,
+ svn_revnum_t revision,
+ apr_uint32_t dirent_fields,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ const char *path;
+
+ path = session->session_url.path;
+
+ /* If we have a relative path, URI encode and append it. */
+ if (rel_path)
+ {
+ path = svn_path_url_add_component2(path, rel_path, pool);
+ }
+
+ /* If the user specified a peg revision other than HEAD, we have to fetch
+ the baseline collection url for that revision. If not, we can use the
+ public url. */
+ if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
+ {
+ SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
+ session, NULL /* conn */,
+ path, revision,
+ pool, pool));
+ revision = SVN_INVALID_REVNUM;
+ }
+ /* REVISION is always SVN_INVALID_REVNUM */
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
+
+ /* If we're asked for children, fetch them now. */
+ if (dirents)
+ {
+ struct path_dirent_visitor_t dirent_walk;
+ apr_hash_t *props;
+ const char *rtype;
+
+ /* Always request node kind to check that path is really a
+ * directory.
+ */
+ dirent_fields |= SVN_DIRENT_KIND;
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
+ path, SVN_INVALID_REVNUM, "1",
+ get_dirent_props(dirent_fields,
+ session, pool),
+ pool, pool));
+
+ /* Check if the path is really a directory. */
+ rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype");
+ if (rtype == NULL || strcmp(rtype, "collection") != 0)
+ return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Can't get entries of non-directory"));
+
+ /* We're going to create two hashes to help the walker along.
+ * We're going to return the 2nd one back to the caller as it
+ * will have the basenames it expects.
+ */
+ dirent_walk.full_paths = apr_hash_make(pool);
+ dirent_walk.base_paths = apr_hash_make(pool);
+ dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool);
+ dirent_walk.supports_deadprop_count = svn_tristate_unknown;
+ dirent_walk.result_pool = pool;
+
+ SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
+ path_dirent_walker, &dirent_walk,
+ pool));
+
+ if (dirent_walk.supports_deadprop_count == svn_tristate_false
+ && session->supports_deadprop_count == svn_tristate_unknown
+ && dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ /* We have to requery as the server didn't give us the right
+ information */
+ session->supports_deadprop_count = svn_tristate_false;
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session,
+ session->conns[0],
+ path, SVN_INVALID_REVNUM, "1",
+ get_dirent_props(dirent_fields,
+ session, pool),
+ pool, pool));
+
+ apr_hash_clear(dirent_walk.full_paths);
+ apr_hash_clear(dirent_walk.base_paths);
+
+ SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
+ path_dirent_walker,
+ &dirent_walk, pool));
+ }
+
+ *dirents = dirent_walk.base_paths;
+
+ if (dirent_walk.supports_deadprop_count != svn_tristate_unknown)
+ session->supports_deadprop_count = dirent_walk.supports_deadprop_count;
+ }
+
+ /* If we're asked for the directory properties, fetch them too. */
+ if (ret_props)
+ {
+ apr_hash_t *props;
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0],
+ path, SVN_INVALID_REVNUM,
+ all_props,
+ pool, pool));
+
+ /* Check if the path is really a directory. */
+ SVN_ERR(resource_is_directory(props));
+
+ /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
+ ### put them into POOL, so we're okay. */
+ SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
+ const char **url,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ if (!session->repos_root_str)
+ {
+ const char *vcc_url;
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
+ }
+
+ *url = session->repos_root_str;
+ return SVN_NO_ERROR;
+}
+
+/* TODO: to fetch the uuid from the repository, we need:
+ 1. a path that exists in HEAD
+ 2. a path that's readable
+
+ get_uuid handles the case where a path doesn't exist in HEAD and also the
+ case where the root of the repository is not readable.
+ However, it does not handle the case where we're fetching path not existing
+ in HEAD of a repository with unreadable root directory.
+
+ Implements svn_ra__vtable_t.get_uuid().
+ */
+static svn_error_t *
+svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
+ const char **uuid,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ if (!session->uuid)
+ {
+ const char *vcc_url;
+
+ /* We should never get here if we have HTTP v2 support, because
+ any server with that support should be transmitting the
+ UUID in the initial OPTIONS response. */
+ SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ /* We're not interested in vcc_url and relative_url, but this call also
+ stores the repository's uuid in the session. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
+ if (!session->uuid)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
+ _("The UUID property was not found on the "
+ "resource or any of its parents"));
+ }
+ }
+
+ *uuid = session->uuid;
+
+ return SVN_NO_ERROR;
+}
+
+
+static const svn_ra__vtable_t serf_vtable = {
+ ra_serf_version,
+ ra_serf_get_description,
+ ra_serf_get_schemes,
+ svn_ra_serf__open,
+ svn_ra_serf__reparent,
+ svn_ra_serf__get_session_url,
+ svn_ra_serf__get_latest_revnum,
+ svn_ra_serf__get_dated_revision,
+ svn_ra_serf__change_rev_prop,
+ svn_ra_serf__rev_proplist,
+ svn_ra_serf__rev_prop,
+ svn_ra_serf__get_commit_editor,
+ svn_ra_serf__get_file,
+ svn_ra_serf__get_dir,
+ svn_ra_serf__get_mergeinfo,
+ svn_ra_serf__do_update,
+ svn_ra_serf__do_switch,
+ svn_ra_serf__do_status,
+ svn_ra_serf__do_diff,
+ svn_ra_serf__get_log,
+ svn_ra_serf__check_path,
+ svn_ra_serf__stat,
+ svn_ra_serf__get_uuid,
+ svn_ra_serf__get_repos_root,
+ svn_ra_serf__get_locations,
+ svn_ra_serf__get_location_segments,
+ svn_ra_serf__get_file_revs,
+ svn_ra_serf__lock,
+ svn_ra_serf__unlock,
+ svn_ra_serf__get_lock,
+ svn_ra_serf__get_locks,
+ svn_ra_serf__replay,
+ svn_ra_serf__has_capability,
+ svn_ra_serf__replay_range,
+ svn_ra_serf__get_deleted_rev,
+ svn_ra_serf__register_editor_shim_callbacks,
+ svn_ra_serf__get_inherited_props
+};
+
+svn_error_t *
+svn_ra_serf__init(const svn_version_t *loader_version,
+ const svn_ra__vtable_t **vtable,
+ apr_pool_t *pool)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+ int serf_major;
+ int serf_minor;
+ int serf_patch;
+
+ SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist));
+
+ /* Simplified version check to make sure we can safely use the
+ VTABLE parameter. The RA loader does a more exhaustive check. */
+ if (loader_version->major != SVN_VER_MAJOR)
+ {
+ return svn_error_createf(
+ SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Unsupported RA loader version (%d) for ra_serf"),
+ loader_version->major);
+ }
+
+ /* Make sure that we have loaded a compatible library: the MAJOR must
+ match, and the minor must be at *least* what we compiled against.
+ The patch level is simply ignored. */
+ serf_lib_version(&serf_major, &serf_minor, &serf_patch);
+ if (serf_major != SERF_MAJOR_VERSION
+ || serf_minor < SERF_MINOR_VERSION)
+ {
+ return svn_error_createf(
+ /* ### should return a unique error */
+ SVN_ERR_VERSION_MISMATCH, NULL,
+ _("ra_serf was compiled for serf %d.%d.%d but loaded "
+ "an incompatible %d.%d.%d library"),
+ SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
+ serf_major, serf_minor, serf_patch);
+ }
+
+ *vtable = &serf_vtable;
+
+ return SVN_NO_ERROR;
+}
+
+/* Compatibility wrapper for pre-1.2 subversions. Needed? */
+#define NAME "ra_serf"
+#define DESCRIPTION RA_SERF_DESCRIPTION
+#define VTBL serf_vtable
+#define INITFUNC svn_ra_serf__init
+#define COMPAT_INITFUNC svn_ra_serf_init
+#include "../libsvn_ra/wrapper_template.h"
diff --git a/subversion/libsvn_ra_serf/update.c b/subversion/libsvn_ra_serf/update.c
new file mode 100644
index 0000000..21ed2df
--- /dev/null
+++ b/subversion/libsvn_ra_serf/update.c
@@ -0,0 +1,3639 @@
+/*
+ * update.c : entry point for update RA functions for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#include <apr_version.h>
+#include <apr_want.h>
+
+#include <apr_uri.h>
+
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "svn_delta.h"
+#include "svn_path.h"
+#include "svn_base64.h"
+#include "svn_props.h"
+
+#include "svn_private_config.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_string_private.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ *
+ * A little explanation of how the parsing works. Every time we see
+ * an open-directory tag, we enter the OPEN_DIR state. Likewise, for
+ * add-directory, open-file, etc. When we see the closing variant of the
+ * open-directory tag, we'll 'pop' out of that state.
+ *
+ * Each state has a pool associated with it that can have temporary
+ * allocations that will live as long as the tag is opened. Once
+ * the tag is 'closed', the pool will be reused.
+ */
+typedef enum report_state_e {
+ NONE = 0,
+ INITIAL = 0,
+ UPDATE_REPORT,
+ TARGET_REVISION,
+ OPEN_DIR,
+ ADD_DIR,
+ ABSENT_DIR,
+ OPEN_FILE,
+ ADD_FILE,
+ ABSENT_FILE,
+ PROP,
+ IGNORE_PROP_NAME,
+ NEED_PROP_NAME,
+ TXDELTA
+} report_state_e;
+
+
+/* While we process the REPORT response, we will queue up GET and PROPFIND
+ requests. For a very large checkout, it is very easy to queue requests
+ faster than they are resolved. Thus, we need to pause the XML processing
+ (which queues more requests) to avoid queueing too many, with their
+ attendant memory costs. When the queue count drops low enough, we will
+ resume XML processing.
+
+ Note that we don't want the count to drop to zero. We have multiple
+ connections that we want to keep busy. These are also heuristic numbers
+ since network and parsing behavior (ie. it doesn't pause immediately)
+ can make the measurements quite imprecise.
+
+ We measure outstanding requests as the sum of NUM_ACTIVE_FETCHES and
+ NUM_ACTIVE_PROPFINDS in the report_context_t structure. */
+#define REQUEST_COUNT_TO_PAUSE 50
+#define REQUEST_COUNT_TO_RESUME 40
+
+
+/* Forward-declare our report context. */
+typedef struct report_context_t report_context_t;
+
+/*
+ * This structure represents the information for a directory.
+ */
+typedef struct report_dir_t
+{
+ /* Our parent directory.
+ *
+ * This value is NULL when we are the root.
+ */
+ struct report_dir_t *parent_dir;
+
+ apr_pool_t *pool;
+
+ /* Pointer back to our original report context. */
+ report_context_t *report_context;
+
+ /* Our name sans any parents. */
+ const char *base_name;
+
+ /* the expanded directory name (including all parent names) */
+ const char *name;
+
+ /* the canonical url for this directory after updating. (received) */
+ const char *url;
+
+ /* The original repos_relpath of this url (from the working copy)
+ or NULL if the repos_relpath can be calculated from the edit root. */
+ const char *repos_relpath;
+
+ /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */
+ svn_revnum_t base_rev;
+
+ /* controlling dir baton - this is only created in ensure_dir_opened() */
+ void *dir_baton;
+ apr_pool_t *dir_baton_pool;
+
+ /* How many references to this directory do we still have open? */
+ apr_size_t ref_count;
+
+ /* Namespace list allocated out of this ->pool. */
+ svn_ra_serf__ns_t *ns_list;
+
+ /* hashtable for all of the properties (shared within a dir) */
+ apr_hash_t *props;
+
+ /* hashtable for all to-be-removed properties (shared within a dir) */
+ apr_hash_t *removed_props;
+
+ /* The propfind request for our current directory */
+ svn_ra_serf__handler_t *propfind_handler;
+
+ /* Has the server told us to fetch the dir props? */
+ svn_boolean_t fetch_props;
+
+ /* Have we closed the directory tag (meaning no more additions)? */
+ svn_boolean_t tag_closed;
+
+ /* The children of this directory */
+ struct report_dir_t *children;
+
+ /* The next sibling of this directory */
+ struct report_dir_t *sibling;
+} report_dir_t;
+
+/*
+ * This structure represents the information for a file.
+ *
+ * A directory may have a report_info_t associated with it as well.
+ *
+ * This structure is created as we parse the REPORT response and
+ * once the element is completed, we create a report_fetch_t structure
+ * to give to serf to retrieve this file.
+ */
+typedef struct report_info_t
+{
+ apr_pool_t *pool;
+
+ /* The enclosing directory.
+ *
+ * If this structure refers to a directory, the dir it points to will be
+ * itself.
+ */
+ report_dir_t *dir;
+
+ /* Our name sans any directory info. */
+ const char *base_name;
+
+ /* the expanded file name (including all parent directory names) */
+ const char *name;
+
+ /* the canonical url for this file. */
+ const char *url;
+
+ /* lock token, if we had one to start off with. */
+ const char *lock_token;
+
+ /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */
+ svn_revnum_t base_rev;
+
+ /* our delta base, if present (NULL if we're adding the file) */
+ const char *delta_base;
+
+ /* Path of original item if add with history */
+ const char *copyfrom_path;
+
+ /* Revision of original item if add with history */
+ svn_revnum_t copyfrom_rev;
+
+ /* The propfind request for our current file (if present) */
+ svn_ra_serf__handler_t *propfind_handler;
+
+ /* Has the server told us to fetch the file props? */
+ svn_boolean_t fetch_props;
+
+ /* Has the server told us to go fetch - only valid if we had it already */
+ svn_boolean_t fetch_file;
+
+ /* The properties for this file */
+ apr_hash_t *props;
+
+ /* pool passed to update->add_file, etc. */
+ apr_pool_t *editor_pool;
+
+ /* controlling file_baton and textdelta handler */
+ void *file_baton;
+ const char *base_checksum;
+ const char *final_sha1_checksum;
+ svn_txdelta_window_handler_t textdelta;
+ void *textdelta_baton;
+ svn_stream_t *svndiff_decoder;
+ svn_stream_t *base64_decoder;
+
+ /* Checksum for close_file */
+ const char *final_checksum;
+
+ /* Stream containing file contents already cached in the working
+ copy (which may be used to avoid a GET request for the same). */
+ svn_stream_t *cached_contents;
+
+ /* temporary property for this file which is currently being parsed
+ * It will eventually be stored in our parent directory's property hash.
+ */
+ const char *prop_ns;
+ const char *prop_name;
+ svn_stringbuf_t *prop_value;
+ const char *prop_encoding;
+} report_info_t;
+
+/*
+ * This structure represents a single request to GET (fetch) a file with
+ * its associated Serf session/connection.
+ */
+typedef struct report_fetch_t {
+
+ /* The handler representing this particular fetch. */
+ svn_ra_serf__handler_t *handler;
+
+ /* The session we should use to fetch the file. */
+ svn_ra_serf__session_t *sess;
+
+ /* The connection we should use to fetch file. */
+ svn_ra_serf__connection_t *conn;
+
+ /* Stores the information for the file we want to fetch. */
+ report_info_t *info;
+
+ /* Have we read our response headers yet? */
+ svn_boolean_t read_headers;
+
+ /* This flag is set when our response is aborted before we reach the
+ * end and we decide to requeue this request.
+ */
+ svn_boolean_t aborted_read;
+ apr_off_t aborted_read_size;
+
+ /* This is the amount of data that we have read so far. */
+ apr_off_t read_size;
+
+ /* If we're receiving an svndiff, this will be non-NULL. */
+ svn_stream_t *delta_stream;
+
+ /* If we're writing this file to a stream, this will be non-NULL. */
+ svn_stream_t *target_stream;
+
+ /* Are we done fetching this file? */
+ svn_boolean_t done;
+
+ /* Discard the rest of the content? */
+ svn_boolean_t discard;
+
+ svn_ra_serf__list_t **done_list;
+ svn_ra_serf__list_t done_item;
+
+} report_fetch_t;
+
+/*
+ * The master structure for a REPORT request and response.
+ */
+struct report_context_t {
+ apr_pool_t *pool;
+
+ svn_ra_serf__session_t *sess;
+ svn_ra_serf__connection_t *conn;
+
+ /* Source path and destination path */
+ const char *source;
+ const char *destination;
+
+ /* Our update target. */
+ const char *update_target;
+
+ /* What is the target revision that we want for this REPORT? */
+ svn_revnum_t target_rev;
+
+ /* Have we been asked to ignore ancestry or textdeltas? */
+ svn_boolean_t ignore_ancestry;
+ svn_boolean_t text_deltas;
+
+ /* Do we want the server to send copyfrom args or not? */
+ svn_boolean_t send_copyfrom_args;
+
+ /* Is the server sending everything in one response? */
+ svn_boolean_t send_all_mode;
+
+ /* Is the server including properties inline for newly added
+ files/dirs? */
+ svn_boolean_t add_props_included;
+
+ /* Path -> lock token mapping. */
+ apr_hash_t *lock_path_tokens;
+
+ /* Path -> const char *repos_relpath mapping */
+ apr_hash_t *switched_paths;
+
+ /* Boolean indicating whether "" is switched.
+ (This indicates that the we are updating a single file) */
+ svn_boolean_t root_is_switched;
+
+ /* Our master update editor and baton. */
+ const svn_delta_editor_t *update_editor;
+ void *update_baton;
+
+ /* The file holding request body for the REPORT.
+ *
+ * ### todo: It will be better for performance to store small
+ * request bodies (like 4k) in memory and bigger bodies on disk.
+ */
+ apr_file_t *body_file;
+
+ /* root directory object */
+ report_dir_t *root_dir;
+
+ /* number of pending GET requests */
+ unsigned int num_active_fetches;
+
+ /* completed fetches (contains report_fetch_t) */
+ svn_ra_serf__list_t *done_fetches;
+
+ /* number of pending PROPFIND requests */
+ unsigned int num_active_propfinds;
+
+ /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */
+ svn_ra_serf__list_t *done_propfinds;
+ svn_ra_serf__list_t *done_dir_propfinds;
+
+ /* list of outstanding prop changes (contains report_dir_t) */
+ svn_ra_serf__list_t *active_dir_propfinds;
+
+ /* list of files that only have prop changes (contains report_info_t) */
+ svn_ra_serf__list_t *file_propchanges_only;
+
+ /* The path to the REPORT request */
+ const char *path;
+
+ /* Are we done parsing the REPORT response? */
+ svn_boolean_t done;
+
+ /* Did we receive all data from the network? */
+ svn_boolean_t report_received;
+
+ /* Did we get a complete (non-truncated) report? */
+ svn_boolean_t report_completed;
+
+ /* The XML parser context for the REPORT response. */
+ svn_ra_serf__xml_parser_t *parser_ctx;
+
+ /* Did we close the root directory? */
+ svn_boolean_t closed_root;
+};
+
+
+#ifdef NOT_USED_YET
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t update_ttable[] = {
+ { INITIAL, S_, "update-report", UPDATE_REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION,
+ FALSE, { "rev", NULL }, TRUE },
+
+ { UPDATE_REPORT, S_, "open-directory", OPEN_DIR,
+ FALSE, { "rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "open-directory", OPEN_DIR,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "add-directory", ADD_DIR,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ADD_DIR, S_, "add-directory", ADD_DIR,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "open-file", OPEN_FILE,
+ FALSE, { "rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "add-file", ADD_FILE,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { ADD_DIR, S_, "add-file", ADD_FILE,
+ FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "delete-entry", OPEN_FILE,
+ FALSE, { "?rev", "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "absent-directory", ABSENT_DIR,
+ FALSE, { "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "absent-directory", ABSENT_DIR,
+ FALSE, { "name", NULL }, TRUE },
+
+ { OPEN_DIR, S_, "absent-file", ABSENT_FILE,
+ FALSE, { "name", NULL }, TRUE },
+
+ { ADD_DIR, S_, "absent-file", ABSENT_FILE,
+ FALSE, { "name", NULL }, TRUE },
+
+ { 0 }
+};
+
+
+
+/* Conforms to svn_ra_serf__xml_opened_t */
+static svn_error_t *
+update_opened(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int entered_state,
+ const svn_ra_serf__dav_props_t *tag,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = baton;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+update_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = baton;
+
+ if (leaving_state == TARGET_REVISION)
+ {
+ const char *rev = svn_hash_gets(attrs, "rev");
+
+ SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
+ SVN_STR_TO_REV(rev),
+ ctx->sess->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_ra_serf__xml_cdata_t */
+static svn_error_t *
+update_cdata(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int current_state,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = baton;
+
+ return SVN_NO_ERROR;
+}
+
+#endif /* NOT_USED_YET */
+
+
+/* Returns best connection for fetching files/properties. */
+static svn_ra_serf__connection_t *
+get_best_connection(report_context_t *ctx)
+{
+ svn_ra_serf__connection_t *conn;
+ int first_conn = 1;
+
+ /* Skip the first connection if the REPORT response hasn't been completely
+ received yet or if we're being told to limit our connections to
+ 2 (because this could be an attempt to ensure that we do all our
+ auxiliary GETs/PROPFINDs on a single connection).
+
+ ### FIXME: This latter requirement (max_connections > 2) is
+ ### really just a hack to work around the fact that some update
+ ### editor implementations (such as svnrdump's dump editor)
+ ### simply can't handle the way ra_serf violates the editor v1
+ ### drive ordering requirements.
+ ###
+ ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
+ */
+ if (ctx->report_received && (ctx->sess->max_connections > 2))
+ first_conn = 0;
+
+ /* Currently, we just cycle connections. In the future we could
+ store the number of pending requests on each connection, or
+ perform other heuristics, to achieve better connection usage.
+ (As an optimization, if there's only one available auxiliary
+ connection to use, don't bother doing all the cur_conn math --
+ just return that one connection.) */
+ if (ctx->sess->num_conns - first_conn == 1)
+ {
+ conn = ctx->sess->conns[first_conn];
+ }
+ else
+ {
+ conn = ctx->sess->conns[ctx->sess->cur_conn];
+ ctx->sess->cur_conn++;
+ if (ctx->sess->cur_conn >= ctx->sess->num_conns)
+ ctx->sess->cur_conn = first_conn;
+ }
+ return conn;
+}
+
+
+/** Report state management helper **/
+
+static report_info_t *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ report_context_t *ctx,
+ report_state_e state)
+{
+ report_info_t *info;
+ apr_pool_t *info_parent_pool;
+
+ svn_ra_serf__xml_push_state(parser, state);
+
+ info = parser->state->private;
+
+ /* Our private pool needs to be disjoint from the state pool. */
+ if (!info)
+ {
+ info_parent_pool = ctx->pool;
+ }
+ else
+ {
+ info_parent_pool = info->pool;
+ }
+
+ if (state == OPEN_DIR || state == ADD_DIR)
+ {
+ report_info_t *new_info;
+
+ new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
+ new_info->pool = svn_pool_create(info_parent_pool);
+ new_info->lock_token = NULL;
+ new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
+
+ new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir));
+ new_info->dir->pool = new_info->pool;
+
+ /* Create the root property tree. */
+ new_info->dir->props = apr_hash_make(new_info->pool);
+ new_info->props = new_info->dir->props;
+ new_info->dir->removed_props = apr_hash_make(new_info->pool);
+
+ new_info->dir->report_context = ctx;
+
+ if (info)
+ {
+ info->dir->ref_count++;
+
+ new_info->dir->parent_dir = info->dir;
+
+ /* Point our ns_list at our parents to try to reuse it. */
+ new_info->dir->ns_list = info->dir->ns_list;
+
+ /* Add ourselves to our parent's list */
+ new_info->dir->sibling = info->dir->children;
+ info->dir->children = new_info->dir;
+ }
+ else
+ {
+ /* Allow us to be found later. */
+ ctx->root_dir = new_info->dir;
+ }
+
+ parser->state->private = new_info;
+ }
+ else if (state == OPEN_FILE || state == ADD_FILE)
+ {
+ report_info_t *new_info;
+
+ new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info));
+ new_info->pool = svn_pool_create(info_parent_pool);
+ new_info->file_baton = NULL;
+ new_info->lock_token = NULL;
+ new_info->fetch_file = FALSE;
+ new_info->prop_value = svn_stringbuf_create_empty(new_info->pool);
+
+ /* Point at our parent's directory state. */
+ new_info->dir = info->dir;
+ info->dir->ref_count++;
+
+ new_info->props = apr_hash_make(new_info->pool);
+
+ parser->state->private = new_info;
+ }
+
+ return parser->state->private;
+}
+
+
+/** Wrappers around our various property walkers **/
+
+static svn_error_t *
+set_file_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ report_info_t *info = baton;
+ const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
+ const char *prop_name;
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
+ if (prop_name != NULL)
+ return svn_error_trace(editor->change_file_prop(info->file_baton,
+ prop_name,
+ val,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+set_dir_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ report_dir_t *dir = baton;
+ const svn_delta_editor_t *editor = dir->report_context->update_editor;
+ const char *prop_name;
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
+ if (prop_name != NULL)
+ return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
+ prop_name,
+ val,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+remove_file_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ report_info_t *info = baton;
+ const svn_delta_editor_t *editor = info->dir->report_context->update_editor;
+ const char *prop_name;
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
+ if (prop_name != NULL)
+ return svn_error_trace(editor->change_file_prop(info->file_baton,
+ prop_name,
+ NULL,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+remove_dir_props(void *baton,
+ const char *ns,
+ const char *name,
+ const svn_string_t *val,
+ apr_pool_t *scratch_pool)
+{
+ report_dir_t *dir = baton;
+ const svn_delta_editor_t *editor = dir->report_context->update_editor;
+ const char *prop_name;
+
+ prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool);
+ if (prop_name != NULL)
+ return svn_error_trace(editor->change_dir_prop(dir->dir_baton,
+ prop_name,
+ NULL,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+
+/** Helpers to open and close directories */
+
+static svn_error_t*
+ensure_dir_opened(report_dir_t *dir)
+{
+ report_context_t *ctx = dir->report_context;
+
+ /* if we're already open, return now */
+ if (dir->dir_baton)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (dir->base_name[0] == '\0')
+ {
+ dir->dir_baton_pool = svn_pool_create(dir->pool);
+
+ if (ctx->destination
+ && ctx->sess->wc_callbacks->invalidate_wc_props)
+ {
+ SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props(
+ ctx->sess->wc_callback_baton,
+ ctx->update_target,
+ SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
+ }
+
+ SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev,
+ dir->dir_baton_pool,
+ &dir->dir_baton));
+ }
+ else
+ {
+ SVN_ERR(ensure_dir_opened(dir->parent_dir));
+
+ dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool);
+
+ if (SVN_IS_VALID_REVNUM(dir->base_rev))
+ {
+ SVN_ERR(ctx->update_editor->open_directory(dir->name,
+ dir->parent_dir->dir_baton,
+ dir->base_rev,
+ dir->dir_baton_pool,
+ &dir->dir_baton));
+ }
+ else
+ {
+ SVN_ERR(ctx->update_editor->add_directory(dir->name,
+ dir->parent_dir->dir_baton,
+ NULL, SVN_INVALID_REVNUM,
+ dir->dir_baton_pool,
+ &dir->dir_baton));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+close_dir(report_dir_t *dir)
+{
+ report_dir_t *prev;
+ report_dir_t *sibling;
+
+ /* ### is there a better pool... this is tossed at end-of-func */
+ apr_pool_t *scratch_pool = dir->dir_baton_pool;
+
+ SVN_ERR_ASSERT(! dir->ref_count);
+
+ SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name,
+ dir->base_rev,
+ set_dir_props, dir,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name,
+ dir->base_rev, remove_dir_props, dir,
+ scratch_pool));
+
+ if (dir->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url,
+ dir->report_context->target_rev,
+ set_dir_props, dir,
+ scratch_pool));
+ }
+
+ SVN_ERR(dir->report_context->update_editor->close_directory(
+ dir->dir_baton, scratch_pool));
+
+ /* remove us from our parent's children list */
+ if (dir->parent_dir)
+ {
+ prev = NULL;
+ sibling = dir->parent_dir->children;
+
+ while (sibling != dir)
+ {
+ prev = sibling;
+ sibling = sibling->sibling;
+ if (!sibling)
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (!prev)
+ {
+ dir->parent_dir->children = dir->sibling;
+ }
+ else
+ {
+ prev->sibling = dir->sibling;
+ }
+ }
+
+ svn_pool_destroy(dir->dir_baton_pool);
+ svn_pool_destroy(dir->pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *close_all_dirs(report_dir_t *dir)
+{
+ while (dir->children)
+ {
+ SVN_ERR(close_all_dirs(dir->children));
+ dir->ref_count--;
+ }
+
+ SVN_ERR_ASSERT(! dir->ref_count);
+
+ SVN_ERR(ensure_dir_opened(dir));
+
+ return close_dir(dir);
+}
+
+
+/** Routines called when we are fetching a file */
+
+/* This function works around a bug in some older versions of
+ * mod_dav_svn in that it will not send remove-prop in the update
+ * report when a lock property disappears when send-all is false.
+ *
+ * Therefore, we'll try to look at our properties and see if there's
+ * an active lock. If not, then we'll assume there isn't a lock
+ * anymore.
+ */
+static void
+check_lock(report_info_t *info)
+{
+ const char *lock_val;
+
+ lock_val = svn_ra_serf__get_ver_prop(info->props, info->url,
+ info->dir->report_context->target_rev,
+ "DAV:", "lockdiscovery");
+
+ if (lock_val)
+ {
+ char *new_lock;
+ new_lock = apr_pstrdup(info->editor_pool, lock_val);
+ apr_collapse_spaces(new_lock, new_lock);
+ lock_val = new_lock;
+ }
+
+ if (!lock_val || lock_val[0] == '\0')
+ {
+ svn_string_t *str;
+
+ str = svn_string_ncreate("", 1, info->editor_pool);
+
+ svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name,
+ info->base_rev, "DAV:", "lock-token",
+ str, info->dir->pool);
+ }
+}
+
+static svn_error_t *
+headers_fetch(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ report_fetch_t *fetch_ctx = baton;
+
+ /* note that we have old VC URL */
+ if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) &&
+ fetch_ctx->info->delta_base)
+ {
+ serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
+ fetch_ctx->info->delta_base);
+ serf_bucket_headers_setn(headers, "Accept-Encoding",
+ "svndiff1;q=0.9,svndiff;q=0.8");
+ }
+ else if (fetch_ctx->sess->using_compression)
+ {
+ serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cancel_fetch(serf_request_t *request,
+ serf_bucket_t *response,
+ int status_code,
+ void *baton)
+{
+ report_fetch_t *fetch_ctx = baton;
+
+ /* Uh-oh. Our connection died on us.
+ *
+ * The core ra_serf layer will requeue our request - we just need to note
+ * that we got cut off in the middle of our song.
+ */
+ if (!response)
+ {
+ /* If we already started the fetch and opened the file handle, we need
+ * to hold subsequent read() ops until we get back to where we were
+ * before the close and we can then resume the textdelta() calls.
+ */
+ if (fetch_ctx->read_headers)
+ {
+ if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
+ {
+ fetch_ctx->aborted_read = TRUE;
+ fetch_ctx->aborted_read_size = fetch_ctx->read_size;
+ }
+ fetch_ctx->read_size = 0;
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* We have no idea what went wrong. */
+ SVN_ERR_MALFUNCTION();
+}
+
+static svn_error_t *
+error_fetch(serf_request_t *request,
+ report_fetch_t *fetch_ctx,
+ svn_error_t *err)
+{
+ fetch_ctx->done = TRUE;
+
+ fetch_ctx->done_item.data = fetch_ctx;
+ fetch_ctx->done_item.next = *fetch_ctx->done_list;
+ *fetch_ctx->done_list = &fetch_ctx->done_item;
+
+ /* Discard the rest of this request
+ (This makes sure it doesn't error when the request is aborted later) */
+ serf_request_set_handler(request,
+ svn_ra_serf__response_discard_handler, NULL);
+
+ /* Some errors would be handled by serf; make sure they really make
+ the update fail by wrapping it in a different error. */
+ if (!SERF_BUCKET_READ_ERROR(err->apr_err))
+ return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ return err;
+}
+
+/* Wield the editor referenced by INFO to open (or add) the file
+ file also associated with INFO, setting properties on the file and
+ calling the editor's apply_textdelta() function on it if necessary
+ (or if FORCE_APPLY_TEXTDELTA is set).
+
+ Callers will probably want to also see the function that serves
+ the opposite purpose of this one, close_updated_file(). */
+static svn_error_t *
+open_updated_file(report_info_t *info,
+ svn_boolean_t force_apply_textdelta,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = info->dir->report_context;
+ const svn_delta_editor_t *update_editor = ctx->update_editor;
+
+ /* Ensure our parent is open. */
+ SVN_ERR(ensure_dir_opened(info->dir));
+ info->editor_pool = svn_pool_create(info->dir->dir_baton_pool);
+
+ /* Expand our full name now if we haven't done so yet. */
+ if (!info->name)
+ {
+ info->name = svn_relpath_join(info->dir->name, info->base_name,
+ info->editor_pool);
+ }
+
+ /* Open (or add) the file. */
+ if (SVN_IS_VALID_REVNUM(info->base_rev))
+ {
+ SVN_ERR(update_editor->open_file(info->name,
+ info->dir->dir_baton,
+ info->base_rev,
+ info->editor_pool,
+ &info->file_baton));
+ }
+ else
+ {
+ SVN_ERR(update_editor->add_file(info->name,
+ info->dir->dir_baton,
+ info->copyfrom_path,
+ info->copyfrom_rev,
+ info->editor_pool,
+ &info->file_baton));
+ }
+
+ /* Check for lock information. */
+ if (info->lock_token)
+ check_lock(info);
+
+ /* Get (maybe) a textdelta window handler for transmitting file
+ content changes. */
+ if (info->fetch_file || force_apply_textdelta)
+ {
+ SVN_ERR(update_editor->apply_textdelta(info->file_baton,
+ info->base_checksum,
+ info->editor_pool,
+ &info->textdelta,
+ &info->textdelta_baton));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Close the file associated with INFO->file_baton, and cleanup other
+ bits of that structure managed by open_updated_file(). */
+static svn_error_t *
+close_updated_file(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = info->dir->report_context;
+
+ /* Set all of the properties we received */
+ SVN_ERR(svn_ra_serf__walk_all_props(info->props,
+ info->base_name,
+ info->base_rev,
+ set_file_props, info,
+ scratch_pool));
+ SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props,
+ info->base_name,
+ info->base_rev,
+ remove_file_props, info,
+ scratch_pool));
+ if (info->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__walk_all_props(info->props,
+ info->url,
+ ctx->target_rev,
+ set_file_props, info,
+ scratch_pool));
+ }
+
+ /* Close the file via the editor. */
+ SVN_ERR(info->dir->report_context->update_editor->close_file(
+ info->file_baton, info->final_checksum, scratch_pool));
+
+ /* We're done with our editor pool. */
+ svn_pool_destroy(info->editor_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+handle_fetch(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ const char *data;
+ apr_size_t len;
+ apr_status_t status;
+ report_fetch_t *fetch_ctx = handler_baton;
+ svn_error_t *err;
+
+ /* ### new field. make sure we didn't miss some initialization. */
+ SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
+
+ if (!fetch_ctx->read_headers)
+ {
+ serf_bucket_t *hdrs;
+ const char *val;
+ report_info_t *info;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ info = fetch_ctx->info;
+
+ if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0)
+ {
+ fetch_ctx->delta_stream =
+ svn_txdelta_parse_svndiff(info->textdelta,
+ info->textdelta_baton,
+ TRUE, info->editor_pool);
+
+ /* Validate the delta base claimed by the server matches
+ what we asked for! */
+ val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER);
+ if (val && (strcmp(val, info->delta_base) != 0))
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request returned unexpected "
+ "delta base: %s"), val);
+ return error_fetch(request, fetch_ctx, err);
+ }
+ }
+ else
+ {
+ fetch_ctx->delta_stream = NULL;
+ }
+
+ fetch_ctx->read_headers = TRUE;
+ }
+
+ /* If the error code wasn't 200, something went wrong. Don't use the returned
+ data as its probably an error message. Just bail out instead. */
+ if (fetch_ctx->handler->sline.code != 200)
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request failed: %d %s"),
+ fetch_ctx->handler->sline.code,
+ fetch_ctx->handler->sline.reason);
+ return error_fetch(request, fetch_ctx, err);
+ }
+
+ while (1)
+ {
+ svn_txdelta_window_t delta_window = { 0 };
+ svn_txdelta_op_t delta_op;
+ svn_string_t window_data;
+
+ status = serf_bucket_read(response, 8000, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ fetch_ctx->read_size += len;
+
+ if (fetch_ctx->aborted_read)
+ {
+ apr_off_t skip;
+ /* We haven't caught up to where we were before. */
+ if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
+ {
+ /* Eek. What did the file shrink or something? */
+ if (APR_STATUS_IS_EOF(status))
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* Skip on to the next iteration of this loop. */
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ continue;
+ }
+
+ /* Woo-hoo. We're back. */
+ fetch_ctx->aborted_read = FALSE;
+
+ /* Update data and len to just provide the new data. */
+ skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
+ data += skip;
+ len -= skip;
+ }
+
+ if (fetch_ctx->delta_stream)
+ {
+ err = svn_stream_write(fetch_ctx->delta_stream, data, &len);
+ if (err)
+ {
+ return error_fetch(request, fetch_ctx, err);
+ }
+ }
+ /* otherwise, manually construct the text delta window. */
+ else if (len)
+ {
+ window_data.data = data;
+ window_data.len = len;
+
+ delta_op.action_code = svn_txdelta_new;
+ delta_op.offset = 0;
+ delta_op.length = len;
+
+ delta_window.tview_len = len;
+ delta_window.num_ops = 1;
+ delta_window.ops = &delta_op;
+ delta_window.new_data = &window_data;
+
+ /* write to the file located in the info. */
+ err = fetch_ctx->info->textdelta(&delta_window,
+ fetch_ctx->info->textdelta_baton);
+ if (err)
+ {
+ return error_fetch(request, fetch_ctx, err);
+ }
+ }
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ report_info_t *info = fetch_ctx->info;
+
+ if (fetch_ctx->delta_stream)
+ err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream));
+ else
+ err = svn_error_trace(info->textdelta(NULL,
+ info->textdelta_baton));
+ if (err)
+ {
+ return error_fetch(request, fetch_ctx, err);
+ }
+
+ err = close_updated_file(info, info->pool);
+ if (err)
+ {
+ return svn_error_trace(error_fetch(request, fetch_ctx, err));
+ }
+
+ fetch_ctx->done = TRUE;
+
+ fetch_ctx->done_item.data = fetch_ctx;
+ fetch_ctx->done_item.next = *fetch_ctx->done_list;
+ *fetch_ctx->done_list = &fetch_ctx->done_item;
+
+ /* We're done with our pool. */
+ svn_pool_destroy(info->pool);
+
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ }
+ /* not reached */
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+handle_stream(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ report_fetch_t *fetch_ctx = handler_baton;
+ svn_error_t *err;
+ apr_status_t status;
+
+ /* ### new field. make sure we didn't miss some initialization. */
+ SVN_ERR_ASSERT(fetch_ctx->handler != NULL);
+
+ err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline.code,
+ fetch_ctx->info->name,
+ fetch_ctx->handler->location);
+ if (err)
+ {
+ fetch_ctx->handler->done = TRUE;
+
+ err = svn_error_compose_create(
+ err,
+ svn_ra_serf__handle_discard_body(request, response, NULL, pool));
+
+ return svn_error_trace(err);
+ }
+
+ while (1)
+ {
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(response, 8000, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ fetch_ctx->read_size += len;
+
+ if (fetch_ctx->aborted_read)
+ {
+ /* We haven't caught up to where we were before. */
+ if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
+ {
+ /* Eek. What did the file shrink or something? */
+ if (APR_STATUS_IS_EOF(status))
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* Skip on to the next iteration of this loop. */
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ continue;
+ }
+
+ /* Woo-hoo. We're back. */
+ fetch_ctx->aborted_read = FALSE;
+
+ /* Increment data and len by the difference. */
+ data += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
+ len += fetch_ctx->read_size - fetch_ctx->aborted_read_size;
+ }
+
+ if (len)
+ {
+ apr_size_t written_len;
+
+ written_len = len;
+
+ SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data,
+ &written_len));
+ }
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ fetch_ctx->done = TRUE;
+ }
+
+ if (status)
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ }
+ /* not reached */
+}
+
+/* Close the directory represented by DIR -- and any suitable parents
+ thereof -- if we are able to do so. This is the case whenever:
+
+ - there are no remaining open items within the directory, and
+ - the directory's XML close tag has been processed (so we know
+ there are no more children to worry about in the future), and
+ - either:
+ - we aren't fetching properties for this directory, or
+ - we've already finished fetching those properties.
+*/
+static svn_error_t *
+maybe_close_dir_chain(report_dir_t *dir)
+{
+ report_dir_t *cur_dir = dir;
+
+ SVN_ERR(ensure_dir_opened(cur_dir));
+
+ while (cur_dir
+ && !cur_dir->ref_count
+ && cur_dir->tag_closed
+ && (!cur_dir->fetch_props || cur_dir->propfind_handler->done))
+ {
+ report_dir_t *parent = cur_dir->parent_dir;
+ report_context_t *report_context = cur_dir->report_context;
+ svn_boolean_t propfind_in_done_list = FALSE;
+ svn_ra_serf__list_t *done_list;
+
+ /* Make sure there are no references to this dir in the
+ active_dir_propfinds list. If there are, don't close the
+ directory -- which would delete the pool from which the
+ relevant active_dir_propfinds list item is allocated -- and
+ of course don't crawl upward to check the parents for
+ a closure opportunity, either. */
+ done_list = report_context->active_dir_propfinds;
+ while (done_list)
+ {
+ if (done_list->data == cur_dir)
+ {
+ propfind_in_done_list = TRUE;
+ break;
+ }
+ done_list = done_list->next;
+ }
+ if (propfind_in_done_list)
+ break;
+
+ SVN_ERR(close_dir(cur_dir));
+ if (parent)
+ {
+ parent->ref_count--;
+ }
+ else
+ {
+ report_context->closed_root = TRUE;
+ }
+ cur_dir = parent;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Open the file associated with INFO for editing, pass along any
+ propchanges we've recorded for it, and then close the file. */
+static svn_error_t *
+handle_propchange_only(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(open_updated_file(info, FALSE, scratch_pool));
+ SVN_ERR(close_updated_file(info, scratch_pool));
+
+ /* We're done with our pool. */
+ svn_pool_destroy(info->pool);
+
+ info->dir->ref_count--;
+
+ /* See if the parent directory of this file (and perhaps even
+ parents of that) can be closed now. */
+ SVN_ERR(maybe_close_dir_chain(info->dir));
+
+ return SVN_NO_ERROR;
+}
+
+/* "Fetch" a file whose contents were made available via the
+ get_wc_contents() callback (as opposed to requiring a GET to the
+ server), and feed the information through the associated update
+ editor. In editor-speak, this will add/open the file, transmit any
+ property changes, handle the contents, and then close the file. */
+static svn_error_t *
+handle_local_content(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta,
+ info->textdelta_baton, NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(info->cached_contents));
+ info->cached_contents = NULL;
+ SVN_ERR(close_updated_file(info, scratch_pool));
+
+ /* We're done with our pool. */
+ svn_pool_destroy(info->pool);
+
+ info->dir->ref_count--;
+
+ /* See if the parent directory of this fetched item (and
+ perhaps even parents of that) can be closed now. */
+ SVN_ERR(maybe_close_dir_chain(info->dir));
+
+ return SVN_NO_ERROR;
+}
+
+/* --------------------------------------------------------- */
+
+static svn_error_t *
+fetch_file(report_context_t *ctx, report_info_t *info)
+{
+ svn_ra_serf__connection_t *conn;
+ svn_ra_serf__handler_t *handler;
+
+ /* What connection should we go on? */
+ conn = get_best_connection(ctx);
+
+ /* If needed, create the PROPFIND to retrieve the file's properties. */
+ info->propfind_handler = NULL;
+ if (info->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props,
+ ctx->sess, conn, info->url,
+ ctx->target_rev, "0", all_props,
+ &ctx->done_propfinds,
+ info->dir->pool));
+ SVN_ERR_ASSERT(info->propfind_handler);
+
+ /* Create a serf request for the PROPFIND. */
+ svn_ra_serf__request_create(info->propfind_handler);
+
+ ctx->num_active_propfinds++;
+ }
+
+ /* If we've been asked to fetch the file or it's an add, do so.
+ * Otherwise, handle the case where only the properties changed.
+ */
+ if (info->fetch_file && ctx->text_deltas)
+ {
+ svn_stream_t *contents = NULL;
+
+ /* Open the file for editing. */
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+
+ if (info->textdelta == svn_delta_noop_window_handler)
+ {
+ /* There is nobody looking for an actual stream.
+
+ Just report an empty stream instead of fetching
+ to be ingored data */
+ info->cached_contents = svn_stream_empty(info->pool);
+ }
+ else if (ctx->sess->wc_callbacks->get_wc_contents
+ && info->final_sha1_checksum)
+ {
+ svn_error_t *err = NULL;
+ svn_checksum_t *checksum = NULL;
+
+ /* Parse the optional SHA1 checksum (1.7+) */
+ err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
+ info->final_sha1_checksum,
+ info->pool);
+
+ /* Okay so far? Let's try to get a stream on some readily
+ available matching content. */
+ if (!err && checksum)
+ {
+ err = ctx->sess->wc_callbacks->get_wc_contents(
+ ctx->sess->wc_callback_baton, &contents,
+ checksum, info->pool);
+
+ if (! err)
+ info->cached_contents = contents;
+ }
+
+ if (err)
+ {
+ /* Meh. Maybe we'll care one day why we're in an
+ errorful state, but this codepath is optional. */
+ svn_error_clear(err);
+ }
+ }
+
+ /* If the working copy can provide cached contents for this
+ file, we don't have to fetch them from the server. */
+ if (info->cached_contents)
+ {
+ /* If we'll be doing a PROPFIND for this file... */
+ if (info->propfind_handler)
+ {
+ /* ... then we'll just leave ourselves a little "todo"
+ about that fact (and we'll deal with the file content
+ stuff later, after we've handled that PROPFIND
+ response. */
+ svn_ra_serf__list_t *list_item;
+
+ list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
+ list_item->data = info;
+ list_item->next = ctx->file_propchanges_only;
+ ctx->file_propchanges_only = list_item;
+ }
+ else
+ {
+ /* Otherwise, if we've no PROPFIND to do, we might as
+ well take care of those locally accessible file
+ contents now. */
+ SVN_ERR(handle_local_content(info, info->pool));
+ }
+ }
+ else
+ {
+ /* Otherwise, we use a GET request for the file's contents. */
+ report_fetch_t *fetch_ctx;
+
+ fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx));
+ fetch_ctx->info = info;
+ fetch_ctx->done_list = &ctx->done_fetches;
+ fetch_ctx->sess = ctx->sess;
+ fetch_ctx->conn = conn;
+
+ handler = apr_pcalloc(info->dir->pool, sizeof(*handler));
+
+ handler->handler_pool = info->dir->pool;
+ handler->method = "GET";
+ handler->path = fetch_ctx->info->url;
+
+ handler->conn = conn;
+ handler->session = ctx->sess;
+
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = headers_fetch;
+ handler->header_delegate_baton = fetch_ctx;
+
+ handler->response_handler = handle_fetch;
+ handler->response_baton = fetch_ctx;
+
+ handler->response_error = cancel_fetch;
+ handler->response_error_baton = fetch_ctx;
+
+ fetch_ctx->handler = handler;
+
+ svn_ra_serf__request_create(handler);
+
+ ctx->num_active_fetches++;
+ }
+ }
+ else if (info->propfind_handler)
+ {
+ svn_ra_serf__list_t *list_item;
+
+ list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
+ list_item->data = info;
+ list_item->next = ctx->file_propchanges_only;
+ ctx->file_propchanges_only = list_item;
+ }
+ else
+ {
+ /* No propfind or GET request. Just handle the prop changes now. */
+ SVN_ERR(handle_propchange_only(info, info->pool));
+ }
+
+ if (ctx->num_active_fetches + ctx->num_active_propfinds
+ > REQUEST_COUNT_TO_PAUSE)
+ ctx->parser_ctx->paused = TRUE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/** XML callbacks for our update-report response parsing */
+
+static svn_error_t *
+start_report(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = parser->user_data;
+ report_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE && strcmp(name.name, "update-report") == 0)
+ {
+ const char *val;
+
+ val = svn_xml_get_attr_value("inline-props", attrs);
+ if (val && (strcmp(val, "true") == 0))
+ ctx->add_props_included = TRUE;
+
+ val = svn_xml_get_attr_value("send-all", attrs);
+ if (val && (strcmp(val, "true") == 0))
+ {
+ ctx->send_all_mode = TRUE;
+
+ /* All properties are included in send-all mode. */
+ ctx->add_props_included = TRUE;
+ }
+ }
+ else if (state == NONE && strcmp(name.name, "target-revision") == 0)
+ {
+ const char *rev;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in target-revision element"));
+ }
+
+ SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton,
+ SVN_STR_TO_REV(rev),
+ ctx->sess->pool));
+ }
+ else if (state == NONE && strcmp(name.name, "open-directory") == 0)
+ {
+ const char *rev;
+ report_info_t *info;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-directory element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_DIR);
+
+ info->base_rev = SVN_STR_TO_REV(rev);
+ info->dir->base_rev = info->base_rev;
+ info->fetch_props = TRUE;
+
+ info->dir->base_name = "";
+ info->dir->name = "";
+
+ info->base_name = info->dir->base_name;
+ info->name = info->dir->name;
+
+ info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, "");
+
+ if (!info->dir->repos_relpath)
+ SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath,
+ ctx->sess->session_url.path,
+ ctx->sess, ctx->conn,
+ info->dir->pool));
+ }
+ else if (state == NONE)
+ {
+ /* do nothing as we haven't seen our valid start tag yet. */
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "open-directory") == 0)
+ {
+ const char *rev, *dirname;
+ report_dir_t *dir;
+ report_info_t *info;
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-directory element"));
+ }
+
+ dirname = svn_xml_get_attr_value("name", attrs);
+
+ if (!dirname)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in open-directory element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_DIR);
+
+ dir = info->dir;
+
+ info->base_rev = SVN_STR_TO_REV(rev);
+ dir->base_rev = info->base_rev;
+
+ info->fetch_props = FALSE;
+
+ dir->base_name = apr_pstrdup(dir->pool, dirname);
+ info->base_name = dir->base_name;
+
+ /* Expand our name. */
+ dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
+ dir->pool);
+ info->name = dir->name;
+
+ dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name);
+
+ if (!dir->repos_relpath)
+ dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
+ dir->base_name, dir->pool);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "add-directory") == 0)
+ {
+ const char *dir_name, *cf, *cr;
+ report_dir_t *dir;
+ report_info_t *info;
+
+ dir_name = svn_xml_get_attr_value("name", attrs);
+ if (!dir_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in add-directory element"));
+ }
+ cf = svn_xml_get_attr_value("copyfrom-path", attrs);
+ cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
+
+ info = push_state(parser, ctx, ADD_DIR);
+
+ dir = info->dir;
+
+ dir->base_name = apr_pstrdup(dir->pool, dir_name);
+ info->base_name = dir->base_name;
+
+ /* Expand our name. */
+ dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name,
+ dir->pool);
+ info->name = dir->name;
+
+ info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
+ info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
+
+ /* Mark that we don't have a base. */
+ info->base_rev = SVN_INVALID_REVNUM;
+ dir->base_rev = info->base_rev;
+
+ /* If the server isn't included properties for added items,
+ we'll need to fetch them ourselves. */
+ if (! ctx->add_props_included)
+ dir->fetch_props = TRUE;
+
+ dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath,
+ dir->base_name, dir->pool);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "open-file") == 0)
+ {
+ const char *file_name, *rev;
+ report_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in open-file element"));
+ }
+
+ rev = svn_xml_get_attr_value("rev", attrs);
+
+ if (!rev)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing revision attr in open-file element"));
+ }
+
+ info = push_state(parser, ctx, OPEN_FILE);
+
+ info->base_rev = SVN_STR_TO_REV(rev);
+ info->fetch_props = FALSE;
+
+ info->base_name = apr_pstrdup(info->pool, file_name);
+ info->name = NULL;
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "add-file") == 0)
+ {
+ const char *file_name, *cf, *cr;
+ report_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+ cf = svn_xml_get_attr_value("copyfrom-path", attrs);
+ cr = svn_xml_get_attr_value("copyfrom-rev", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in add-file element"));
+ }
+
+ info = push_state(parser, ctx, ADD_FILE);
+
+ info->base_rev = SVN_INVALID_REVNUM;
+
+ /* If the server isn't in "send-all" mode, we should expect to
+ fetch contents for added files. */
+ if (! ctx->send_all_mode)
+ info->fetch_file = TRUE;
+
+ /* If the server isn't included properties for added items,
+ we'll need to fetch them ourselves. */
+ if (! ctx->add_props_included)
+ info->fetch_props = TRUE;
+
+ info->base_name = apr_pstrdup(info->pool, file_name);
+ info->name = NULL;
+
+ info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL;
+ info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM;
+
+ info->final_sha1_checksum =
+ svn_xml_get_attr_value("sha1-checksum", attrs);
+ if (info->final_sha1_checksum)
+ info->final_sha1_checksum = apr_pstrdup(info->pool,
+ info->final_sha1_checksum);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "delete-entry") == 0)
+ {
+ const char *file_name;
+ const char *rev_str;
+ report_info_t *info;
+ apr_pool_t *tmppool;
+ const char *full_path;
+ svn_revnum_t delete_rev = SVN_INVALID_REVNUM;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in delete-entry element"));
+ }
+
+ rev_str = svn_xml_get_attr_value("rev", attrs);
+ if (rev_str) /* Not available on older repositories! */
+ delete_rev = SVN_STR_TO_REV(rev_str);
+
+ info = parser->state->private;
+
+ SVN_ERR(ensure_dir_opened(info->dir));
+
+ tmppool = svn_pool_create(info->dir->dir_baton_pool);
+
+ full_path = svn_relpath_join(info->dir->name, file_name, tmppool);
+
+ SVN_ERR(ctx->update_editor->delete_entry(full_path,
+ delete_rev,
+ info->dir->dir_baton,
+ tmppool));
+
+ svn_pool_destroy(tmppool);
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "absent-directory") == 0)
+ {
+ const char *file_name;
+ report_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in absent-directory element"));
+ }
+
+ info = parser->state->private;
+
+ SVN_ERR(ensure_dir_opened(info->dir));
+
+ SVN_ERR(ctx->update_editor->absent_directory(
+ svn_relpath_join(info->name, file_name,
+ info->dir->pool),
+ info->dir->dir_baton,
+ info->dir->pool));
+ }
+ else if ((state == OPEN_DIR || state == ADD_DIR) &&
+ strcmp(name.name, "absent-file") == 0)
+ {
+ const char *file_name;
+ report_info_t *info;
+
+ file_name = svn_xml_get_attr_value("name", attrs);
+
+ if (!file_name)
+ {
+ return svn_error_create(
+ SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in absent-file element"));
+ }
+
+ info = parser->state->private;
+
+ SVN_ERR(ensure_dir_opened(info->dir));
+
+ SVN_ERR(ctx->update_editor->absent_file(
+ svn_relpath_join(info->name, file_name,
+ info->dir->pool),
+ info->dir->dir_baton,
+ info->dir->pool));
+ }
+ else if (state == OPEN_DIR || state == ADD_DIR)
+ {
+ report_info_t *info;
+
+ if (strcmp(name.name, "checked-in") == 0)
+ {
+ info = push_state(parser, ctx, IGNORE_PROP_NAME);
+ info->prop_ns = name.namespace;
+ info->prop_name = apr_pstrdup(parser->state->pool, name.name);
+ info->prop_encoding = NULL;
+ svn_stringbuf_setempty(info->prop_value);
+ }
+ else if (strcmp(name.name, "set-prop") == 0 ||
+ strcmp(name.name, "remove-prop") == 0)
+ {
+ const char *full_prop_name;
+ const char *colon;
+
+ info = push_state(parser, ctx, PROP);
+
+ full_prop_name = svn_xml_get_attr_value("name", attrs);
+ if (!full_prop_name)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in %s element"),
+ name.name);
+ }
+
+ colon = strchr(full_prop_name, ':');
+
+ if (colon)
+ colon++;
+ else
+ colon = full_prop_name;
+
+ info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
+ colon - full_prop_name);
+ info->prop_name = apr_pstrdup(parser->state->pool, colon);
+ info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
+ svn_stringbuf_setempty(info->prop_value);
+ }
+ else if (strcmp(name.name, "prop") == 0)
+ {
+ /* need to fetch it. */
+ push_state(parser, ctx, NEED_PROP_NAME);
+ }
+ else if (strcmp(name.name, "fetch-props") == 0)
+ {
+ info = parser->state->private;
+
+ info->dir->fetch_props = TRUE;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unknown tag '%s' while at state %d"),
+ name.name, state);
+ }
+
+ }
+ else if (state == OPEN_FILE || state == ADD_FILE)
+ {
+ report_info_t *info;
+
+ if (strcmp(name.name, "checked-in") == 0)
+ {
+ info = push_state(parser, ctx, IGNORE_PROP_NAME);
+ info->prop_ns = name.namespace;
+ info->prop_name = apr_pstrdup(parser->state->pool, name.name);
+ info->prop_encoding = NULL;
+ svn_stringbuf_setempty(info->prop_value);
+ }
+ else if (strcmp(name.name, "prop") == 0)
+ {
+ /* need to fetch it. */
+ push_state(parser, ctx, NEED_PROP_NAME);
+ }
+ else if (strcmp(name.name, "fetch-props") == 0)
+ {
+ info = parser->state->private;
+
+ info->fetch_props = TRUE;
+ }
+ else if (strcmp(name.name, "fetch-file") == 0)
+ {
+ info = parser->state->private;
+ info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs);
+
+ if (info->base_checksum)
+ info->base_checksum = apr_pstrdup(info->pool, info->base_checksum);
+
+ info->final_sha1_checksum =
+ svn_xml_get_attr_value("sha1-checksum", attrs);
+ if (info->final_sha1_checksum)
+ info->final_sha1_checksum = apr_pstrdup(info->pool,
+ info->final_sha1_checksum);
+
+ info->fetch_file = TRUE;
+ }
+ else if (strcmp(name.name, "set-prop") == 0 ||
+ strcmp(name.name, "remove-prop") == 0)
+ {
+ const char *full_prop_name;
+ const char *colon;
+
+ info = push_state(parser, ctx, PROP);
+
+ full_prop_name = svn_xml_get_attr_value("name", attrs);
+ if (!full_prop_name)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in %s element"),
+ name.name);
+ }
+ colon = strchr(full_prop_name, ':');
+
+ if (colon)
+ colon++;
+ else
+ colon = full_prop_name;
+
+ info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name,
+ colon - full_prop_name);
+ info->prop_name = apr_pstrdup(parser->state->pool, colon);
+ info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
+ svn_stringbuf_setempty(info->prop_value);
+ }
+ else if (strcmp(name.name, "txdelta") == 0)
+ {
+ /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in
+ addition to <fetch-file>s and such) when *not* in
+ "send-all" mode. As a client, we're smart enough to know
+ that's wrong, so we'll just ignore these tags. */
+ if (ctx->send_all_mode)
+ {
+ const svn_delta_editor_t *update_editor = ctx->update_editor;
+
+ info = push_state(parser, ctx, TXDELTA);
+
+ if (! info->file_baton)
+ {
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ }
+
+ info->base_checksum = svn_xml_get_attr_value("base-checksum",
+ attrs);
+ SVN_ERR(update_editor->apply_textdelta(info->file_baton,
+ info->base_checksum,
+ info->editor_pool,
+ &info->textdelta,
+ &info->textdelta_baton));
+ info->svndiff_decoder = svn_txdelta_parse_svndiff(
+ info->textdelta,
+ info->textdelta_baton,
+ TRUE, info->pool);
+ info->base64_decoder = svn_base64_decode(info->svndiff_decoder,
+ info->pool);
+ }
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unknown tag '%s' while at state %d"),
+ name.name, state);
+ }
+ }
+ else if (state == IGNORE_PROP_NAME)
+ {
+ report_info_t *info = push_state(parser, ctx, PROP);
+ info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
+ }
+ else if (state == NEED_PROP_NAME)
+ {
+ report_info_t *info;
+
+ info = push_state(parser, ctx, PROP);
+
+ info->prop_ns = name.namespace;
+ info->prop_name = apr_pstrdup(parser->state->pool, name.name);
+ info->prop_encoding = svn_xml_get_attr_value("encoding", attrs);
+ svn_stringbuf_setempty(info->prop_value);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_report(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = parser->user_data;
+ report_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE)
+ {
+ if (strcmp(name.name, "update-report") == 0)
+ {
+ ctx->report_completed = TRUE;
+ }
+ else
+ {
+ /* nothing to close yet. */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) ||
+ (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0))))
+ {
+ const char *checked_in_url;
+ report_info_t *info = parser->state->private;
+
+ /* We've now closed this directory; note it. */
+ info->dir->tag_closed = TRUE;
+
+ /* go fetch info->file_name from DAV:checked-in */
+ checked_in_url =
+ svn_ra_serf__get_ver_prop(info->dir->props, info->base_name,
+ info->base_rev, "DAV:", "checked-in");
+
+ /* If we were expecting to have the properties and we aren't able to
+ * get it, bail.
+ */
+ if (!checked_in_url &&
+ (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props))
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
+ }
+
+ info->dir->url = checked_in_url;
+
+ /* At this point, we should have the checked-in href.
+ * If needed, create the PROPFIND to retrieve the dir's properties.
+ */
+ if (info->dir->fetch_props)
+ {
+ svn_ra_serf__list_t *list_item;
+
+ SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler,
+ info->dir->props, ctx->sess,
+ get_best_connection(ctx),
+ info->dir->url,
+ ctx->target_rev, "0",
+ all_props,
+ &ctx->done_dir_propfinds,
+ info->dir->pool));
+ SVN_ERR_ASSERT(info->dir->propfind_handler);
+
+ /* Create a serf request for the PROPFIND. */
+ svn_ra_serf__request_create(info->dir->propfind_handler);
+
+ ctx->num_active_propfinds++;
+
+ list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item));
+ list_item->data = info->dir;
+ list_item->next = ctx->active_dir_propfinds;
+ ctx->active_dir_propfinds = list_item;
+
+ if (ctx->num_active_fetches + ctx->num_active_propfinds
+ > REQUEST_COUNT_TO_PAUSE)
+ ctx->parser_ctx->paused = TRUE;
+ }
+ else
+ {
+ info->dir->propfind_handler = NULL;
+ }
+
+ /* See if this directory (and perhaps even parents of that) can
+ be closed now. This is likely to be the case only if we
+ didn't need to contact the server for supplemental
+ information required to handle any of this directory's
+ children. */
+ SVN_ERR(maybe_close_dir_chain(info->dir));
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0)
+ {
+ report_info_t *info = parser->state->private;
+
+ /* Expand our full name now if we haven't done so yet. */
+ if (!info->name)
+ {
+ info->name = svn_relpath_join(info->dir->name, info->base_name,
+ info->pool);
+ }
+
+ info->lock_token = svn_hash_gets(ctx->lock_path_tokens, info->name);
+
+ if (info->lock_token && !info->fetch_props)
+ info->fetch_props = TRUE;
+
+ /* If possible, we'd like to fetch only a delta against a
+ * version of the file we already have in our working copy,
+ * rather than fetching a fulltext.
+ *
+ * In HTTP v2, we can simply construct the URL we need given the
+ * repos_relpath and base revision number.
+ */
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
+ {
+ const char *repos_relpath;
+
+ /* If this file is switched vs the editor root we should provide
+ its real url instead of the one calculated from the session root.
+ */
+ repos_relpath = svn_hash_gets(ctx->switched_paths, info->name);
+
+ if (!repos_relpath)
+ {
+ if (ctx->root_is_switched)
+ {
+ /* We are updating a direct target (most likely a file)
+ that is switched vs its parent url */
+ SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool)
+ == '\0');
+
+ repos_relpath = svn_hash_gets(ctx->switched_paths, "");
+ }
+ else
+ repos_relpath = svn_relpath_join(info->dir->repos_relpath,
+ info->base_name, info->pool);
+ }
+
+ info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s",
+ ctx->sess->rev_root_stub,
+ info->base_rev,
+ svn_path_uri_encode(repos_relpath,
+ info->pool));
+ }
+ else if (ctx->sess->wc_callbacks->get_wc_prop)
+ {
+ /* If we have a WC, we might be able to dive all the way into the WC
+ * to get the previous URL so we can do a differential GET with the
+ * base URL.
+ */
+ const svn_string_t *value = NULL;
+ SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop(
+ ctx->sess->wc_callback_baton, info->name,
+ SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool));
+
+ info->delta_base = value ? value->data : NULL;
+ }
+
+ /* go fetch info->name from DAV:checked-in */
+ info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
+ info->base_rev, "DAV:", "checked-in");
+ if (!info->url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
+ }
+
+ /* If the server is in "send-all" mode, we might have opened the
+ file when we started seeing content for it. If we didn't get
+ any content for it, we still need to open the file. But in
+ any case, we can then immediately close it. */
+ if (ctx->send_all_mode)
+ {
+ if (! info->file_baton)
+ {
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ }
+ SVN_ERR(close_updated_file(info, info->pool));
+ info->dir->ref_count--;
+ }
+ /* Otherwise, if the server is *not* in "send-all" mode, we
+ should be at a point where we can queue up any auxiliary
+ content-fetching requests. */
+ else
+ {
+ SVN_ERR(fetch_file(ctx, info));
+ }
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
+ {
+ report_info_t *info = parser->state->private;
+
+ /* go fetch info->name from DAV:checked-in */
+ info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name,
+ info->base_rev, "DAV:", "checked-in");
+ if (!info->url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("The REPORT or PROPFIND response did not "
+ "include the requested checked-in value"));
+ }
+
+ /* If the server is in "send-all" mode, we might have opened the
+ file when we started seeing content for it. If we didn't get
+ any content for it, we still need to open the file. But in
+ any case, we can then immediately close it. */
+ if (ctx->send_all_mode)
+ {
+ if (! info->file_baton)
+ {
+ SVN_ERR(open_updated_file(info, FALSE, info->pool));
+ }
+ SVN_ERR(close_updated_file(info, info->pool));
+ info->dir->ref_count--;
+ }
+ /* Otherwise, if the server is *not* in "send-all" mode, we
+ should be at a point where we can queue up any auxiliary
+ content-fetching requests. */
+ else
+ {
+ SVN_ERR(fetch_file(ctx, info));
+ }
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0)
+ {
+ report_info_t *info = parser->state->private;
+
+ /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
+ <fetch-file>s and such) when *not* in "send-all" mode. As a
+ client, we're smart enough to know that's wrong, so when not
+ in "receiving-all" mode, we'll ignore these tags. */
+ if (ctx->send_all_mode)
+ {
+ SVN_ERR(svn_stream_close(info->base64_decoder));
+ }
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == PROP)
+ {
+ /* We need to move the prop_ns, prop_name, and prop_value into the
+ * same lifetime as the dir->pool.
+ */
+ svn_ra_serf__ns_t *ns, *ns_name_match;
+ svn_boolean_t found = FALSE;
+ report_info_t *info;
+ report_dir_t *dir;
+ apr_hash_t *props;
+ const svn_string_t *set_val_str;
+ apr_pool_t *pool;
+
+ info = parser->state->private;
+ dir = info->dir;
+
+ /* We're going to be slightly tricky. We don't care what the ->url
+ * field is here at this point. So, we're going to stick a single
+ * copy of the property name inside of the ->url field.
+ */
+ ns_name_match = NULL;
+ for (ns = dir->ns_list; ns; ns = ns->next)
+ {
+ if (strcmp(ns->namespace, info->prop_ns) == 0)
+ {
+ ns_name_match = ns;
+ if (strcmp(ns->url, info->prop_name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ ns = apr_palloc(dir->pool, sizeof(*ns));
+ if (!ns_name_match)
+ {
+ ns->namespace = apr_pstrdup(dir->pool, info->prop_ns);
+ }
+ else
+ {
+ ns->namespace = ns_name_match->namespace;
+ }
+ ns->url = apr_pstrdup(dir->pool, info->prop_name);
+
+ ns->next = dir->ns_list;
+ dir->ns_list = ns;
+ }
+
+ if (strcmp(name.name, "remove-prop") != 0)
+ {
+ props = info->props;
+ pool = info->pool;
+ }
+ else
+ {
+ props = dir->removed_props;
+ pool = dir->pool;
+ svn_stringbuf_setempty(info->prop_value);
+ }
+
+ if (info->prop_encoding)
+ {
+ if (strcmp(info->prop_encoding, "base64") == 0)
+ {
+ svn_string_t tmp;
+
+ /* Don't use morph_info_string cuz we need prop_value to
+ remain usable. */
+ tmp.data = info->prop_value->data;
+ tmp.len = info->prop_value->len;
+
+ set_val_str = svn_base64_decode_string(&tmp, pool);
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ NULL,
+ _("Got unrecognized encoding '%s'"),
+ info->prop_encoding);
+ }
+ }
+ else
+ {
+ set_val_str = svn_string_create_from_buf(info->prop_value, pool);
+ }
+
+ svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
+ ns->namespace, ns->url, set_val_str, pool);
+
+ /* Advance handling: if we spotted the md5-checksum property on
+ the wire, remember it's value. */
+ if (strcmp(ns->url, "md5-checksum") == 0
+ && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0)
+ info->final_checksum = apr_pstrdup(info->pool, set_val_str->data);
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_report(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *ctx = parser->user_data;
+
+ UNUSED_CTX(ctx);
+
+ if (parser->state->current_state == PROP)
+ {
+ report_info_t *info = parser->state->private;
+
+ svn_stringbuf_appendbytes(info->prop_value, data, len);
+ }
+ else if (parser->state->current_state == TXDELTA)
+ {
+ /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to
+ <fetch-file>s and such) when *not* in "send-all" mode. As a
+ client, we're smart enough to know that's wrong, so when not
+ in "receiving-all" mode, we'll ignore these tags. */
+ if (ctx->send_all_mode)
+ {
+ apr_size_t nlen = len;
+ report_info_t *info = parser->state->private;
+
+ SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen));
+ if (nlen != len)
+ {
+ /* Short write without associated error? "Can't happen." */
+ return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
+ _("Error writing to '%s': unexpected EOF"),
+ info->name);
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/** Editor callbacks given to callers to create request body */
+
+/* Helper to create simple xml tag without attributes. */
+static void
+make_simple_xml_tag(svn_stringbuf_t **buf_p,
+ const char *tagname,
+ const char *cdata,
+ apr_pool_t *pool)
+{
+ svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL);
+ svn_xml_escape_cdata_cstring(buf_p, cdata, pool);
+ svn_xml_make_close_tag(buf_p, pool, tagname);
+}
+
+static svn_error_t *
+set_path(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ report_context_t *report = report_baton;
+ svn_stringbuf_t *buf = NULL;
+
+ svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
+ "rev", apr_ltoa(pool, revision),
+ "lock-token", lock_token,
+ "depth", svn_depth_to_word(depth),
+ "start-empty", start_empty ? "true" : NULL,
+ NULL);
+ svn_xml_escape_cdata_cstring(&buf, path, pool);
+ svn_xml_make_close_tag(&buf, pool, "S:entry");
+
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, pool));
+
+ if (lock_token)
+ {
+ svn_hash_sets(report->lock_path_tokens,
+ apr_pstrdup(report->pool, path),
+ apr_pstrdup(report->pool, lock_token));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_path(void *report_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ report_context_t *report = report_baton;
+ svn_stringbuf_t *buf = NULL;
+
+ make_simple_xml_tag(&buf, "S:missing", path, pool);
+
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+link_path(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ report_context_t *report = report_baton;
+ const char *link, *report_target;
+ apr_uri_t uri;
+ apr_status_t status;
+ svn_stringbuf_t *buf = NULL;
+
+ /* We need to pass in the baseline relative path.
+ *
+ * TODO Confirm that it's on the same server?
+ */
+ status = apr_uri_parse(pool, url, &uri);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unable to parse URL '%s'"), url);
+ }
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess,
+ NULL, pool));
+ SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess,
+ NULL, pool));
+
+ link = apr_pstrcat(pool, "/", link, (char *)NULL);
+
+ svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry",
+ "rev", apr_ltoa(pool, revision),
+ "lock-token", lock_token,
+ "depth", svn_depth_to_word(depth),
+ "linkpath", link,
+ "start-empty", start_empty ? "true" : NULL,
+ NULL);
+ svn_xml_escape_cdata_cstring(&buf, path, pool);
+ svn_xml_make_close_tag(&buf, pool, "S:entry");
+
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, pool));
+
+ /* Store the switch roots to allow generating repos_relpaths from just
+ the working copy paths. (Needed for HTTPv2) */
+ path = apr_pstrdup(report->pool, path);
+ svn_hash_sets(report->switched_paths,
+ path, apr_pstrdup(report->pool, link + 1));
+
+ if (!*path)
+ report->root_is_switched = TRUE;
+
+ if (lock_token)
+ {
+ svn_hash_sets(report->lock_path_tokens,
+ path, apr_pstrdup(report->pool, lock_token));
+ }
+
+ return APR_SUCCESS;
+}
+
+/** Minimum nr. of outstanding requests needed before a new connection is
+ * opened. */
+#define REQS_PER_CONN 8
+
+/** This function creates a new connection for this serf session, but only
+ * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is
+ * only one main connection open.
+ */
+static svn_error_t *
+open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs)
+{
+ /* For each REQS_PER_CONN outstanding requests open a new connection, with
+ * a minimum of 1 extra connection. */
+ if (sess->num_conns == 1 ||
+ ((num_active_reqs / REQS_PER_CONN) > sess->num_conns))
+ {
+ int cur = sess->num_conns;
+ apr_status_t status;
+
+ sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur]));
+ sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
+ NULL, NULL);
+ sess->conns[cur]->last_status_code = -1;
+ sess->conns[cur]->session = sess;
+ status = serf_connection_create2(&sess->conns[cur]->conn,
+ sess->context,
+ sess->session_url,
+ svn_ra_serf__conn_setup,
+ sess->conns[cur],
+ svn_ra_serf__conn_closed,
+ sess->conns[cur],
+ sess->pool);
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+
+ sess->num_conns++;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Serf callback to create update request body bucket. */
+static svn_error_t *
+create_update_report_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool)
+{
+ report_context_t *report = baton;
+ apr_off_t offset;
+
+ offset = 0;
+ apr_file_seek(report->body_file, APR_SET, &offset);
+
+ *body_bkt = serf_bucket_file_create(report->body_file, alloc);
+
+ return SVN_NO_ERROR;
+}
+
+/* Serf callback to setup update request headers. */
+static svn_error_t *
+setup_update_report_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ report_context_t *report = baton;
+
+ if (report->sess->using_compression)
+ {
+ serf_bucket_headers_setn(headers, "Accept-Encoding",
+ "gzip;svndiff1;q=0.9,svndiff;q=0.8");
+ }
+ else
+ {
+ serf_bucket_headers_setn(headers, "Accept-Encoding",
+ "svndiff1;q=0.9,svndiff;q=0.8");
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+finish_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ report_context_t *report = report_baton;
+ svn_ra_serf__session_t *sess = report->sess;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ const char *report_target;
+ svn_stringbuf_t *buf = NULL;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_error_t *err;
+ apr_interval_time_t waittime_left = sess->timeout;
+
+ svn_xml_make_close_tag(&buf, iterpool, "S:update-report");
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, iterpool));
+
+ /* We need to flush the file, make it unbuffered (so that it can be
+ * zero-copied via mmap), and reset the position before attempting to
+ * deliver the file.
+ *
+ * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap
+ * and zero-copy the PUT body. However, on older APR versions, we can't
+ * check the buffer status; but serf will fall through and create a file
+ * bucket for us on the buffered svndiff handle.
+ */
+ apr_file_flush(report->body_file);
+#if APR_VERSION_AT_LEAST(1, 3, 0)
+ apr_file_buffer_set(report->body_file, NULL, 0);
+#endif
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool));
+
+ /* create and deliver request */
+ report->path = report_target;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->handler_pool = pool;
+ handler->method = "REPORT";
+ handler->path = report->path;
+ handler->body_delegate = create_update_report_body;
+ handler->body_delegate_baton = report;
+ handler->body_type = "text/xml";
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = setup_update_report_headers;
+ handler->header_delegate_baton = report;
+ handler->conn = sess->conns[0];
+ handler->session = sess;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->response_type = "update-report";
+ parser_ctx->user_data = report;
+ parser_ctx->start = start_report;
+ parser_ctx->end = end_report;
+ parser_ctx->cdata = cdata_report;
+ parser_ctx->done = &report->done;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ report->parser_ctx = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ /* Open the first extra connection. */
+ SVN_ERR(open_connection_if_needed(sess, 0));
+
+ sess->cur_conn = 1;
+
+ /* Note that we may have no active GET or PROPFIND requests, yet the
+ processing has not been completed. This could be from a delay on the
+ network or because we've spooled the entire response into our "pending"
+ content of the XML parser. The DONE flag will get set when all the
+ XML content has been received *and* parsed. */
+ while (!report->done
+ || report->num_active_fetches
+ || report->num_active_propfinds)
+ {
+ apr_pool_t *iterpool_inner;
+ svn_ra_serf__list_t *done_list;
+ int i;
+ apr_status_t status;
+
+ /* Note: this throws out the old ITERPOOL_INNER. */
+ svn_pool_clear(iterpool);
+
+ if (sess->cancel_func)
+ SVN_ERR(sess->cancel_func(sess->cancel_baton));
+
+ /* We need to be careful between the outer and inner ITERPOOLs,
+ and what items are allocated within. */
+ iterpool_inner = svn_pool_create(iterpool);
+
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ iterpool_inner);
+
+ err = sess->pending_error;
+ sess->pending_error = SVN_NO_ERROR;
+
+ if (!err && handler->done && handler->server_error)
+ {
+ err = handler->server_error->error;
+ }
+
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
+ if (APR_STATUS_IS_TIMEUP(status))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ status = 0;
+
+ if (sess->timeout)
+ {
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out"));
+ }
+ }
+ }
+ else
+ {
+ waittime_left = sess->timeout;
+ }
+
+ if (status && handler->sline.code != 200)
+ {
+ return svn_error_trace(
+ svn_error_compose_create(
+ svn_ra_serf__error_on_status(handler->sline.code,
+ handler->path,
+ handler->location),
+ err));
+ }
+ SVN_ERR(err);
+ if (status)
+ {
+ return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT"));
+ }
+
+ /* Open extra connections if we have enough requests to send. */
+ if (sess->num_conns < sess->max_connections)
+ SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches +
+ report->num_active_propfinds));
+
+ /* Prune completed file PROPFINDs. */
+ done_list = report->done_propfinds;
+ while (done_list)
+ {
+ svn_ra_serf__list_t *next_done = done_list->next;
+
+ svn_pool_clear(iterpool_inner);
+
+ report->num_active_propfinds--;
+
+ /* If we have some files that we won't be fetching the content
+ * for, ensure that we update the file with any altered props.
+ */
+ if (report->file_propchanges_only)
+ {
+ svn_ra_serf__list_t *cur, *prev;
+
+ prev = NULL;
+ cur = report->file_propchanges_only;
+
+ while (cur)
+ {
+ report_info_t *item = cur->data;
+
+ if (item->propfind_handler == done_list->data)
+ {
+ break;
+ }
+
+ prev = cur;
+ cur = cur->next;
+ }
+
+ /* If we found a match, set the new props and remove this
+ * propchange from our list.
+ */
+ if (cur)
+ {
+ report_info_t *info = cur->data;
+
+ if (!prev)
+ {
+ report->file_propchanges_only = cur->next;
+ }
+ else
+ {
+ prev->next = cur->next;
+ }
+
+ /* If we've got cached file content for this file,
+ take care of the locally collected properties and
+ file content at once. Otherwise, just deal with
+ the collected properties.
+
+ NOTE: These functions below could delete
+ info->dir->pool (via maybe_close_dir_chain()),
+ from which is allocated the list item in
+ report->file_propchanges_only.
+ */
+ if (info->cached_contents)
+ {
+ SVN_ERR(handle_local_content(info, iterpool_inner));
+ }
+ else
+ {
+ SVN_ERR(handle_propchange_only(info, iterpool_inner));
+ }
+ }
+ }
+
+ done_list = next_done;
+ }
+ report->done_propfinds = NULL;
+
+ /* Prune completed fetches from our list. */
+ done_list = report->done_fetches;
+ while (done_list)
+ {
+ report_fetch_t *done_fetch = done_list->data;
+ svn_ra_serf__list_t *next_done = done_list->next;
+ report_dir_t *cur_dir;
+
+ /* Decrease the refcount in the parent directory of the file
+ whose fetch has completed. */
+ cur_dir = done_fetch->info->dir;
+ cur_dir->ref_count--;
+
+ /* Decrement our active fetch count. */
+ report->num_active_fetches--;
+
+ /* See if the parent directory of this fetched item (and
+ perhaps even parents of that) can be closed now.
+
+ NOTE: This could delete cur_dir->pool, from which is
+ allocated the list item in report->done_fetches.
+ */
+ SVN_ERR(maybe_close_dir_chain(cur_dir));
+
+ done_list = next_done;
+ }
+ report->done_fetches = NULL;
+
+ /* Prune completed directory PROPFINDs. */
+ done_list = report->done_dir_propfinds;
+ while (done_list)
+ {
+ svn_ra_serf__list_t *next_done = done_list->next;
+
+ report->num_active_propfinds--;
+
+ if (report->active_dir_propfinds)
+ {
+ svn_ra_serf__list_t *cur, *prev;
+
+ prev = NULL;
+ cur = report->active_dir_propfinds;
+
+ while (cur)
+ {
+ report_dir_t *item = cur->data;
+
+ if (item->propfind_handler == done_list->data)
+ {
+ break;
+ }
+
+ prev = cur;
+ cur = cur->next;
+ }
+ SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */
+
+ /* If we found a match, set the new props and remove this
+ * propchange from our list.
+ */
+ if (cur)
+ {
+ report_dir_t *cur_dir = cur->data;
+
+ if (!prev)
+ {
+ report->active_dir_propfinds = cur->next;
+ }
+ else
+ {
+ prev->next = cur->next;
+ }
+
+ /* See if this directory (and perhaps even parents of that)
+ can be closed now.
+
+ NOTE: This could delete cur_dir->pool, from which is
+ allocated the list item in report->active_dir_propfinds.
+ */
+ SVN_ERR(maybe_close_dir_chain(cur_dir));
+ }
+ }
+
+ done_list = next_done;
+ }
+ report->done_dir_propfinds = NULL;
+
+ /* If the parser is paused, and the number of active requests has
+ dropped far enough, then resume parsing. */
+ if (parser_ctx->paused
+ && (report->num_active_fetches + report->num_active_propfinds
+ < REQUEST_COUNT_TO_RESUME))
+ parser_ctx->paused = FALSE;
+
+ /* If we have not paused the parser and it looks like data MAY be
+ present (we can't know for sure because of the private structure),
+ then go process the pending content. */
+ if (!parser_ctx->paused && parser_ctx->pending != NULL)
+ SVN_ERR(svn_ra_serf__process_pending(parser_ctx,
+ &report->report_received,
+ iterpool_inner));
+
+ /* Debugging purposes only! */
+ for (i = 0; i < sess->num_conns; i++)
+ {
+ serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
+ }
+ }
+
+ /* If we got a complete report, close the edit. Otherwise, abort it. */
+ if (report->report_completed)
+ {
+ /* Ensure that we opened and closed our root dir and that we closed
+ * all of our children. */
+ if (!report->closed_root && report->root_dir != NULL)
+ {
+ SVN_ERR(close_all_dirs(report->root_dir));
+ }
+
+ err = report->update_editor->close_edit(report->update_baton, iterpool);
+ }
+ else
+ {
+ /* Tell the editor that something failed */
+ err = report->update_editor->abort_edit(report->update_baton, iterpool);
+
+ err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
+ _("Missing update-report close tag"));
+ }
+
+ svn_pool_destroy(iterpool);
+ return svn_error_trace(err);
+}
+
+
+static svn_error_t *
+abort_report(void *report_baton,
+ apr_pool_t *pool)
+{
+#if 0
+ report_context_t *report = report_baton;
+#endif
+
+ /* Should we perform some cleanup here? */
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra_reporter3_t ra_serf_reporter = {
+ set_path,
+ delete_path,
+ link_path,
+ finish_report,
+ abort_report
+};
+
+
+/** RA function implementations and body */
+
+static svn_error_t *
+make_update_reporter(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *src_path,
+ const char *dest_path,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ svn_boolean_t send_copyfrom_args,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ report_context_t *report;
+ const svn_delta_editor_t *filter_editor;
+ void *filter_baton;
+ svn_boolean_t has_target = *update_target != '\0';
+ svn_boolean_t server_supports_depth;
+ svn_ra_serf__session_t *sess = ra_session->priv;
+ svn_stringbuf_t *buf = NULL;
+ svn_boolean_t use_bulk_updates;
+
+ SVN_ERR(svn_ra_serf__has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, scratch_pool));
+ /* We can skip the depth filtering when the user requested
+ depth_files or depth_infinity because the server will
+ transmit the right stuff anyway. */
+ if ((depth != svn_depth_files)
+ && (depth != svn_depth_infinity)
+ && ! server_supports_depth)
+ {
+ SVN_ERR(svn_delta_depth_filter_editor(&filter_editor,
+ &filter_baton,
+ update_editor,
+ update_baton,
+ depth, has_target,
+ sess->pool));
+ update_editor = filter_editor;
+ update_baton = filter_baton;
+ }
+
+ report = apr_pcalloc(result_pool, sizeof(*report));
+ report->pool = result_pool;
+ report->sess = sess;
+ report->conn = report->sess->conns[0];
+ report->target_rev = revision;
+ report->ignore_ancestry = ignore_ancestry;
+ report->send_copyfrom_args = send_copyfrom_args;
+ report->text_deltas = text_deltas;
+ report->lock_path_tokens = apr_hash_make(report->pool);
+ report->switched_paths = apr_hash_make(report->pool);
+
+ report->source = src_path;
+ report->destination = dest_path;
+ report->update_target = update_target;
+
+ report->update_editor = update_editor;
+ report->update_baton = update_baton;
+ report->done = FALSE;
+
+ *reporter = &ra_serf_reporter;
+ *report_baton = report;
+
+ SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ report->pool, scratch_pool));
+
+ if (sess->bulk_updates == svn_tristate_true)
+ {
+ /* User would like to use bulk updates. */
+ use_bulk_updates = TRUE;
+ }
+ else if (sess->bulk_updates == svn_tristate_false)
+ {
+ /* User doesn't want bulk updates. */
+ use_bulk_updates = FALSE;
+ }
+ else
+ {
+ /* User doesn't have any preferences on bulk updates. Decide on server
+ preferences and capabilities. */
+ if (sess->server_allows_bulk)
+ {
+ if (apr_strnatcasecmp(sess->server_allows_bulk, "off") == 0)
+ {
+ /* Server doesn't want bulk updates */
+ use_bulk_updates = FALSE;
+ }
+ else if (apr_strnatcasecmp(sess->server_allows_bulk, "prefer") == 0)
+ {
+ /* Server prefers bulk updates, and we respect that */
+ use_bulk_updates = TRUE;
+ }
+ else
+ {
+ /* Server allows bulk updates, but doesn't dictate its use. Do
+ whatever is the default. */
+ use_bulk_updates = FALSE;
+ }
+ }
+ else
+ {
+ /* Pre-1.8 server didn't send the bulk_updates header. Check if server
+ supports inlining properties in update editor report. */
+ if (sess->supports_inline_props)
+ {
+ /* Inline props supported: do not use bulk updates. */
+ use_bulk_updates = FALSE;
+ }
+ else
+ {
+ /* Inline props are not supported: use bulk updates to avoid
+ * PROPFINDs for every added node. */
+ use_bulk_updates = TRUE;
+ }
+ }
+ }
+
+ if (use_bulk_updates)
+ {
+ svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
+ "S:update-report",
+ "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true",
+ NULL);
+ }
+ else
+ {
+ svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal,
+ "S:update-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+ /* Subversion 1.8+ servers can be told to send properties for newly
+ added items inline even when doing a skelta response. */
+ make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool);
+ }
+
+ make_simple_xml_tag(&buf, "S:src-path", report->source, scratch_pool);
+
+ if (SVN_IS_VALID_REVNUM(report->target_rev))
+ {
+ make_simple_xml_tag(&buf, "S:target-revision",
+ apr_ltoa(scratch_pool, report->target_rev),
+ scratch_pool);
+ }
+
+ if (report->destination && *report->destination)
+ {
+ make_simple_xml_tag(&buf, "S:dst-path", report->destination,
+ scratch_pool);
+ }
+
+ if (report->update_target && *report->update_target)
+ {
+ make_simple_xml_tag(&buf, "S:update-target", report->update_target,
+ scratch_pool);
+ }
+
+ if (report->ignore_ancestry)
+ {
+ make_simple_xml_tag(&buf, "S:ignore-ancestry", "yes", scratch_pool);
+ }
+
+ if (report->send_copyfrom_args)
+ {
+ make_simple_xml_tag(&buf, "S:send-copyfrom-args", "yes", scratch_pool);
+ }
+
+ /* Old servers know "recursive" but not "depth"; help them DTRT. */
+ if (depth == svn_depth_files || depth == svn_depth_empty)
+ {
+ make_simple_xml_tag(&buf, "S:recursive", "no", scratch_pool);
+ }
+
+ /* When in 'send-all' mode, mod_dav_svn will assume that it should
+ calculate and transmit real text-deltas (instead of empty windows
+ that merely indicate "text is changed") unless it finds this
+ element.
+
+ NOTE: Do NOT count on servers actually obeying this, as some exist
+ which obey send-all, but do not check for this directive at all!
+
+ NOTE 2: When not in 'send-all' mode, mod_dav_svn can still be configured to
+ override our request and send text-deltas. */
+ if (! text_deltas)
+ {
+ make_simple_xml_tag(&buf, "S:text-deltas", "no", scratch_pool);
+ }
+
+ make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool);
+
+ SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len,
+ NULL, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__do_update(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_update_to,
+ const char *update_target,
+ svn_depth_t depth,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
+ revision_to_update_to,
+ session->session_url.path, NULL, update_target,
+ depth, ignore_ancestry, TRUE /* text_deltas */,
+ send_copyfrom_args,
+ update_editor, update_baton,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__do_diff(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision,
+ const char *diff_target,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t text_deltas,
+ const char *versus_url,
+ const svn_delta_editor_t *diff_editor,
+ void *diff_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
+ revision,
+ session->session_url.path, versus_url, diff_target,
+ depth, ignore_ancestry, text_deltas, FALSE,
+ diff_editor, diff_baton,
+ pool, scratch_pool));
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__do_status(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ const char *status_target,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ const svn_delta_editor_t *status_editor,
+ void *status_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ SVN_ERR(make_update_reporter(ra_session, reporter, report_baton,
+ revision,
+ session->session_url.path, NULL, status_target,
+ depth, FALSE, FALSE, FALSE,
+ status_editor, status_baton,
+ pool, scratch_pool));
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__do_switch(svn_ra_session_t *ra_session,
+ const svn_ra_reporter3_t **reporter,
+ void **report_baton,
+ svn_revnum_t revision_to_switch_to,
+ const char *switch_target,
+ svn_depth_t depth,
+ const char *switch_url,
+ svn_boolean_t send_copyfrom_args,
+ svn_boolean_t ignore_ancestry,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ return make_update_reporter(ra_session, reporter, report_baton,
+ revision_to_switch_to,
+ session->session_url.path,
+ switch_url, switch_target,
+ depth,
+ ignore_ancestry,
+ TRUE /* text_deltas */,
+ send_copyfrom_args,
+ switch_editor, switch_baton,
+ result_pool, scratch_pool);
+}
+
+/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
+ * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
+ * present in PROPS.
+ *
+ * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
+ *
+ * Performs all temporary allocations in POOL.
+ */
+static svn_error_t *
+try_get_wc_contents(svn_boolean_t *found_p,
+ svn_ra_serf__session_t *session,
+ apr_hash_t *props,
+ svn_stream_t *dst_stream,
+ apr_pool_t *pool)
+{
+ apr_hash_t *svn_props;
+ const char *sha1_checksum_prop;
+ svn_checksum_t *checksum;
+ svn_stream_t *wc_stream;
+ svn_error_t *err;
+
+ /* No contents found by default. */
+ *found_p = FALSE;
+
+ if (!session->wc_callbacks->get_wc_contents)
+ {
+ /* No callback, nothing to do. */
+ return SVN_NO_ERROR;
+ }
+
+
+ svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
+ if (!svn_props)
+ {
+ /* No properties -- therefore no checksum property -- in response. */
+ return SVN_NO_ERROR;
+ }
+
+ sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum");
+ if (sha1_checksum_prop == NULL)
+ {
+ /* No checksum property in response. */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
+ sha1_checksum_prop, pool));
+
+ err = session->wc_callbacks->get_wc_contents(
+ session->wc_callback_baton, &wc_stream, checksum, pool);
+
+ if (err)
+ {
+ svn_error_clear(err);
+
+ /* Ignore errors for now. */
+ return SVN_NO_ERROR;
+ }
+
+ if (wc_stream)
+ {
+ SVN_ERR(svn_stream_copy3(wc_stream,
+ svn_stream_disown(dst_stream, pool),
+ NULL, NULL, pool));
+ *found_p = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_file(svn_ra_session_t *ra_session,
+ const char *path,
+ svn_revnum_t revision,
+ svn_stream_t *stream,
+ svn_revnum_t *fetched_rev,
+ apr_hash_t **props,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__connection_t *conn;
+ const char *fetch_url;
+ apr_hash_t *fetch_props;
+ svn_node_kind_t res_kind;
+ const svn_ra_serf__dav_props_t *which_props;
+
+ /* What connection should we go on? */
+ conn = session->conns[session->cur_conn];
+
+ /* Fetch properties. */
+
+ fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
+
+ /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
+ *
+ * Otherwise, we need to get the baseline version for this particular
+ * revision and then fetch that file.
+ */
+ if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
+ {
+ SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
+ session, conn,
+ fetch_url, revision,
+ pool, pool));
+ revision = SVN_INVALID_REVNUM;
+ }
+ /* REVISION is always SVN_INVALID_REVNUM */
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
+
+ if (props)
+ {
+ which_props = all_props;
+ }
+ else if (stream && session->wc_callbacks->get_wc_contents)
+ {
+ which_props = type_and_checksum_props;
+ }
+ else
+ {
+ which_props = check_path_props;
+ }
+
+ SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url,
+ SVN_INVALID_REVNUM,
+ which_props,
+ pool, pool));
+
+ /* Verify that resource type is not collection. */
+ SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props));
+ if (res_kind != svn_node_file)
+ {
+ return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
+ _("Can't get text contents of a directory"));
+ }
+
+ /* TODO Filter out all of our props into a usable format. */
+ if (props)
+ {
+ /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
+ ### put them into POOL, so we're okay. */
+ SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props,
+ pool, pool));
+ }
+
+ if (stream)
+ {
+ svn_boolean_t found;
+ SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool));
+
+ /* No contents found in the WC, let's fetch from server. */
+ if (!found)
+ {
+ report_fetch_t *stream_ctx;
+ svn_ra_serf__handler_t *handler;
+
+ /* Create the fetch context. */
+ stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
+ stream_ctx->target_stream = stream;
+ stream_ctx->sess = session;
+ stream_ctx->conn = conn;
+ stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info));
+ stream_ctx->info->name = fetch_url;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->handler_pool = pool;
+ handler->method = "GET";
+ handler->path = fetch_url;
+ handler->conn = conn;
+ handler->session = session;
+
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = headers_fetch;
+ handler->header_delegate_baton = stream_ctx;
+
+ handler->response_handler = handle_stream;
+ handler->response_baton = stream_ctx;
+
+ handler->response_error = cancel_fetch;
+ handler->response_error_baton = stream_ctx;
+
+ stream_ctx->handler = handler;
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c
new file mode 100644
index 0000000..c7a1716
--- /dev/null
+++ b/subversion/libsvn_ra_serf/util.c
@@ -0,0 +1,2614 @@
+/*
+ * util.c : serf utility routines for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <assert.h>
+
+#define APR_WANT_STRFUNC
+#include <apr.h>
+#include <apr_want.h>
+#include <apr_fnmatch.h>
+
+#include <serf.h>
+#include <serf_bucket_types.h>
+
+#include <expat.h>
+
+#include "svn_hash.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+#include "svn_props.h"
+#include "svn_dirent_uri.h"
+
+#include "../libsvn_ra/ra_loader.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.h"
+#include "private/svn_subr_private.h"
+
+#include "ra_serf.h"
+
+
+/* Fix for older expat 1.95.x's that do not define
+ * XML_STATUS_OK/XML_STATUS_ERROR
+ */
+#ifndef XML_STATUS_OK
+#define XML_STATUS_OK 1
+#define XML_STATUS_ERROR 0
+#endif
+
+#ifndef XML_VERSION_AT_LEAST
+#define XML_VERSION_AT_LEAST(major,minor,patch) \
+(((major) < XML_MAJOR_VERSION) \
+ || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \
+ || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
+ (patch) <= XML_MICRO_VERSION))
+#endif /* APR_VERSION_AT_LEAST */
+
+#if XML_VERSION_AT_LEAST(1, 95, 8)
+#define EXPAT_HAS_STOPPARSER
+#endif
+
+/* Read/write chunks of this size into the spillbuf. */
+#define PARSE_CHUNK_SIZE 8000
+
+/* We will store one megabyte in memory, before switching to store content
+ into a temporary file. */
+#define SPILL_SIZE 1000000
+
+
+/* This structure records pending data for the parser in memory blocks,
+ and possibly into a temporary file if "too much" content arrives. */
+struct svn_ra_serf__pending_t {
+ /* The spillbuf where we record the pending data. */
+ svn_spillbuf_t *buf;
+
+ /* This flag is set when the network has reached EOF. The PENDING
+ processing can then properly detect when parsing has completed. */
+ svn_boolean_t network_eof;
+};
+
+#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \
+ && svn_spillbuf__get_size((p)->buf) != 0)
+
+
+struct expat_ctx_t {
+ svn_ra_serf__xml_context_t *xmlctx;
+ XML_Parser parser;
+ svn_ra_serf__handler_t *handler;
+
+ svn_error_t *inner_error;
+
+ /* Do not use this pool for allocation. It is merely recorded for running
+ the cleanup handler. */
+ apr_pool_t *cleanup_pool;
+};
+
+
+static const apr_uint32_t serf_failure_map[][2] =
+{
+ { SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID },
+ { SERF_SSL_CERT_EXPIRED, SVN_AUTH_SSL_EXPIRED },
+ { SERF_SSL_CERT_SELF_SIGNED, SVN_AUTH_SSL_UNKNOWNCA },
+ { SERF_SSL_CERT_UNKNOWNCA, SVN_AUTH_SSL_UNKNOWNCA }
+};
+
+/* Return a Subversion failure mask based on FAILURES, a serf SSL
+ failure mask. If anything in FAILURES is not directly mappable to
+ Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */
+static apr_uint32_t
+ssl_convert_serf_failures(int failures)
+{
+ apr_uint32_t svn_failures = 0;
+ apr_size_t i;
+
+ for (i = 0; i < sizeof(serf_failure_map) / (2 * sizeof(apr_uint32_t)); ++i)
+ {
+ if (failures & serf_failure_map[i][0])
+ {
+ svn_failures |= serf_failure_map[i][1];
+ failures &= ~serf_failure_map[i][0];
+ }
+ }
+
+ /* Map any remaining failure bits to our OTHER bit. */
+ if (failures)
+ {
+ svn_failures |= SVN_AUTH_SSL_OTHER;
+ }
+
+ return svn_failures;
+}
+
+
+static apr_status_t
+save_error(svn_ra_serf__session_t *session,
+ svn_error_t *err)
+{
+ if (err || session->pending_error)
+ {
+ session->pending_error = svn_error_compose_create(
+ session->pending_error,
+ err);
+ return session->pending_error->apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+
+/* Construct the realmstring, e.g. https://svn.collab.net:443. */
+static const char *
+construct_realm(svn_ra_serf__session_t *session,
+ apr_pool_t *pool)
+{
+ const char *realm;
+ apr_port_t port;
+
+ if (session->session_url.port_str)
+ {
+ port = session->session_url.port;
+ }
+ else
+ {
+ port = apr_uri_port_of_scheme(session->session_url.scheme);
+ }
+
+ realm = apr_psprintf(pool, "%s://%s:%d",
+ session->session_url.scheme,
+ session->session_url.hostname,
+ port);
+
+ return realm;
+}
+
+/* Convert a hash table containing the fields (as documented in X.509) of an
+ organisation to a string ORG, allocated in POOL. ORG is as returned by
+ serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
+static char *
+convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
+{
+ return apr_psprintf(pool, "%s, %s, %s, %s, %s (%s)",
+ (char*)svn_hash_gets(org, "OU"),
+ (char*)svn_hash_gets(org, "O"),
+ (char*)svn_hash_gets(org, "L"),
+ (char*)svn_hash_gets(org, "ST"),
+ (char*)svn_hash_gets(org, "C"),
+ (char*)svn_hash_gets(org, "E"));
+}
+
+/* This function is called on receiving a ssl certificate of a server when
+ opening a https connection. It allows Subversion to override the initial
+ validation done by serf.
+ Serf provides us the @a baton as provided in the call to
+ serf_ssl_server_cert_callback_set. The result of serf's initial validation
+ of the certificate @a CERT is returned as a bitmask in FAILURES. */
+static svn_error_t *
+ssl_server_cert(void *baton, int failures,
+ const serf_ssl_certificate_t *cert,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__connection_t *conn = baton;
+ svn_auth_ssl_server_cert_info_t cert_info;
+ svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
+ svn_auth_iterstate_t *state;
+ const char *realmstring;
+ apr_uint32_t svn_failures;
+ apr_hash_t *issuer, *subject, *serf_cert;
+ apr_array_header_t *san;
+ void *creds;
+ int found_matching_hostname = 0;
+
+ /* Implicitly approve any non-server certs. */
+ if (serf_ssl_cert_depth(cert) > 0)
+ {
+ if (failures)
+ conn->server_cert_failures |= ssl_convert_serf_failures(failures);
+ return APR_SUCCESS;
+ }
+
+ /* Extract the info from the certificate */
+ subject = serf_ssl_cert_subject(cert, scratch_pool);
+ issuer = serf_ssl_cert_issuer(cert, scratch_pool);
+ serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
+
+ cert_info.hostname = svn_hash_gets(subject, "CN");
+ san = svn_hash_gets(serf_cert, "subjectAltName");
+ cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
+ if (! cert_info.fingerprint)
+ cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
+ cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
+ if (! cert_info.valid_from)
+ cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
+ cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
+ if (! cert_info.valid_until)
+ cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
+ cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
+ cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
+
+ svn_failures = (ssl_convert_serf_failures(failures)
+ | conn->server_cert_failures);
+
+ /* Try to find matching server name via subjectAltName first... */
+ if (san) {
+ int i;
+ for (i = 0; i < san->nelts; i++) {
+ char *s = APR_ARRAY_IDX(san, i, char*);
+ if (apr_fnmatch(s, conn->session->session_url.hostname,
+ APR_FNM_PERIOD) == APR_SUCCESS) {
+ found_matching_hostname = 1;
+ cert_info.hostname = s;
+ break;
+ }
+ }
+ }
+
+ /* Match server certificate CN with the hostname of the server */
+ if (!found_matching_hostname && cert_info.hostname)
+ {
+ if (apr_fnmatch(cert_info.hostname, conn->session->session_url.hostname,
+ APR_FNM_PERIOD) == APR_FNM_NOMATCH)
+ {
+ svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
+ }
+ }
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
+ &svn_failures);
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
+ &cert_info);
+
+ realmstring = construct_realm(conn->session, conn->session->pool);
+
+ SVN_ERR(svn_auth_first_credentials(&creds, &state,
+ SVN_AUTH_CRED_SSL_SERVER_TRUST,
+ realmstring,
+ conn->session->wc_callbacks->auth_baton,
+ scratch_pool));
+ if (creds)
+ {
+ server_creds = creds;
+ SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
+ }
+
+ svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
+ SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
+
+ if (!server_creds)
+ return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
+static apr_status_t
+ssl_server_cert_cb(void *baton, int failures,
+ const serf_ssl_certificate_t *cert)
+{
+ svn_ra_serf__connection_t *conn = baton;
+ svn_ra_serf__session_t *session = conn->session;
+ apr_pool_t *subpool;
+ svn_error_t *err;
+
+ subpool = svn_pool_create(session->pool);
+ err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
+ svn_pool_destroy(subpool);
+
+ return save_error(session, err);
+}
+
+static svn_error_t *
+load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *files = svn_cstring_split(authorities, ";",
+ TRUE /* chop_whitespace */,
+ pool);
+ int i;
+
+ for (i = 0; i < files->nelts; ++i)
+ {
+ const char *file = APR_ARRAY_IDX(files, i, const char *);
+ serf_ssl_certificate_t *ca_cert;
+ apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
+
+ if (status == APR_SUCCESS)
+ status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
+
+ if (status != APR_SUCCESS)
+ {
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid config: unable to load certificate file '%s'"),
+ svn_dirent_local_style(file, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+conn_setup(apr_socket_t *sock,
+ serf_bucket_t **read_bkt,
+ serf_bucket_t **write_bkt,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = baton;
+
+ *read_bkt = serf_context_bucket_socket_create(conn->session->context,
+ sock, conn->bkt_alloc);
+
+ if (conn->session->using_ssl)
+ {
+ /* input stream */
+ *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
+ conn->bkt_alloc);
+ if (!conn->ssl_context)
+ {
+ conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
+
+#if SERF_VERSION_AT_LEAST(1,0,0)
+ serf_ssl_set_hostname(conn->ssl_context,
+ conn->session->session_url.hostname);
+#endif
+
+ serf_ssl_client_cert_provider_set(conn->ssl_context,
+ svn_ra_serf__handle_client_cert,
+ conn, conn->session->pool);
+ serf_ssl_client_cert_password_set(conn->ssl_context,
+ svn_ra_serf__handle_client_cert_pw,
+ conn, conn->session->pool);
+ serf_ssl_server_cert_callback_set(conn->ssl_context,
+ ssl_server_cert_cb,
+ conn);
+
+ /* See if the user wants us to trust "default" openssl CAs. */
+ if (conn->session->trust_default_ca)
+ {
+ serf_ssl_use_default_certificates(conn->ssl_context);
+ }
+ /* Are there custom CAs to load? */
+ if (conn->session->ssl_authorities)
+ {
+ SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
+ conn->session->pool));
+ }
+ }
+
+ if (write_bkt)
+ {
+ /* output stream */
+ *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
+ conn->ssl_context,
+ conn->bkt_alloc);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* svn_ra_serf__conn_setup is a callback for serf. This function
+ creates a read bucket and will wrap the write bucket if SSL
+ is needed. */
+apr_status_t
+svn_ra_serf__conn_setup(apr_socket_t *sock,
+ serf_bucket_t **read_bkt,
+ serf_bucket_t **write_bkt,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = baton;
+ svn_ra_serf__session_t *session = conn->session;
+ svn_error_t *err;
+
+ err = svn_error_trace(conn_setup(sock,
+ read_bkt,
+ write_bkt,
+ baton,
+ pool));
+ return save_error(session, err);
+}
+
+
+/* Our default serf response acceptor. */
+static serf_bucket_t *
+accept_response(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *c;
+ serf_bucket_alloc_t *bkt_alloc;
+
+ bkt_alloc = serf_request_get_alloc(request);
+ c = serf_bucket_barrier_create(stream, bkt_alloc);
+
+ return serf_bucket_response_create(c, bkt_alloc);
+}
+
+
+/* Custom response acceptor for HEAD requests. */
+static serf_bucket_t *
+accept_head(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *response;
+
+ response = accept_response(request, stream, acceptor_baton, pool);
+
+ /* We know we shouldn't get a response body. */
+ serf_bucket_response_set_head(response);
+
+ return response;
+}
+
+static svn_error_t *
+connection_closed(svn_ra_serf__connection_t *conn,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ if (why)
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (conn->session->using_ssl)
+ conn->ssl_context = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+void
+svn_ra_serf__conn_closed(serf_connection_t *conn,
+ void *closed_baton,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *ra_conn = closed_baton;
+ svn_error_t *err;
+
+ err = svn_error_trace(connection_closed(ra_conn, why, pool));
+
+ (void) save_error(ra_conn->session, err);
+}
+
+
+/* Implementation of svn_ra_serf__handle_client_cert */
+static svn_error_t *
+handle_client_cert(void *data,
+ const char **cert_path,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ const char *realm;
+ void *creds;
+
+ *cert_path = NULL;
+
+ realm = construct_realm(session, session->pool);
+
+ if (!conn->ssl_client_auth_state)
+ {
+ SVN_ERR(svn_auth_first_credentials(&creds,
+ &conn->ssl_client_auth_state,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT,
+ realm,
+ session->wc_callbacks->auth_baton,
+ pool));
+ }
+ else
+ {
+ SVN_ERR(svn_auth_next_credentials(&creds,
+ conn->ssl_client_auth_state,
+ session->pool));
+ }
+
+ if (creds)
+ {
+ svn_auth_cred_ssl_client_cert_t *client_creds;
+ client_creds = creds;
+ *cert_path = client_creds->cert_file;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements serf_ssl_need_client_cert_t for handle_client_cert */
+apr_status_t svn_ra_serf__handle_client_cert(void *data,
+ const char **cert_path)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ svn_error_t *err;
+
+ err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
+
+ return save_error(session, err);
+}
+
+/* Implementation for svn_ra_serf__handle_client_cert_pw */
+static svn_error_t *
+handle_client_cert_pw(void *data,
+ const char *cert_path,
+ const char **password,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ void *creds;
+
+ *password = NULL;
+
+ if (!conn->ssl_client_pw_auth_state)
+ {
+ SVN_ERR(svn_auth_first_credentials(&creds,
+ &conn->ssl_client_pw_auth_state,
+ SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
+ cert_path,
+ session->wc_callbacks->auth_baton,
+ pool));
+ }
+ else
+ {
+ SVN_ERR(svn_auth_next_credentials(&creds,
+ conn->ssl_client_pw_auth_state,
+ pool));
+ }
+
+ if (creds)
+ {
+ svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
+ pw_creds = creds;
+ *password = pw_creds->password;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
+apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
+ const char *cert_path,
+ const char **password)
+{
+ svn_ra_serf__connection_t *conn = data;
+ svn_ra_serf__session_t *session = conn->session;
+ svn_error_t *err;
+
+ err = svn_error_trace(handle_client_cert_pw(data,
+ cert_path,
+ password,
+ session->pool));
+
+ return save_error(session, err);
+}
+
+
+/*
+ * Given a REQUEST on connection CONN, construct a request bucket for it,
+ * returning the bucket in *REQ_BKT.
+ *
+ * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
+ * corresponds to the new request.
+ *
+ * The request will be METHOD at URL.
+ *
+ * If BODY_BKT is not-NULL, it will be sent as the request body.
+ *
+ * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
+ *
+ * REQUEST_POOL should live for the duration of the request. Serf will
+ * construct this and provide it to the request_setup callback, so we
+ * should just use that one.
+ */
+static svn_error_t *
+setup_serf_req(serf_request_t *request,
+ serf_bucket_t **req_bkt,
+ serf_bucket_t **hdrs_bkt,
+ svn_ra_serf__session_t *session,
+ const char *method, const char *url,
+ serf_bucket_t *body_bkt, const char *content_type,
+ const char *accept_encoding,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
+
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ svn_spillbuf_t *buf;
+
+ if (session->http10 && body_bkt != NULL)
+ {
+ /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
+ it speaks HTTP/1.1 (and thus, chunked requests), or because the
+ server actually responded as only supporting HTTP/1.0.
+
+ We'll take the existing body_bkt, spool it into a spillbuf, and
+ then wrap a bucket around that spillbuf. The spillbuf will give
+ us the Content-Length value. */
+ SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
+ request_pool,
+ scratch_pool));
+ /* Destroy original bucket since it content is already copied
+ to spillbuf. */
+ serf_bucket_destroy(body_bkt);
+
+ body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
+ request_pool,
+ scratch_pool);
+ }
+#endif
+
+ /* Create a request bucket. Note that this sucker is kind enough to
+ add a "Host" header for us. */
+ *req_bkt = serf_request_bucket_request_create(request, method, url,
+ body_bkt, allocator);
+
+ /* Set the Content-Length value. This will also trigger an HTTP/1.0
+ request (rather than the default chunked request). */
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ if (session->http10)
+ {
+ if (body_bkt == NULL)
+ serf_bucket_request_set_CL(*req_bkt, 0);
+ else
+ serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
+ }
+#endif
+
+ *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
+
+ /* We use serf_bucket_headers_setn() because the USERAGENT has a
+ lifetime longer than this bucket. Thus, there is no need to copy
+ the header values. */
+ serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
+
+ if (content_type)
+ {
+ serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
+ }
+
+#if SERF_VERSION_AT_LEAST(1, 1, 0)
+ if (session->http10)
+ serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
+#endif
+
+ if (accept_encoding)
+ {
+ serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
+ }
+
+ /* These headers need to be sent with every request; see issue #3255
+ ("mod_dav_svn does not pass client capabilities to start-commit
+ hooks") for why. */
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
+ serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__context_run_wait(svn_boolean_t *done,
+ svn_ra_serf__session_t *sess,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+ apr_interval_time_t waittime_left = sess->timeout;
+
+ assert(sess->pending_error == SVN_NO_ERROR);
+
+ iterpool = svn_pool_create(scratch_pool);
+ while (!*done)
+ {
+ apr_status_t status;
+ svn_error_t *err;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ if (sess->cancel_func)
+ SVN_ERR((*sess->cancel_func)(sess->cancel_baton));
+
+ status = serf_context_run(sess->context,
+ SVN_RA_SERF__CONTEXT_RUN_DURATION,
+ iterpool);
+
+ err = sess->pending_error;
+ sess->pending_error = SVN_NO_ERROR;
+
+ /* If the context duration timeout is up, we'll subtract that
+ duration from the total time alloted for such things. If
+ there's no time left, we fail with a message indicating that
+ the connection timed out. */
+ if (APR_STATUS_IS_TIMEUP(status))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ status = 0;
+
+ if (sess->timeout)
+ {
+ if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
+ {
+ waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
+ _("Connection timed out"));
+ }
+ }
+ }
+ else
+ {
+ waittime_left = sess->timeout;
+ }
+
+ SVN_ERR(err);
+ if (status)
+ {
+ if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
+ {
+ /* apr can't translate subversion errors to text */
+ SVN_ERR_W(svn_error_create(status, NULL, NULL),
+ _("Error running context"));
+ }
+
+ return svn_ra_serf__wrap_err(status, _("Error running context"));
+ }
+
+ /* Debugging purposes only! */
+ for (i = 0; i < sess->num_conns; i++)
+ {
+ serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ /* Create a serf request based on HANDLER. */
+ svn_ra_serf__request_create(handler);
+
+ /* Wait until the response logic marks its DONE status. */
+ err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
+ scratch_pool);
+ if (handler->server_error)
+ {
+ err = svn_error_compose_create(err, handler->server_error->error);
+ handler->server_error = NULL;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+/*
+ * Expat callback invoked on a start element tag for an error response.
+ */
+static svn_error_t *
+start_error(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (!ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "error") == 0)
+ {
+ ctx->in_error = TRUE;
+ }
+ else if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
+ {
+ const char *err_code;
+
+ err_code = svn_xml_get_attr_value("errcode", attrs);
+ if (err_code)
+ {
+ apr_int64_t val;
+
+ SVN_ERR(svn_cstring_atoi64(&val, err_code));
+ ctx->error->apr_err = (apr_status_t)val;
+ }
+
+ /* If there's no error code provided, or if the provided code is
+ 0 (which can happen sometimes depending on how the error is
+ constructed on the server-side), just pick a generic error
+ code to run with. */
+ if (! ctx->error->apr_err)
+ {
+ ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+ }
+
+ /* Start collecting cdata. */
+ svn_stringbuf_setempty(ctx->cdata);
+ ctx->collect_cdata = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on an end element tag for a PROPFIND response.
+ */
+static svn_error_t *
+end_error(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "error") == 0)
+ {
+ ctx->in_error = FALSE;
+ }
+ if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
+ {
+ /* On the server dav_error_response_tag() will add a leading
+ and trailing newline if DEBUG_CR is defined in mod_dav.h,
+ so remove any such characters here. */
+ svn_stringbuf_strip_whitespace(ctx->cdata);
+
+ ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
+ ctx->cdata->len);
+ ctx->collect_cdata = FALSE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on CDATA elements in an error response.
+ *
+ * This callback can be called multiple times.
+ */
+static svn_error_t *
+cdata_error(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->collect_cdata)
+ {
+ svn_stringbuf_appendbytes(ctx->cdata, data, len);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static apr_status_t
+drain_bucket(serf_bucket_t *bucket)
+{
+ /* Read whatever is in the bucket, and just drop it. */
+ while (1)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
+ if (status)
+ return status;
+ }
+}
+
+
+static svn_ra_serf__server_error_t *
+begin_error_parsing(svn_ra_serf__xml_start_element_t start,
+ svn_ra_serf__xml_end_element_t end,
+ svn_ra_serf__xml_cdata_chunk_handler_t cdata,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = apr_pcalloc(result_pool, sizeof(*server_err));
+ server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err->contains_precondition_error = FALSE;
+ server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool);
+ server_err->collect_cdata = FALSE;
+ server_err->parser.pool = server_err->error->pool;
+ server_err->parser.user_data = server_err;
+ server_err->parser.start = start;
+ server_err->parser.end = end;
+ server_err->parser.cdata = cdata;
+ server_err->parser.ignore_errors = TRUE;
+
+ return server_err;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_discard_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+
+ status = drain_bucket(response);
+ if (status)
+ return svn_ra_serf__wrap_err(status, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+apr_status_t
+svn_ra_serf__response_discard_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ return drain_bucket(response);
+}
+
+
+/* Return the value of the RESPONSE's Location header if any, or NULL
+ otherwise. */
+static const char *
+response_get_location(serf_bucket_t *response,
+ const char *base_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_t *headers;
+ const char *location;
+
+ headers = serf_bucket_response_get_headers(response);
+ location = serf_bucket_headers_get(headers, "Location");
+ if (location == NULL)
+ return NULL;
+
+ /* The RFCs say we should have received a full url in LOCATION, but
+ older apache versions and many custom web handlers just return a
+ relative path here...
+
+ And we can't trust anything because it is network data.
+ */
+ if (*location == '/')
+ {
+ apr_uri_t uri;
+ apr_status_t status;
+
+ status = apr_uri_parse(scratch_pool, base_url, &uri);
+
+ if (status != APR_SUCCESS)
+ return NULL;
+
+ /* Replace the path path with what we got */
+ uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
+
+ /* And make APR produce a proper full url for us */
+ location = apr_uri_unparse(scratch_pool, &uri, 0);
+
+ /* Fall through to ensure our canonicalization rules */
+ }
+ else if (!svn_path_is_url(location))
+ {
+ return NULL; /* Any other formats we should support? */
+ }
+
+ return svn_uri_canonicalize(location, result_pool);
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__expect_empty_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ /* This function is just like handle_multistatus_only() except for the
+ XML parsing callbacks. We want to look for the human-readable element. */
+
+ /* We should see this just once, in order to initialize SERVER_ERROR.
+ At that point, the core error processing will take over. If we choose
+ not to parse an error, then we'll never return here (because we
+ change the response handler). */
+ SVN_ERR_ASSERT(handler->server_error == NULL);
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = begin_error_parsing(start_error, end_error, cdata_error,
+ handler->handler_pool);
+
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
+ }
+ else
+ {
+ /* The body was not text/xml, so we don't know what to do with it.
+ Toss anything that arrives. */
+ handler->discard_body = TRUE;
+ }
+
+ /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
+ to call the response handler again. That will start up the XML parsing,
+ or it will be dropped on the floor (per the decision above). */
+ return SVN_NO_ERROR;
+}
+
+
+/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric
+ status code into *STATUS_CODE_OUT. Ignores leading whitespace. */
+static svn_error_t *
+parse_dav_status(int *status_code_out, svn_stringbuf_t *buf,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ const char *token;
+ char *tok_status;
+ svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool);
+
+ svn_stringbuf_strip_whitespace(temp_buf);
+ token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status);
+ if (token)
+ token = apr_strtok(NULL, " \t\r\n", &tok_status);
+ if (!token)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Malformed DAV:status CDATA '%s'"),
+ buf->data);
+ err = svn_cstring_atoi(status_code_out, token);
+ if (err)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
+ _("Malformed DAV:status CDATA '%s'"),
+ buf->data);
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on a start element tag for a 207 response.
+ */
+static svn_error_t *
+start_207(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (!ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "multistatus") == 0)
+ {
+ ctx->in_error = TRUE;
+ }
+ else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
+ {
+ /* Start collecting cdata. */
+ svn_stringbuf_setempty(ctx->cdata);
+ ctx->collect_cdata = TRUE;
+ }
+ else if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "status") == 0)
+ {
+ /* Start collecting cdata. */
+ svn_stringbuf_setempty(ctx->cdata);
+ ctx->collect_cdata = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on an end element tag for a 207 response.
+ */
+static svn_error_t *
+end_207(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__dav_props_t name,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "multistatus") == 0)
+ {
+ ctx->in_error = FALSE;
+ }
+ if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
+ {
+ /* Remove leading newline added by DEBUG_CR on server */
+ svn_stringbuf_strip_whitespace(ctx->cdata);
+
+ ctx->collect_cdata = FALSE;
+ ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
+ ctx->cdata->len);
+ if (ctx->contains_precondition_error)
+ ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
+ else
+ ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+ }
+ else if (ctx->in_error &&
+ strcmp(name.namespace, "DAV:") == 0 &&
+ strcmp(name.name, "status") == 0)
+ {
+ int status_code;
+
+ ctx->collect_cdata = FALSE;
+
+ SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool));
+ if (status_code == 412)
+ ctx->contains_precondition_error = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on CDATA elements in a 207 response.
+ *
+ * This callback can be called multiple times.
+ */
+static svn_error_t *
+cdata_207(svn_ra_serf__xml_parser_t *parser,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t *ctx = parser->user_data;
+
+ if (ctx->collect_cdata)
+ {
+ svn_stringbuf_appendbytes(ctx->cdata, data, len);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_multistatus_only(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+
+ /* This function is just like expect_empty_body() except for the
+ XML parsing callbacks. We are looking for very limited pieces of
+ the multistatus response. */
+
+ /* We should see this just once, in order to initialize SERVER_ERROR.
+ At that point, the core error processing will take over. If we choose
+ not to parse an error, then we'll never return here (because we
+ change the response handler). */
+ SVN_ERR_ASSERT(handler->server_error == NULL);
+
+ {
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = begin_error_parsing(start_207, end_207, cdata_207,
+ handler->handler_pool);
+
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
+ }
+ else
+ {
+ /* The body was not text/xml, so we don't know what to do with it.
+ Toss anything that arrives. */
+ handler->discard_body = TRUE;
+ }
+ }
+
+ /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
+ to call the response handler again. That will start up the XML parsing,
+ or it will be dropped on the floor (per the decision above). */
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to Expat's XML_StartElementHandler */
+static void
+start_xml(void *userData, const char *raw_name, const char **attrs)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+ svn_ra_serf__dav_props_t name;
+ apr_pool_t *scratch_pool;
+ svn_error_t *err;
+
+ if (parser->error)
+ return;
+
+ if (!parser->state)
+ svn_ra_serf__xml_push_state(parser, 0);
+
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool);
+
+ svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
+
+ err = parser->start(parser, name, attrs, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
+}
+
+
+/* Conforms to Expat's XML_EndElementHandler */
+static void
+end_xml(void *userData, const char *raw_name)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+ svn_ra_serf__dav_props_t name;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool;
+
+ if (parser->error)
+ return;
+
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
+
+ err = parser->end(parser, name, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
+}
+
+
+/* Conforms to Expat's XML_CharacterDataHandler */
+static void
+cdata_xml(void *userData, const char *data, int len)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+ svn_error_t *err;
+ apr_pool_t *scratch_pool;
+
+ if (parser->error)
+ return;
+
+ if (!parser->state)
+ svn_ra_serf__xml_push_state(parser, 0);
+
+ /* ### get a real scratch_pool */
+ scratch_pool = parser->state->pool;
+
+ err = parser->cdata(parser, data, len, scratch_pool);
+ if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
+ err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
+
+ parser->error = err;
+}
+
+/* Flip the requisite bits in CTX to indicate that processing of the
+ response is complete, adding the current "done item" to the list of
+ completed items. */
+static void
+add_done_item(svn_ra_serf__xml_parser_t *ctx)
+{
+ /* Make sure we don't add to DONE_LIST twice. */
+ if (!*ctx->done)
+ {
+ *ctx->done = TRUE;
+ if (ctx->done_list)
+ {
+ ctx->done_item->data = ctx->user_data;
+ ctx->done_item->next = *ctx->done_list;
+ *ctx->done_list = ctx->done_item;
+ }
+ }
+}
+
+
+static svn_error_t *
+write_to_pending(svn_ra_serf__xml_parser_t *ctx,
+ const char *data,
+ apr_size_t len,
+ apr_pool_t *scratch_pool)
+{
+ if (ctx->pending == NULL)
+ {
+ ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
+ ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE,
+ SPILL_SIZE,
+ ctx->pool);
+ }
+
+ /* Copy the data into one or more chunks in the spill buffer. */
+ return svn_error_trace(svn_spillbuf__write(ctx->pending->buf,
+ data, len,
+ scratch_pool));
+}
+
+
+static svn_error_t *
+inject_to_parser(svn_ra_serf__xml_parser_t *ctx,
+ const char *data,
+ apr_size_t len,
+ const serf_status_line *sl)
+{
+ int xml_status;
+
+ xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0);
+ if (xml_status == XML_STATUS_ERROR && !ctx->ignore_errors)
+ {
+ if (sl == NULL)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML parsing failed"));
+
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("XML parsing failed: (%d %s)"),
+ sl->code, sl->reason);
+ }
+
+ if (ctx->error && !ctx->ignore_errors)
+ return svn_error_trace(ctx->error);
+
+ return SVN_NO_ERROR;
+}
+
+/* Apr pool cleanup handler to release an XML_Parser in success and error
+ conditions */
+static apr_status_t
+xml_parser_cleanup(void *baton)
+{
+ XML_Parser *xmlp = baton;
+
+ if (*xmlp)
+ {
+ (void) XML_ParserFree(*xmlp);
+ *xmlp = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Limit the amount of pending content to parse at once to < 100KB per
+ iteration. This number is chosen somewhat arbitrarely. Making it lower
+ will have a drastical negative impact on performance, whereas increasing it
+ increases the risk for connection timeouts.
+ */
+#define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5
+
+svn_error_t *
+svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
+ svn_boolean_t *network_eof,
+ apr_pool_t *scratch_pool)
+{
+ svn_boolean_t pending_empty = FALSE;
+ apr_size_t cur_read = 0;
+
+ /* Fast path exit: already paused, nothing to do, or already done. */
+ if (parser->paused || parser->pending == NULL || *parser->done)
+ {
+ *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Parsing the pending conten in the spillbuf will result in many disc i/o
+ operations. This can be so slow that we don't run the network event
+ processing loop often enough, resulting in timed out connections.
+
+ So we limit the amounts of bytes parsed per iteration.
+ */
+ while (cur_read < PENDING_TO_PARSE)
+ {
+ const char *data;
+ apr_size_t len;
+
+ /* Get a block of content, stopping the loop when we run out. */
+ SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf,
+ scratch_pool));
+ if (data)
+ {
+ /* Inject the content into the XML parser. */
+ SVN_ERR(inject_to_parser(parser, data, len, NULL));
+
+ /* If the XML parsing callbacks paused us, then we're done for now. */
+ if (parser->paused)
+ break;
+
+ cur_read += len;
+ }
+ else
+ {
+ /* The buffer is empty. */
+ pending_empty = TRUE;
+ break;
+ }
+ }
+
+ /* If the PENDING structures are empty *and* we consumed all content from
+ the network, then we're completely done with the parsing. */
+ if (pending_empty &&
+ parser->pending->network_eof)
+ {
+ SVN_ERR_ASSERT(parser->xmlp != NULL);
+
+ /* Tell the parser that no more content will be parsed. Ignore the
+ return status. We just don't care. */
+ (void) XML_Parse(parser->xmlp, NULL, 0, 1);
+
+ apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
+ parser->xmlp = NULL;
+ add_done_item(parser);
+ }
+
+ *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
+
+ return SVN_NO_ERROR;
+}
+#undef PENDING_TO_PARSE
+
+
+/* ### this is still broken conceptually. just shifting incrementally... */
+static svn_error_t *
+handle_server_error(serf_request_t *request,
+ serf_bucket_t *response,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__server_error_t server_err = { 0 };
+ serf_bucket_t *hdrs;
+ const char *val;
+ apr_status_t err;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ /* ### we should figure out how to reuse begin_error_parsing */
+
+ server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err.contains_precondition_error = FALSE;
+ server_err.cdata = svn_stringbuf_create_empty(scratch_pool);
+ server_err.collect_cdata = FALSE;
+ server_err.parser.pool = server_err.error->pool;
+ server_err.parser.user_data = &server_err;
+ server_err.parser.start = start_error;
+ server_err.parser.end = end_error;
+ server_err.parser.cdata = cdata_error;
+ server_err.parser.done = &server_err.done;
+ server_err.parser.ignore_errors = TRUE;
+
+ /* We don't care about any errors except for SERVER_ERR.ERROR */
+ svn_error_clear(svn_ra_serf__handle_xml_parser(request,
+ response,
+ &server_err.parser,
+ scratch_pool));
+
+ /* ### checking DONE is silly. the above only parses whatever has
+ ### been received at the network interface. totally wrong. but
+ ### it is what we have for now (maintaining historical code),
+ ### until we fully migrate. */
+ if (server_err.done && server_err.error->apr_err == APR_SUCCESS)
+ {
+ svn_error_clear(server_err.error);
+ server_err.error = SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(server_err.error);
+ }
+
+ /* The only error that we will return is from the XML response body.
+ Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to
+ surface. */
+ err = drain_bucket(response);
+ if (err && !SERF_BUCKET_READ_ERROR(err))
+ return svn_ra_serf__wrap_err(err, NULL);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_xml_parser(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ serf_status_line sl;
+ apr_status_t status;
+ svn_ra_serf__xml_parser_t *ctx = baton;
+ svn_error_t *err;
+
+ /* ### get the HANDLER rather than fetching this. */
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ /* Woo-hoo. Nothing here to see. */
+ if (sl.code == 404 && !ctx->ignore_errors)
+ {
+ err = handle_server_error(request, response, pool);
+
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ add_done_item(ctx);
+
+ return svn_error_trace(err);
+ }
+
+ if (ctx->headers_baton == NULL)
+ ctx->headers_baton = serf_bucket_response_get_headers(response);
+ else if (ctx->headers_baton != serf_bucket_response_get_headers(response))
+ {
+ /* We got a new response to an existing parser...
+ This tells us the connection has restarted and we should continue
+ where we stopped last time.
+ */
+
+ /* Is this a second attempt?? */
+ if (!ctx->skip_size)
+ ctx->skip_size = ctx->read_size;
+
+ ctx->read_size = 0; /* New request, nothing read */
+ }
+
+ if (!ctx->xmlp)
+ {
+ ctx->xmlp = XML_ParserCreate(NULL);
+ apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup,
+ apr_pool_cleanup_null);
+ XML_SetUserData(ctx->xmlp, ctx);
+ XML_SetElementHandler(ctx->xmlp, start_xml, end_xml);
+ if (ctx->cdata)
+ {
+ XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml);
+ }
+ }
+
+ while (1)
+ {
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
+
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ ctx->read_size += len;
+
+ if (ctx->skip_size)
+ {
+ /* Handle restarted requests correctly: Skip what we already read */
+ apr_size_t skip;
+
+ if (ctx->skip_size >= ctx->read_size)
+ {
+ /* Eek. What did the file shrink or something? */
+ if (APR_STATUS_IS_EOF(status))
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ /* Skip on to the next iteration of this loop. */
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ continue;
+ }
+
+ skip = (apr_size_t)(len - (ctx->read_size - ctx->skip_size));
+ data += skip;
+ len -= skip;
+ ctx->skip_size = 0;
+ }
+
+ /* Note: once the callbacks invoked by inject_to_parser() sets the
+ PAUSED flag, then it will not be cleared. write_to_pending() will
+ only save the content. Logic outside of serf_context_run() will
+ clear that flag, as appropriate, along with processing the
+ content that we have placed into the PENDING buffer.
+
+ We want to save arriving content into the PENDING structures if
+ the parser has been paused, or we already have data in there (so
+ the arriving data is appended, rather than injected out of order) */
+ if (ctx->paused || HAS_PENDING_DATA(ctx->pending))
+ {
+ err = write_to_pending(ctx, data, len, pool);
+ }
+ else
+ {
+ err = inject_to_parser(ctx, data, len, &sl);
+ if (err)
+ {
+ /* Should have no errors if IGNORE_ERRORS is set. */
+ SVN_ERR_ASSERT(!ctx->ignore_errors);
+ }
+ }
+ if (err)
+ {
+ SVN_ERR_ASSERT(ctx->xmlp != NULL);
+
+ apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
+ add_done_item(ctx);
+ return svn_error_trace(err);
+ }
+
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ if (ctx->pending != NULL)
+ ctx->pending->network_eof = TRUE;
+
+ /* We just hit the end of the network content. If we have nothing
+ in the PENDING structures, then we're completely done. */
+ if (!HAS_PENDING_DATA(ctx->pending))
+ {
+ SVN_ERR_ASSERT(ctx->xmlp != NULL);
+
+ /* Ignore the return status. We just don't care. */
+ (void) XML_Parse(ctx->xmlp, NULL, 0, 1);
+
+ apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
+ add_done_item(ctx);
+ }
+
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+
+ /* feed me! */
+ }
+ /* not reached */
+}
+
+
+apr_status_t
+svn_ra_serf__credentials_callback(char **username, char **password,
+ serf_request_t *request, void *baton,
+ int code, const char *authn_type,
+ const char *realm,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ svn_ra_serf__session_t *session = handler->session;
+ void *creds;
+ svn_auth_cred_simple_t *simple_creds;
+ svn_error_t *err;
+
+ if (code == 401)
+ {
+ /* Use svn_auth_first_credentials if this is the first time we ask for
+ credentials during this session OR if the last time we asked
+ session->auth_state wasn't set (eg. if the credentials provider was
+ cancelled by the user). */
+ if (!session->auth_state)
+ {
+ err = svn_auth_first_credentials(&creds,
+ &session->auth_state,
+ SVN_AUTH_CRED_SIMPLE,
+ realm,
+ session->wc_callbacks->auth_baton,
+ session->pool);
+ }
+ else
+ {
+ err = svn_auth_next_credentials(&creds,
+ session->auth_state,
+ session->pool);
+ }
+
+ if (err)
+ {
+ (void) save_error(session, err);
+ return err->apr_err;
+ }
+
+ session->auth_attempts++;
+
+ if (!creds || session->auth_attempts > 4)
+ {
+ /* No more credentials. */
+ (void) save_error(session,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("No more credentials or we tried too many "
+ "times.\nAuthentication failed")));
+ return SVN_ERR_AUTHN_FAILED;
+ }
+
+ simple_creds = creds;
+ *username = apr_pstrdup(pool, simple_creds->username);
+ *password = apr_pstrdup(pool, simple_creds->password);
+ }
+ else
+ {
+ *username = apr_pstrdup(pool, session->proxy_username);
+ *password = apr_pstrdup(pool, session->proxy_password);
+
+ session->proxy_auth_attempts++;
+
+ if (!session->proxy_username || session->proxy_auth_attempts > 4)
+ {
+ /* No more credentials. */
+ (void) save_error(session,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("Proxy authentication failed")));
+ return SVN_ERR_AUTHN_FAILED;
+ }
+ }
+
+ handler->conn->last_status_code = code;
+
+ return APR_SUCCESS;
+}
+
+/* Wait for HTTP response status and headers, and invoke HANDLER->
+ response_handler() to carry out operation-specific processing.
+ Afterwards, check for connection close.
+
+ SERF_STATUS allows returning errors to serf without creating a
+ subversion error object.
+ */
+static svn_error_t *
+handle_response(serf_request_t *request,
+ serf_bucket_t *response,
+ svn_ra_serf__handler_t *handler,
+ apr_status_t *serf_status,
+ apr_pool_t *scratch_pool)
+{
+ apr_status_t status;
+ svn_error_t *err;
+
+ /* ### need to verify whether this already gets init'd on every
+ ### successful exit. for an error-exit, it will (properly) be
+ ### ignored by the caller. */
+ *serf_status = APR_SUCCESS;
+
+ if (!response)
+ {
+ /* Uh-oh. Our connection died. */
+ if (handler->response_error)
+ SVN_ERR(handler->response_error(request, response, 0,
+ handler->response_error_baton));
+
+ /* Requeue another request for this handler.
+ ### how do we know if the handler can deal with this?! */
+ svn_ra_serf__request_create(handler);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If we're reading the body, then skip all this preparation. */
+ if (handler->reading_body)
+ goto process_body;
+
+ /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
+ if (handler->sline.version == 0)
+ {
+ serf_status_line sl;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (status != APR_SUCCESS)
+ {
+ /* The response line is not (yet) ready, or some other error. */
+ *serf_status = status;
+ return SVN_NO_ERROR; /* Handled by serf */
+ }
+
+ /* If we got APR_SUCCESS, then we should have Status-Line info. */
+ SVN_ERR_ASSERT(sl.version != 0);
+
+ handler->sline = sl;
+ handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
+
+ /* HTTP/1.1? (or later) */
+ if (sl.version != SERF_HTTP_10)
+ handler->session->http10 = FALSE;
+ }
+
+ /* Keep reading from the network until we've read all the headers. */
+ status = serf_bucket_response_wait_for_headers(response);
+ if (status)
+ {
+ /* The typical "error" will be APR_EAGAIN, meaning that more input
+ from the network is required to complete the reading of the
+ headers. */
+ if (!APR_STATUS_IS_EOF(status))
+ {
+ /* Either the headers are not (yet) complete, or there really
+ was an error. */
+ *serf_status = status;
+ return SVN_NO_ERROR;
+ }
+
+ /* wait_for_headers() will return EOF if there is no body in this
+ response, or if we completely read the body. The latter is not
+ true since we would have set READING_BODY to get the body read,
+ and we would not be back to this code block.
+
+ It can also return EOF if we truly hit EOF while (say) processing
+ the headers. aka Badness. */
+
+ /* Cases where a lack of a response body (via EOF) is okay:
+ * - A HEAD request
+ * - 204/304 response
+ *
+ * Otherwise, if we get an EOF here, something went really wrong: either
+ * the server closed on us early or we're reading too much. Either way,
+ * scream loudly.
+ */
+ if (strcmp(handler->method, "HEAD") != 0
+ && handler->sline.code != 204
+ && handler->sline.code != 304)
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ svn_ra_serf__wrap_err(status, NULL),
+ _("Premature EOF seen from server"
+ " (http status=%d)"),
+ handler->sline.code);
+
+ /* In case anything else arrives... discard it. */
+ handler->discard_body = TRUE;
+
+ return err;
+ }
+ }
+
+ /* ... and set up the header fields in HANDLER. */
+ handler->location = response_get_location(response,
+ handler->session->session_url_str,
+ handler->handler_pool,
+ scratch_pool);
+
+ /* On the last request, we failed authentication. We succeeded this time,
+ so let's save away these credentials. */
+ if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
+ {
+ SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
+ handler->session->pool));
+ handler->session->auth_attempts = 0;
+ handler->session->auth_state = NULL;
+ }
+ handler->conn->last_status_code = handler->sline.code;
+
+ if (handler->sline.code == 405
+ || handler->sline.code == 408
+ || handler->sline.code == 409
+ || handler->sline.code >= 500)
+ {
+ /* 405 Method Not allowed.
+ 408 Request Timeout
+ 409 Conflict: can indicate a hook error.
+ 5xx (Internal) Server error. */
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, "Content-Type");
+ if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
+ {
+ svn_ra_serf__server_error_t *server_err;
+
+ server_err = begin_error_parsing(start_error, end_error, cdata_error,
+ handler->handler_pool);
+ /* Get the parser to set our DONE flag. */
+ server_err->parser.done = &handler->done;
+
+ handler->server_error = server_err;
+ }
+ else
+ {
+ handler->discard_body = TRUE;
+
+ if (!handler->session->pending_error)
+ {
+ apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
+
+ /* 405 == Method Not Allowed (Occurs when trying to lock a working
+ copy path which no longer exists at HEAD in the repository. */
+ if (handler->sline.code == 405
+ && strcmp(handler->method, "LOCK") == 0)
+ apr_err = SVN_ERR_FS_OUT_OF_DATE;
+
+ handler->session->pending_error =
+ svn_error_createf(apr_err, NULL,
+ _("%s request on '%s' failed: %d %s"),
+ handler->method, handler->path,
+ handler->sline.code, handler->sline.reason);
+ }
+ }
+ }
+
+ /* Stop processing the above, on every packet arrival. */
+ handler->reading_body = TRUE;
+
+ process_body:
+
+ /* We've been instructed to ignore the body. Drain whatever is present. */
+ if (handler->discard_body)
+ {
+ *serf_status = drain_bucket(response);
+
+ /* If the handler hasn't set done (which it shouldn't have) and
+ we now have the EOF, go ahead and set it so that we can stop
+ our context loops.
+ */
+ if (!handler->done && APR_STATUS_IS_EOF(*serf_status))
+ handler->done = TRUE;
+
+ return SVN_NO_ERROR;
+ }
+
+ /* If we are supposed to parse the body as a server_error, then do
+ that now. */
+ if (handler->server_error != NULL)
+ {
+ err = svn_ra_serf__handle_xml_parser(request, response,
+ &handler->server_error->parser,
+ scratch_pool);
+
+ /* If we do not receive an error or it is a non-transient error, return
+ immediately.
+
+ APR_EOF will be returned when parsing is complete.
+
+ APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through
+ parsing and the network has no more data right now. If we receive that,
+ clear the error and return - allowing serf to wait for more data.
+ */
+ if (!err || SERF_BUCKET_READ_ERROR(err->apr_err))
+ return svn_error_trace(err);
+
+ if (!APR_STATUS_IS_EOF(err->apr_err))
+ {
+ *serf_status = err->apr_err;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ /* Clear the EOF. We don't need it. */
+ svn_error_clear(err);
+
+ /* If the parsing is done, and we did not extract an error, then
+ simply toss everything, and anything else that might arrive.
+ The higher-level code will need to investigate HANDLER->SLINE,
+ as we have no further information for them. */
+ if (handler->done
+ && handler->server_error->error->apr_err == APR_SUCCESS)
+ {
+ svn_error_clear(handler->server_error->error);
+
+ /* Stop parsing for a server error. */
+ handler->server_error = NULL;
+
+ /* If anything arrives after this, then just discard it. */
+ handler->discard_body = TRUE;
+ }
+
+ *serf_status = APR_EOF;
+ return SVN_NO_ERROR;
+ }
+
+ /* Pass the body along to the registered response handler. */
+ err = handler->response_handler(request, response,
+ handler->response_baton,
+ scratch_pool);
+
+ if (err
+ && (!SERF_BUCKET_READ_ERROR(err->apr_err)
+ || APR_STATUS_IS_ECONNRESET(err->apr_err)
+ || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
+ {
+ /* These errors are special cased in serf
+ ### We hope no handler returns these by accident. */
+ *serf_status = err->apr_err;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+/* Implements serf_response_handler_t for handle_response. Storing
+ errors in handler->session->pending_error if appropriate. */
+static apr_status_t
+handle_response_cb(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__handler_t *handler = baton;
+ svn_error_t *err;
+ apr_status_t inner_status;
+ apr_status_t outer_status;
+
+ err = svn_error_trace(handle_response(request, response,
+ handler, &inner_status,
+ scratch_pool));
+
+ /* Select the right status value to return. */
+ outer_status = save_error(handler->session, err);
+ if (!outer_status)
+ outer_status = inner_status;
+
+ /* Make sure the DONE flag is set properly. */
+ if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
+ handler->done = TRUE;
+
+ return outer_status;
+}
+
+/* Perform basic request setup, with special handling for HEAD requests,
+ and finer-grained callbacks invoked (if non-NULL) to produce the request
+ headers and body. */
+static svn_error_t *
+setup_request(serf_request_t *request,
+ svn_ra_serf__handler_t *handler,
+ serf_bucket_t **req_bkt,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_t *body_bkt;
+ serf_bucket_t *headers_bkt;
+ const char *accept_encoding;
+
+ if (handler->body_delegate)
+ {
+ serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
+
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
+ bkt_alloc, request_pool));
+ }
+ else
+ {
+ body_bkt = NULL;
+ }
+
+ if (handler->custom_accept_encoding)
+ {
+ accept_encoding = NULL;
+ }
+ else if (handler->session->using_compression)
+ {
+ /* Accept gzip compression if enabled. */
+ accept_encoding = "gzip";
+ }
+ else
+ {
+ accept_encoding = NULL;
+ }
+
+ SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
+ handler->session, handler->method, handler->path,
+ body_bkt, handler->body_type, accept_encoding,
+ request_pool, scratch_pool));
+
+ if (handler->header_delegate)
+ {
+ /* ### should pass the scratch_pool */
+ SVN_ERR(handler->header_delegate(headers_bkt,
+ handler->header_delegate_baton,
+ request_pool));
+ }
+
+ return APR_SUCCESS;
+}
+
+/* Implements the serf_request_setup_t interface (which sets up both a
+ request and its response handler callback). Handles errors for
+ setup_request_cb */
+static apr_status_t
+setup_request_cb(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *s_handler,
+ void **s_handler_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *handler = setup_baton;
+ svn_error_t *err;
+
+ /* ### construct a scratch_pool? serf gives us a pool that will live for
+ ### the duration of the request. */
+ apr_pool_t *scratch_pool = pool;
+
+ if (strcmp(handler->method, "HEAD") == 0)
+ *acceptor = accept_head;
+ else
+ *acceptor = accept_response;
+ *acceptor_baton = handler->session;
+
+ *s_handler = handle_response_cb;
+ *s_handler_baton = handler;
+
+ err = svn_error_trace(setup_request(request, handler, req_bkt,
+ pool /* request_pool */, scratch_pool));
+
+ return save_error(handler->session, err);
+}
+
+void
+svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
+{
+ SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL);
+
+ /* In case HANDLER is re-queued, reset the various transient fields.
+
+ ### prior to recent changes, HANDLER was constant. maybe we should
+ ### break out these processing fields, apart from the request
+ ### definition. */
+ handler->done = FALSE;
+ handler->server_error = NULL;
+ handler->sline.version = 0;
+ handler->location = NULL;
+ handler->reading_body = FALSE;
+ handler->discard_body = FALSE;
+
+ /* ### do we ever alter the >response_handler? */
+
+ /* ### do we need to hold onto the returned request object, or just
+ ### not worry about it (the serf ctx will manage it). */
+ (void) serf_connection_request_create(handler->conn->conn,
+ setup_request_cb, handler);
+}
+
+
+svn_error_t *
+svn_ra_serf__discover_vcc(const char **vcc_url,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ const char *path;
+ const char *relative_path;
+ const char *uuid;
+
+ /* If we've already got the information our caller seeks, just return it. */
+ if (session->vcc_url && session->repos_root_str)
+ {
+ *vcc_url = session->vcc_url;
+ return SVN_NO_ERROR;
+ }
+
+ /* If no connection is provided, use the default one. */
+ if (! conn)
+ {
+ conn = session->conns[0];
+ }
+
+ path = session->session_url.path;
+ *vcc_url = NULL;
+ uuid = NULL;
+
+ do
+ {
+ apr_hash_t *props;
+ svn_error_t *err;
+
+ err = svn_ra_serf__fetch_node_props(&props, conn,
+ path, SVN_INVALID_REVNUM,
+ base_props, pool, pool);
+ if (! err)
+ {
+ apr_hash_t *ns_props;
+
+ ns_props = apr_hash_get(props, "DAV:", 4);
+ *vcc_url = svn_prop_get_value(ns_props,
+ "version-controlled-configuration");
+
+ ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
+ relative_path = svn_prop_get_value(ns_props,
+ "baseline-relative-path");
+ uuid = svn_prop_get_value(ns_props, "repository-uuid");
+ break;
+ }
+ else
+ {
+ if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
+ (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
+ {
+ return svn_error_trace(err); /* found a _real_ error */
+ }
+ else
+ {
+ /* This happens when the file is missing in HEAD. */
+ svn_error_clear(err);
+
+ /* Okay, strip off a component from PATH. */
+ path = svn_urlpath__dirname(path, pool);
+
+ /* An error occurred on conns. serf 0.4.0 remembers that
+ the connection had a problem. We need to reset it, in
+ order to use it again. */
+ serf_connection_reset(conn->conn);
+ }
+ }
+ }
+ while ((path[0] != '\0')
+ && (! (path[0] == '/' && path[1] == '\0')));
+
+ if (!*vcc_url)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The PROPFIND response did not include the "
+ "requested version-controlled-configuration "
+ "value"));
+ }
+
+ /* Store our VCC in our cache. */
+ if (!session->vcc_url)
+ {
+ session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
+ }
+
+ /* Update our cached repository root URL. */
+ if (!session->repos_root_str)
+ {
+ svn_stringbuf_t *url_buf;
+
+ url_buf = svn_stringbuf_create(path, pool);
+
+ svn_path_remove_components(url_buf,
+ svn_path_component_count(relative_path));
+
+ /* Now recreate the root_url. */
+ session->repos_root = session->session_url;
+ session->repos_root.path =
+ (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
+ session->repos_root_str =
+ svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
+ &session->repos_root, 0),
+ session->pool);
+ }
+
+ /* Store the repository UUID in the cache. */
+ if (!session->uuid)
+ {
+ session->uuid = apr_pstrdup(session->pool, uuid);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_relative_path(const char **rel_path,
+ const char *orig_path,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ const char *decoded_root, *decoded_orig;
+
+ if (! session->repos_root.path)
+ {
+ const char *vcc_url;
+
+ /* This should only happen if we haven't detected HTTP v2
+ support from the server. */
+ assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
+
+ /* We don't actually care about the VCC_URL, but this API
+ promises to populate the session's root-url cache, and that's
+ what we really want. */
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
+ conn ? conn : session->conns[0],
+ pool));
+ }
+
+ decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
+ decoded_orig = svn_path_uri_decode(orig_path, pool);
+ *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
+ SVN_ERR_ASSERT(*rel_path != NULL);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__report_resource(const char **report_target,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ apr_pool_t *pool)
+{
+ /* If we have HTTP v2 support, we want to report against the 'me'
+ resource. */
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
+ *report_target = apr_pstrdup(pool, session->me_resource);
+
+ /* Otherwise, we'll use the default VCC. */
+ else
+ SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__error_on_status(int status_code,
+ const char *path,
+ const char *location)
+{
+ switch(status_code)
+ {
+ case 301:
+ case 302:
+ case 307:
+ return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
+ (status_code == 301)
+ ? _("Repository moved permanently to '%s';"
+ " please relocate")
+ : _("Repository moved temporarily to '%s';"
+ " please relocate"), location);
+ case 403:
+ return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
+ _("Access to '%s' forbidden"), path);
+
+ case 404:
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' path not found"), path);
+ case 423:
+ return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
+ _("'%s': no lock token available"), path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
+ svn_delta_shim_callbacks_t *callbacks)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ session->shim_callbacks = callbacks;
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to Expat's XML_StartElementHandler */
+static void
+expat_start(void *userData, const char *raw_name, const char **attrs)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_start(ectx->xmlctx,
+ raw_name, attrs));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Conforms to Expat's XML_EndElementHandler */
+static void
+expat_end(void *userData, const char *raw_name)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Conforms to Expat's XML_CharacterDataHandler */
+static void
+expat_cdata(void *userData, const char *data, int len)
+{
+ struct expat_ctx_t *ectx = userData;
+
+ if (ectx->inner_error != NULL)
+ return;
+
+ ectx->inner_error = svn_error_trace(
+ svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len));
+
+#ifdef EXPAT_HAS_STOPPARSER
+ if (ectx->inner_error)
+ (void) XML_StopParser(ectx->parser, 0 /* resumable */);
+#endif
+}
+
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+expat_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct expat_ctx_t *ectx = baton;
+
+ if (!ectx->parser)
+ {
+ ectx->parser = XML_ParserCreate(NULL);
+ apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup, apr_pool_cleanup_null);
+ XML_SetUserData(ectx->parser, ectx);
+ XML_SetElementHandler(ectx->parser, expat_start, expat_end);
+ XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
+ }
+
+ /* ### TODO: sline.code < 200 should really be handled by the core */
+ if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
+ {
+ /* By deferring to expect_empty_body(), it will make a choice on
+ how to handle the body. Whatever the decision, the core handler
+ will take over, and we will not be called again. */
+ return svn_error_trace(svn_ra_serf__expect_empty_body(
+ request, response, ectx->handler,
+ scratch_pool));
+ }
+
+ while (1)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+ int expat_status;
+
+ status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
+ if (SERF_BUCKET_READ_ERROR(status))
+ return svn_ra_serf__wrap_err(status, NULL);
+
+#if 0
+ /* ### move restart/skip into the core handler */
+ ectx->handler->read_size += len;
+#endif
+
+ /* ### move PAUSED behavior to a new response handler that can feed
+ ### an inner handler, or can pause for a while. */
+
+ /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */
+
+ expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */);
+
+ /* We need to check INNER_ERROR first. This is an error from the
+ callbacks that has been "dropped off" for us to retrieve. On
+ current Expat parsers, we stop the parser when an error occurs,
+ so we want to ignore EXPAT_STATUS (which reports the stoppage).
+
+ If an error is not present, THEN we go ahead and look for parsing
+ errors. */
+ if (ectx->inner_error)
+ {
+ apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup);
+ return svn_error_trace(ectx->inner_error);
+ }
+ if (expat_status == XML_STATUS_ERROR)
+ return svn_error_createf(SVN_ERR_XML_MALFORMED,
+ ectx->inner_error,
+ _("The %s response contains invalid XML"
+ " (%d %s)"),
+ ectx->handler->method,
+ ectx->handler->sline.code,
+ ectx->handler->sline.reason);
+
+ /* The parsing went fine. What has the bucket told us? */
+
+ if (APR_STATUS_IS_EOF(status))
+ {
+ /* Tell expat we've reached the end of the content. Ignore the
+ return status. We just don't care. */
+ (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */);
+
+ svn_ra_serf__xml_context_destroy(ectx->xmlctx);
+ apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
+ xml_parser_cleanup);
+
+ /* ### should check XMLCTX to see if it has returned to the
+ ### INITIAL state. we may have ended early... */
+ }
+
+ if (status && !SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_ra_serf__wrap_err(status, NULL);
+ }
+ }
+
+ /* NOTREACHED */
+}
+
+
+svn_ra_serf__handler_t *
+svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__handler_t *handler;
+ struct expat_ctx_t *ectx;
+
+ ectx = apr_pcalloc(result_pool, sizeof(*ectx));
+ ectx->xmlctx = xmlctx;
+ ectx->parser = NULL;
+ ectx->cleanup_pool = result_pool;
+
+
+ handler = apr_pcalloc(result_pool, sizeof(*handler));
+ handler->handler_pool = result_pool;
+ handler->response_handler = expat_response_handler;
+ handler->response_baton = ectx;
+
+ ectx->handler = handler;
+
+ return handler;
+}
diff --git a/subversion/libsvn_ra_serf/util_error.c b/subversion/libsvn_ra_serf/util_error.c
new file mode 100644
index 0000000..da66091
--- /dev/null
+++ b/subversion/libsvn_ra_serf/util_error.c
@@ -0,0 +1,100 @@
+/*
+ * util_error.c : serf utility routines for wrapping serf status codes
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+#include <serf.h>
+
+#include "svn_utf.h"
+#include "private/svn_error_private.h"
+
+#include "ra_serf.h"
+
+/*
+ * Undefine the helpers for creating errors.
+ *
+ * *NOTE*: Any use of these functions in any other function may need
+ * to call svn_error__locate() because the macro that would otherwise
+ * do this is being undefined and the filename and line number will
+ * not be properly set in the static error_file and error_line
+ * variables.
+ */
+#undef svn_error_create
+#undef svn_error_createf
+#undef svn_error_quick_wrap
+#undef svn_error_wrap_apr
+#undef svn_ra_serf__wrap_err
+
+svn_error_t *
+svn_ra_serf__wrap_err(apr_status_t status,
+ const char *fmt,
+ ...)
+{
+ const char *serf_err_msg = serf_error_string(status);
+ svn_error_t *err;
+ va_list ap;
+
+ err = svn_error_create(status, NULL, NULL);
+
+ if (serf_err_msg || fmt)
+ {
+ const char *msg;
+ const char *err_msg;
+ char errbuf[255]; /* Buffer for APR error message. */
+
+ if (serf_err_msg)
+ {
+ err_msg = serf_err_msg;
+ }
+ else
+ {
+ svn_error_t *utf8_err;
+
+ /* Grab the APR error message. */
+ apr_strerror(status, errbuf, sizeof(errbuf));
+ utf8_err = svn_utf_cstring_to_utf8(&err_msg, errbuf, err->pool);
+ if (utf8_err)
+ err_msg = NULL;
+ svn_error_clear(utf8_err);
+ }
+
+ /* Append it to the formatted message. */
+ if (fmt)
+ {
+ va_start(ap, fmt);
+ msg = apr_pvsprintf(err->pool, fmt, ap);
+ va_end(ap);
+ }
+ else
+ {
+ msg = "ra_serf";
+ }
+ if (err_msg)
+ {
+ err->message = apr_pstrcat(err->pool, msg, ": ", err_msg, NULL);
+ }
+ else
+ {
+ err->message = msg;
+ }
+ }
+
+ return err;
+}
diff --git a/subversion/libsvn_ra_serf/xml.c b/subversion/libsvn_ra_serf/xml.c
new file mode 100644
index 0000000..95017d5
--- /dev/null
+++ b/subversion/libsvn_ra_serf/xml.c
@@ -0,0 +1,825 @@
+/*
+ * xml.c : standard XML parsing routines for ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.h"
+#include "../libsvn_ra/ra_loader.h"
+#include "svn_config.h"
+#include "svn_delta.h"
+#include "svn_path.h"
+
+#include "svn_private_config.h"
+#include "private/svn_string_private.h"
+
+#include "ra_serf.h"
+
+
+struct svn_ra_serf__xml_context_t {
+ /* Current state information. */
+ svn_ra_serf__xml_estate_t *current;
+
+ /* If WAITING.NAMESPACE != NULL, wait for NAMESPACE:NAME element to be
+ closed before looking for transitions from CURRENT->STATE. */
+ svn_ra_serf__dav_props_t waiting;
+
+ /* The transition table. */
+ const svn_ra_serf__xml_transition_t *ttable;
+
+ /* The callback information. */
+ svn_ra_serf__xml_opened_t opened_cb;
+ svn_ra_serf__xml_closed_t closed_cb;
+ svn_ra_serf__xml_cdata_t cdata_cb;
+ void *baton;
+
+ /* Linked list of free states. */
+ svn_ra_serf__xml_estate_t *free_states;
+
+#ifdef SVN_DEBUG
+ /* Used to verify we are not re-entering a callback, specifically to
+ ensure SCRATCH_POOL is not cleared while an outer callback is
+ trying to use it. */
+ svn_boolean_t within_callback;
+#define START_CALLBACK(xmlctx) \
+ do { \
+ svn_ra_serf__xml_context_t *xmlctx__tmp = (xmlctx); \
+ SVN_ERR_ASSERT(!xmlctx__tmp->within_callback); \
+ xmlctx__tmp->within_callback = TRUE; \
+ } while (0)
+#define END_CALLBACK(xmlctx) ((xmlctx)->within_callback = FALSE)
+#else
+#define START_CALLBACK(xmlctx) /* empty */
+#define END_CALLBACK(xmlctx) /* empty */
+#endif /* SVN_DEBUG */
+
+ apr_pool_t *scratch_pool;
+
+};
+
+struct svn_ra_serf__xml_estate_t {
+ /* The current state value. */
+ int state;
+
+ /* The xml tag that opened this state. Waiting for the tag to close. */
+ svn_ra_serf__dav_props_t tag;
+
+ /* Should the CLOSED_CB function be called for custom processing when
+ this tag is closed? */
+ svn_boolean_t custom_close;
+
+ /* A pool may be constructed for this state. */
+ apr_pool_t *state_pool;
+
+ /* The namespaces extent for this state/element. This will start with
+ the parent's NS_LIST, and we will push new namespaces into our
+ local list. The parent will be unaffected by our locally-scoped data. */
+ svn_ra_serf__ns_t *ns_list;
+
+ /* Any collected attribute values. char * -> svn_string_t *. May be NULL
+ if no attributes have been collected. */
+ apr_hash_t *attrs;
+
+ /* Any collected cdata. May be NULL if no cdata is being collected. */
+ svn_stringbuf_t *cdata;
+
+ /* Previous/outer state. */
+ svn_ra_serf__xml_estate_t *prev;
+
+};
+
+
+static void
+define_namespaces(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *(*get_pool)(void *baton),
+ void *baton)
+{
+ const char *const *tmp_attrs = attrs;
+
+ for (tmp_attrs = attrs; *tmp_attrs != NULL; tmp_attrs += 2)
+ {
+ if (strncmp(*tmp_attrs, "xmlns", 5) == 0)
+ {
+ const svn_ra_serf__ns_t *cur_ns;
+ svn_boolean_t found = FALSE;
+ const char *prefix;
+
+ /* The empty prefix, or a named-prefix. */
+ if (tmp_attrs[0][5] == ':')
+ prefix = &tmp_attrs[0][6];
+ else
+ prefix = "";
+
+ /* Have we already defined this ns previously? */
+ for (cur_ns = *ns_list; cur_ns; cur_ns = cur_ns->next)
+ {
+ if (strcmp(cur_ns->namespace, prefix) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ apr_pool_t *pool;
+ svn_ra_serf__ns_t *new_ns;
+
+ if (get_pool)
+ pool = get_pool(baton);
+ else
+ pool = baton;
+ new_ns = apr_palloc(pool, sizeof(*new_ns));
+ new_ns->namespace = apr_pstrdup(pool, prefix);
+ new_ns->url = apr_pstrdup(pool, tmp_attrs[1]);
+
+ /* Push into the front of NS_LIST. Parent states will point
+ to later in the chain, so will be unaffected by
+ shadowing/other namespaces pushed onto NS_LIST. */
+ new_ns->next = *ns_list;
+ *ns_list = new_ns;
+ }
+ }
+ }
+}
+
+
+void
+svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
+ const char *const *attrs,
+ apr_pool_t *result_pool)
+{
+ define_namespaces(ns_list, attrs, NULL /* get_pool */, result_pool);
+}
+
+
+/*
+ * Look up NAME in the NS_LIST list for previously declared namespace
+ * definitions and return a DAV_PROPS_T-tuple that has values.
+ */
+void
+svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name,
+ const svn_ra_serf__ns_t *ns_list,
+ const char *name)
+{
+ const char *colon;
+
+ colon = strchr(name, ':');
+ if (colon)
+ {
+ const svn_ra_serf__ns_t *ns;
+
+ for (ns = ns_list; ns; ns = ns->next)
+ {
+ if (strncmp(ns->namespace, name, colon - name) == 0)
+ {
+ returned_prop_name->namespace = ns->url;
+ returned_prop_name->name = colon + 1;
+ return;
+ }
+ }
+ }
+ else
+ {
+ const svn_ra_serf__ns_t *ns;
+
+ for (ns = ns_list; ns; ns = ns->next)
+ {
+ if (! ns->namespace[0])
+ {
+ returned_prop_name->namespace = ns->url;
+ returned_prop_name->name = name;
+ return;
+ }
+ }
+ }
+
+ /* If the prefix is not found, then the name is NOT within a
+ namespace. */
+ returned_prop_name->namespace = "";
+ returned_prop_name->name = name;
+}
+
+
+#define XML_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+
+void
+svn_ra_serf__add_xml_header_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc)
+{
+ serf_bucket_t *tmp;
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(XML_HEADER, sizeof(XML_HEADER) - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+}
+
+void
+svn_ra_serf__add_open_tag_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *tag, ...)
+{
+ va_list ap;
+ const char *key;
+ serf_bucket_t *tmp;
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ va_start(ap, tag);
+ while ((key = va_arg(ap, char *)) != NULL)
+ {
+ const char *val = va_arg(ap, const char *);
+ if (val)
+ {
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" ", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(key, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("=\"", 2, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(val, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+ }
+ }
+ va_end(ap);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+}
+
+void
+svn_ra_serf__add_close_tag_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *tag)
+{
+ serf_bucket_t *tmp;
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</", 2, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(">", 1, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp);
+}
+
+void
+svn_ra_serf__add_cdata_len_buckets(serf_bucket_t *agg_bucket,
+ serf_bucket_alloc_t *bkt_alloc,
+ const char *data, apr_size_t len)
+{
+ const char *end = data + len;
+ const char *p = data, *q;
+ serf_bucket_t *tmp_bkt;
+
+ while (1)
+ {
+ /* Find a character which needs to be quoted and append bytes up
+ to that point. Strictly speaking, '>' only needs to be
+ quoted if it follows "]]", but it's easier to quote it all
+ the time.
+
+ So, why are we escaping '\r' here? Well, according to the
+ XML spec, '\r\n' gets converted to '\n' during XML parsing.
+ Also, any '\r' not followed by '\n' is converted to '\n'. By
+ golly, if we say we want to escape a '\r', we want to make
+ sure it remains a '\r'! */
+ q = p;
+ while (q < end && *q != '&' && *q != '<' && *q != '>' && *q != '\r')
+ q++;
+
+
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(p, q - p, bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+
+ /* We may already be a winner. */
+ if (q == end)
+ break;
+
+ /* Append the entity reference for the character. */
+ if (*q == '&')
+ {
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&amp;", sizeof("&amp;") - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+ }
+ else if (*q == '<')
+ {
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&lt;", sizeof("&lt;") - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+ }
+ else if (*q == '>')
+ {
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&gt;", sizeof("&gt;") - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+ }
+ else if (*q == '\r')
+ {
+ tmp_bkt = SERF_BUCKET_SIMPLE_STRING_LEN("&#13;", sizeof("&#13;") - 1,
+ bkt_alloc);
+ serf_bucket_aggregate_append(agg_bucket, tmp_bkt);
+ }
+
+ p = q + 1;
+ }
+}
+
+void svn_ra_serf__add_tag_buckets(serf_bucket_t *agg_bucket, const char *tag,
+ const char *value,
+ serf_bucket_alloc_t *bkt_alloc)
+{
+ svn_ra_serf__add_open_tag_buckets(agg_bucket, bkt_alloc, tag, NULL);
+
+ if (value)
+ {
+ svn_ra_serf__add_cdata_len_buckets(agg_bucket, bkt_alloc,
+ value, strlen(value));
+ }
+
+ svn_ra_serf__add_close_tag_buckets(agg_bucket, bkt_alloc, tag);
+}
+
+void
+svn_ra_serf__xml_push_state(svn_ra_serf__xml_parser_t *parser,
+ int state)
+{
+ svn_ra_serf__xml_state_t *new_state;
+
+ if (!parser->free_state)
+ {
+ new_state = apr_palloc(parser->pool, sizeof(*new_state));
+ new_state->pool = svn_pool_create(parser->pool);
+ }
+ else
+ {
+ new_state = parser->free_state;
+ parser->free_state = parser->free_state->prev;
+
+ svn_pool_clear(new_state->pool);
+ }
+
+ if (parser->state)
+ {
+ new_state->private = parser->state->private;
+ new_state->ns_list = parser->state->ns_list;
+ }
+ else
+ {
+ new_state->private = NULL;
+ new_state->ns_list = NULL;
+ }
+
+ new_state->current_state = state;
+
+ /* Add it to the state chain. */
+ new_state->prev = parser->state;
+ parser->state = new_state;
+}
+
+void svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser)
+{
+ svn_ra_serf__xml_state_t *cur_state;
+
+ cur_state = parser->state;
+ parser->state = cur_state->prev;
+ cur_state->prev = parser->free_state;
+ parser->free_state = cur_state;
+}
+
+
+/* Return a pool for XES to use for self-alloc (and other specifics). */
+static apr_pool_t *
+xes_pool(const svn_ra_serf__xml_estate_t *xes)
+{
+ /* Move up through parent states looking for one with a pool. This
+ will always terminate since the initial state has a pool. */
+ while (xes->state_pool == NULL)
+ xes = xes->prev;
+ return xes->state_pool;
+}
+
+
+static void
+ensure_pool(svn_ra_serf__xml_estate_t *xes)
+{
+ if (xes->state_pool == NULL)
+ xes->state_pool = svn_pool_create(xes_pool(xes));
+}
+
+
+/* This callback is used by define_namespaces() to wait until a pool is
+ required before constructing it. */
+static apr_pool_t *
+lazy_create_pool(void *baton)
+{
+ svn_ra_serf__xml_estate_t *xes = baton;
+
+ ensure_pool(xes);
+ return xes->state_pool;
+}
+
+void
+svn_ra_serf__xml_context_destroy(
+ svn_ra_serf__xml_context_t *xmlctx)
+{
+ svn_pool_destroy(xmlctx->scratch_pool);
+}
+
+svn_ra_serf__xml_context_t *
+svn_ra_serf__xml_context_create(
+ const svn_ra_serf__xml_transition_t *ttable,
+ svn_ra_serf__xml_opened_t opened_cb,
+ svn_ra_serf__xml_closed_t closed_cb,
+ svn_ra_serf__xml_cdata_t cdata_cb,
+ void *baton,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__xml_context_t *xmlctx;
+ svn_ra_serf__xml_estate_t *xes;
+
+ xmlctx = apr_pcalloc(result_pool, sizeof(*xmlctx));
+ xmlctx->ttable = ttable;
+ xmlctx->opened_cb = opened_cb;
+ xmlctx->closed_cb = closed_cb;
+ xmlctx->cdata_cb = cdata_cb;
+ xmlctx->baton = baton;
+ xmlctx->scratch_pool = svn_pool_create(result_pool);
+
+ xes = apr_pcalloc(result_pool, sizeof(*xes));
+ /* XES->STATE == 0 */
+
+ /* Child states may use this pool to allocate themselves. If a child
+ needs to collect information, then it will construct a subpool and
+ will use that to allocate itself and its collected data. */
+ xes->state_pool = result_pool;
+
+ xmlctx->current = xes;
+
+ return xmlctx;
+}
+
+
+apr_hash_t *
+svn_ra_serf__xml_gather_since(svn_ra_serf__xml_estate_t *xes,
+ int stop_state)
+{
+ apr_hash_t *data;
+ apr_pool_t *pool;
+
+ ensure_pool(xes);
+ pool = xes->state_pool;
+
+ data = apr_hash_make(pool);
+
+ for (; xes != NULL; xes = xes->prev)
+ {
+ if (xes->attrs != NULL)
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, xes->attrs); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+
+ /* Parent name/value lifetimes are at least as long as POOL. */
+ apr_hash_this(hi, &key, &klen, &val);
+ apr_hash_set(data, key, klen, val);
+ }
+ }
+
+ if (xes->state == stop_state)
+ break;
+ }
+
+ return data;
+}
+
+
+void
+svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes,
+ int state,
+ const char *name,
+ const char *value)
+{
+ svn_ra_serf__xml_estate_t *scan;
+
+ for (scan = xes; scan != NULL && scan->state != state; scan = scan->prev)
+ /* pass */ ;
+
+ SVN_ERR_ASSERT_NO_RETURN(scan != NULL);
+
+ /* Make sure the target state has a pool. */
+ ensure_pool(scan);
+
+ /* ... and attribute storage. */
+ if (scan->attrs == NULL)
+ scan->attrs = apr_hash_make(scan->state_pool);
+
+ /* In all likelihood, NAME is a string constant. But we can't really
+ be sure. And it isn't like we're storing a billion of these into
+ the state pool. */
+ svn_hash_sets(scan->attrs,
+ apr_pstrdup(scan->state_pool, name),
+ apr_pstrdup(scan->state_pool, value));
+}
+
+
+apr_pool_t *
+svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes)
+{
+ /* If they asked for a pool, then ensure that we have one to provide. */
+ ensure_pool(xes);
+
+ return xes->state_pool;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name,
+ const char *const *attrs)
+{
+ svn_ra_serf__xml_estate_t *current = xmlctx->current;
+ svn_ra_serf__dav_props_t elemname;
+ const svn_ra_serf__xml_transition_t *scan;
+ apr_pool_t *new_pool;
+ svn_ra_serf__xml_estate_t *new_xes;
+
+ /* If we're waiting for an element to close, then just ignore all
+ other element-opens. */
+ if (xmlctx->waiting.namespace != NULL)
+ return SVN_NO_ERROR;
+
+ /* Look for xmlns: attributes. Lazily create the state pool if any
+ were found. */
+ define_namespaces(&current->ns_list, attrs, lazy_create_pool, current);
+
+ svn_ra_serf__expand_ns(&elemname, current->ns_list, raw_name);
+
+ for (scan = xmlctx->ttable; scan->ns != NULL; ++scan)
+ {
+ if (scan->from_state != current->state)
+ continue;
+
+ /* Wildcard tag match. */
+ if (*scan->name == '*')
+ break;
+
+ /* Found a specific transition. */
+ if (strcmp(elemname.name, scan->name) == 0
+ && strcmp(elemname.namespace, scan->ns) == 0)
+ break;
+ }
+ if (scan->ns == NULL)
+ {
+ xmlctx->waiting = elemname;
+ /* ### return? */
+ return SVN_NO_ERROR;
+ }
+
+ /* We should not be told to collect cdata if the closed_cb will not
+ be called. */
+ SVN_ERR_ASSERT(!scan->collect_cdata || scan->custom_close);
+
+ /* Found a transition. Make it happen. */
+
+ /* ### todo. push state */
+
+ /* ### how to use free states? */
+ /* This state should be allocated in the extent pool. If we will be
+ collecting information for this state, then construct a subpool.
+
+ ### potentially optimize away the subpool if none of the
+ ### attributes are present. subpools are cheap, tho... */
+ new_pool = xes_pool(current);
+ if (scan->collect_cdata || scan->collect_attrs[0])
+ {
+ new_pool = svn_pool_create(new_pool);
+
+ /* Prep the new state. */
+ new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
+ new_xes->state_pool = new_pool;
+
+ /* If we're supposed to collect cdata, then set up a buffer for
+ this. The existence of this buffer will instruct our cdata
+ callback to collect the cdata. */
+ if (scan->collect_cdata)
+ new_xes->cdata = svn_stringbuf_create_empty(new_pool);
+
+ if (scan->collect_attrs[0] != NULL)
+ {
+ const char *const *saveattr = &scan->collect_attrs[0];
+
+ new_xes->attrs = apr_hash_make(new_pool);
+ for (; *saveattr != NULL; ++saveattr)
+ {
+ const char *name;
+ const char *value;
+
+ if (**saveattr == '?')
+ {
+ name = *saveattr + 1;
+ value = svn_xml_get_attr_value(name, attrs);
+ }
+ else
+ {
+ name = *saveattr;
+ value = svn_xml_get_attr_value(name, attrs);
+ if (value == NULL)
+ return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND,
+ NULL,
+ _("Missing XML attribute: '%s'"),
+ name);
+ }
+
+ if (value)
+ svn_hash_sets(new_xes->attrs, name,
+ apr_pstrdup(new_pool, value));
+ }
+ }
+ }
+ else
+ {
+ /* Prep the new state. */
+ new_xes = apr_pcalloc(new_pool, sizeof(*new_xes));
+ /* STATE_POOL remains NULL. */
+ }
+
+ /* Some basic copies to set up the new estate. */
+ new_xes->state = scan->to_state;
+ new_xes->tag.name = apr_pstrdup(new_pool, elemname.name);
+ new_xes->tag.namespace = apr_pstrdup(new_pool, elemname.namespace);
+ new_xes->custom_close = scan->custom_close;
+
+ /* Start with the parent's namespace set. */
+ new_xes->ns_list = current->ns_list;
+
+ /* The new state is prepared. Make it current. */
+ new_xes->prev = current;
+ xmlctx->current = new_xes;
+
+ if (xmlctx->opened_cb)
+ {
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->opened_cb(new_xes, xmlctx->baton,
+ new_xes->state, &new_xes->tag,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx,
+ const char *raw_name)
+{
+ svn_ra_serf__xml_estate_t *xes = xmlctx->current;
+ svn_ra_serf__dav_props_t elemname;
+
+ svn_ra_serf__expand_ns(&elemname, xes->ns_list, raw_name);
+
+ if (xmlctx->waiting.namespace != NULL)
+ {
+ /* If this element is not the closer, then keep waiting... */
+ if (strcmp(elemname.name, xmlctx->waiting.name) != 0
+ || strcmp(elemname.namespace, xmlctx->waiting.namespace) != 0)
+ return SVN_NO_ERROR;
+
+ /* Found it. Stop waiting, and go back for more. */
+ xmlctx->waiting.namespace = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* We should be looking at the same tag that opened the current state.
+
+ Unknown elements are simply skipped, so we wouldn't reach this check.
+
+ Known elements push a new state for a given tag. Some other elemname
+ would imply closing an ancestor tag (where did ours go?) or a spurious
+ tag closure. */
+ if (strcmp(elemname.name, xes->tag.name) != 0
+ || strcmp(elemname.namespace, xes->tag.namespace) != 0)
+ return svn_error_create(SVN_ERR_XML_MALFORMED, NULL,
+ _("The response contains invalid XML"));
+
+ if (xes->custom_close)
+ {
+ const svn_string_t *cdata;
+
+ if (xes->cdata)
+ {
+ cdata = svn_stringbuf__morph_into_string(xes->cdata);
+#ifdef SVN_DEBUG
+ /* We might toss the pool holding this structure, but it could also
+ be within a parent pool. In any case, for safety's sake, disable
+ the stringbuf against future Badness. */
+ xes->cdata->pool = NULL;
+#endif
+ }
+ else
+ cdata = NULL;
+
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->closed_cb(xes, xmlctx->baton, xes->state,
+ cdata, xes->attrs,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ /* Pop the state. */
+ xmlctx->current = xes->prev;
+
+ /* ### not everything should go on the free state list. XES may go
+ ### away with the state pool. */
+ xes->prev = xmlctx->free_states;
+ xmlctx->free_states = xes;
+
+ /* If there is a STATE_POOL, then toss it. This will get rid of as much
+ memory as possible. Potentially the XES (if we didn't create a pool
+ right away, then XES may be in a parent pool). */
+ if (xes->state_pool)
+ svn_pool_destroy(xes->state_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
+ const char *data,
+ apr_size_t len)
+{
+ /* If we are waiting for a closing tag, then we are uninterested in
+ the cdata. Just return. */
+ if (xmlctx->waiting.namespace != NULL)
+ return SVN_NO_ERROR;
+
+ /* If the current state is collecting cdata, then copy the cdata. */
+ if (xmlctx->current->cdata != NULL)
+ {
+ svn_stringbuf_appendbytes(xmlctx->current->cdata, data, len);
+ }
+ /* ... else if a CDATA_CB has been supplied, then invoke it for
+ all states. */
+ else if (xmlctx->cdata_cb != NULL)
+ {
+ START_CALLBACK(xmlctx);
+ SVN_ERR(xmlctx->cdata_cb(xmlctx->current,
+ xmlctx->baton,
+ xmlctx->current->state,
+ data, len,
+ xmlctx->scratch_pool));
+ END_CALLBACK(xmlctx);
+ svn_pool_clear(xmlctx->scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
OpenPOWER on IntegriCloud