summaryrefslogtreecommitdiff
path: root/subversion/libsvn_ra_serf
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
commitf1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch)
tree4223b2035bf2240d681a53822808b3c7f687b905 /subversion/libsvn_ra_serf
downloadsubversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz
Tarball conversion
Diffstat (limited to 'subversion/libsvn_ra_serf')
-rw-r--r--subversion/libsvn_ra_serf/README84
-rw-r--r--subversion/libsvn_ra_serf/blame.c493
-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.c2493
-rw-r--r--subversion/libsvn_ra_serf/get_deleted_rev.c239
-rw-r--r--subversion/libsvn_ra_serf/getdate.c242
-rw-r--r--subversion/libsvn_ra_serf/getlocations.c284
-rw-r--r--subversion/libsvn_ra_serf/getlocationsegments.c252
-rw-r--r--subversion/libsvn_ra_serf/getlocks.c372
-rw-r--r--subversion/libsvn_ra_serf/locks.c778
-rw-r--r--subversion/libsvn_ra_serf/log.c733
-rw-r--r--subversion/libsvn_ra_serf/merge.c589
-rw-r--r--subversion/libsvn_ra_serf/mergeinfo.c309
-rw-r--r--subversion/libsvn_ra_serf/options.c637
-rw-r--r--subversion/libsvn_ra_serf/property.c1126
-rw-r--r--subversion/libsvn_ra_serf/ra_serf.h1458
-rw-r--r--subversion/libsvn_ra_serf/replay.c888
-rw-r--r--subversion/libsvn_ra_serf/serf.c1217
-rw-r--r--subversion/libsvn_ra_serf/update.c2885
-rw-r--r--subversion/libsvn_ra_serf/util.c2265
-rw-r--r--subversion/libsvn_ra_serf/xml.c342
22 files changed, 17955 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..c166ee7
--- /dev/null
+++ b/subversion/libsvn_ra_serf/blame.c
@@ -0,0 +1,493 @@
+/*
+ * 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 <expat.h>
+
+#include <serf.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 "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 {
+ NONE = 0,
+ FILE_REVS_REPORT,
+ FILE_REV,
+ REV_PROP,
+ SET_PROP,
+ REMOVE_PROP,
+ MERGED_REVISION,
+ TXDELTA
+} blame_state_e;
+
+typedef struct blame_info_t {
+ /* Current pool. */
+ apr_pool_t *pool;
+
+ /* our suspicious file */
+ const char *path;
+
+ /* the intended suspect */
+ svn_revnum_t rev;
+
+ /* Hashtable of revision properties */
+ apr_hash_t *rev_props;
+
+ /* Added and removed properties (svn_prop_t*'s) */
+ apr_array_header_t *prop_diffs;
+
+ /* txdelta */
+ svn_txdelta_window_handler_t txdelta;
+ void *txdelta_baton;
+
+ /* returned txdelta stream */
+ svn_stream_t *stream;
+
+ /* Is this property base64-encoded? */
+ svn_boolean_t prop_base64;
+
+ /* The currently collected value as we build it up */
+ const char *prop_name;
+ const char *prop_attr;
+ apr_size_t prop_attr_len;
+
+ svn_string_t *prop_string;
+
+ /* Merged revision flag */
+ svn_boolean_t merged_revision;
+
+} blame_info_t;
+
+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;
+
+ /* are we done? */
+ svn_boolean_t done;
+
+ /* blame handler and baton */
+ svn_file_rev_handler_t file_rev;
+ void *file_rev_baton;
+} blame_context_t;
+
+
+static blame_info_t *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ blame_context_t *blame_ctx,
+ blame_state_e state)
+{
+ svn_ra_serf__xml_push_state(parser, state);
+
+ if (state == FILE_REV)
+ {
+ blame_info_t *info;
+
+ info = apr_palloc(parser->state->pool, sizeof(*info));
+
+ info->pool = parser->state->pool;
+
+ info->rev = SVN_INVALID_REVNUM;
+ info->path = NULL;
+
+ info->rev_props = apr_hash_make(info->pool);
+ info->prop_diffs = apr_array_make(info->pool, 0, sizeof(svn_prop_t));
+
+ info->stream = NULL;
+ info->merged_revision = FALSE;
+
+ parser->state->private = info;
+ }
+
+ return parser->state->private;
+}
+
+static const svn_string_t *
+create_propval(blame_info_t *info)
+{
+ const svn_string_t *s;
+
+ if (!info->prop_attr)
+ {
+ return svn_string_create("", info->pool);
+ }
+ else
+ {
+ info->prop_attr = apr_pmemdup(info->pool, info->prop_attr,
+ info->prop_attr_len + 1);
+ }
+
+ s = svn_string_ncreate(info->prop_attr, info->prop_attr_len, info->pool);
+ if (info->prop_base64)
+ {
+ s = svn_base64_decode_string(s, info->pool);
+ }
+ return s;
+}
+
+static svn_error_t *
+start_blame(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ blame_context_t *blame_ctx = userData;
+ blame_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE && strcmp(name.name, "file-revs-report") == 0)
+ {
+ push_state(parser, blame_ctx, FILE_REVS_REPORT);
+ }
+ else if (state == FILE_REVS_REPORT &&
+ strcmp(name.name, "file-rev") == 0)
+ {
+ blame_info_t *info;
+
+ info = push_state(parser, blame_ctx, FILE_REV);
+
+ info->path = apr_pstrdup(info->pool,
+ svn_xml_get_attr_value("path", attrs));
+ info->rev = SVN_STR_TO_REV(svn_xml_get_attr_value("rev", attrs));
+ }
+ else if (state == FILE_REV)
+ {
+ blame_info_t *info;
+ const char *enc;
+
+ info = parser->state->private;
+
+ if (strcmp(name.name, "rev-prop") == 0)
+ {
+ push_state(parser, blame_ctx, REV_PROP);
+ }
+ else if (strcmp(name.name, "set-prop") == 0)
+ {
+ push_state(parser, blame_ctx, SET_PROP);
+ }
+ if (strcmp(name.name, "remove-prop") == 0)
+ {
+ push_state(parser, blame_ctx, REMOVE_PROP);
+ }
+ else if (strcmp(name.name, "merged-revision") == 0)
+ {
+ push_state(parser, blame_ctx, MERGED_REVISION);
+ }
+ else if (strcmp(name.name, "txdelta") == 0)
+ {
+ SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
+ info->path, info->rev,
+ info->rev_props, info->merged_revision,
+ &info->txdelta, &info->txdelta_baton,
+ info->prop_diffs, info->pool));
+
+ info->stream = svn_base64_decode
+ (svn_txdelta_parse_svndiff(info->txdelta, info->txdelta_baton,
+ TRUE, info->pool), info->pool);
+
+ push_state(parser, blame_ctx, TXDELTA);
+ }
+
+ state = parser->state->current_state;
+
+ switch (state)
+ {
+ case REV_PROP:
+ case SET_PROP:
+ case REMOVE_PROP:
+ info->prop_name = apr_pstrdup(info->pool,
+ svn_xml_get_attr_value("name", attrs));
+ info->prop_attr = NULL;
+ info->prop_attr_len = 0;
+
+ enc = svn_xml_get_attr_value("encoding", attrs);
+ if (enc && strcmp(enc, "base64") == 0)
+ {
+ info->prop_base64 = TRUE;
+ }
+ else
+ {
+ info->prop_base64 = FALSE;
+ }
+ break;
+ case MERGED_REVISION:
+ info->merged_revision = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_blame(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ blame_context_t *blame_ctx = userData;
+ blame_state_e state;
+ blame_info_t *info;
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == NONE)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ if (state == FILE_REVS_REPORT &&
+ strcmp(name.name, "file-revs-report") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == FILE_REV &&
+ strcmp(name.name, "file-rev") == 0)
+ {
+ /* no file changes. */
+ if (!info->stream)
+ {
+ SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton,
+ info->path, info->rev,
+ info->rev_props, FALSE,
+ NULL, NULL,
+ info->prop_diffs, info->pool));
+ }
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == REV_PROP &&
+ strcmp(name.name, "rev-prop") == 0)
+ {
+ apr_hash_set(info->rev_props,
+ info->prop_name, APR_HASH_KEY_STRING,
+ create_propval(info));
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if ((state == SET_PROP &&
+ strcmp(name.name, "set-prop") == 0) ||
+ (state == REMOVE_PROP &&
+ strcmp(name.name, "remove-prop") == 0))
+ {
+ svn_prop_t *prop = apr_array_push(info->prop_diffs);
+ prop->name = info->prop_name;
+ prop->value = create_propval(info);
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == MERGED_REVISION &&
+ strcmp(name.name, "merged-revision") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == TXDELTA &&
+ strcmp(name.name, "txdelta") == 0)
+ {
+ SVN_ERR(svn_stream_close(info->stream));
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_blame(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ blame_context_t *blame_ctx = userData;
+ blame_state_e state;
+ blame_info_t *info;
+
+ UNUSED_CTX(blame_ctx);
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == NONE)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ switch (state)
+ {
+ case REV_PROP:
+ case SET_PROP:
+ svn_ra_serf__expand_string(&info->prop_attr, &info->prop_attr_len,
+ data, len, parser->state->pool);
+ break;
+ case TXDELTA:
+ if (info->stream)
+ {
+ apr_size_t ret_len;
+
+ ret_len = len;
+
+ SVN_ERR(svn_stream_write(info->stream, data, &ret_len));
+ }
+ break;
+ default:
+ break;
+ }
+
+ 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_parser_t *parser_ctx;
+ const char *relative_url, *basecoll_url, *req_url;
+ int status_code;
+ 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;
+ blame_ctx->done = FALSE;
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
+ NULL, session->session_url.path,
+ end, NULL, pool));
+ req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ 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;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = blame_ctx;
+ parser_ctx->start = start_blame;
+ parser_ctx->end = end_blame;
+ parser_ctx->cdata = cdata_blame;
+ parser_ctx->done = &blame_ctx->done;
+ parser_ctx->status_code = &status_code;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&blame_ctx->done, session, pool);
+
+ err = svn_error_compose_create(
+ svn_ra_serf__error_on_status(status_code,
+ handler->path,
+ parser_ctx->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..fe39284
--- /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_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 = apr_hash_get(blncache->baseline_info, baseline_url,
+ APR_HASH_KEY_STRING);
+ 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..25aefb3
--- /dev/null
+++ b/subversion/libsvn_ra_serf/commit.c
@@ -0,0 +1,2493 @@
+/*
+ * 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 <expat.h>
+
+#include <serf.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 "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+/* Structure associated with a CHECKOUT request. */
+typedef struct checkout_context_t {
+
+ apr_pool_t *pool;
+
+ const char *activity_url;
+ const char *checkout_url;
+ const char *resource_url;
+
+ svn_ra_serf__simple_request_context_t progress;
+
+} checkout_context_t;
+
+/* 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... */
+ checkout_context_t *baseline; /* checkout for the baseline */
+ 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;
+
+ svn_ra_serf__simple_request_context_t progress;
+} 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;
+
+ svn_ra_serf__simple_request_context_t progress;
+} 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 context for this directory. May be NULL; if so
+ call checkout_dir() first. */
+ checkout_context_t *checkout;
+
+} 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 context for this file. */
+ checkout_context_t *checkout;
+
+ /* 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_ra_serf__simple_request_context_t *ctx)
+{
+ svn_error_t *err;
+
+ /* Ye Olde Fallback Error */
+ err = svn_error_compose_create(
+ ctx->server_error.error,
+ svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("%s of '%s': %d %s"),
+ handler->method, handler->path,
+ ctx->status, ctx->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(ctx->status,
+ handler->path,
+ ctx->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)
+{
+ checkout_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: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_ra_serf__add_cdata_len_buckets(body_bkt, alloc,
+ ctx->activity_url, strlen(ctx->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;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+handle_checkout(serf_request_t *request,
+ serf_bucket_t *response,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ checkout_context_t *ctx = handler_baton;
+
+ svn_error_t *err = svn_ra_serf__handle_status_only(request, response,
+ &ctx->progress, pool);
+
+ /* These handler functions are supposed to return an APR_EOF status
+ wrapped in a svn_error_t to indicate to serf that the response was
+ completely read. While we have to return this status code to our
+ caller, we should treat it as the normal case for now. */
+ if (err && ! APR_STATUS_IS_EOF(err->apr_err))
+ return err;
+
+ /* Get the resulting location. */
+ if (ctx->progress.done && ctx->progress.status == 201)
+ {
+ serf_bucket_t *hdrs;
+ apr_uri_t uri;
+ const char *location;
+ apr_status_t status;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ location = serf_bucket_headers_get(hdrs, "Location");
+ if (!location)
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
+ _("No Location header received"));
+
+ status = apr_uri_parse(pool, location, &uri);
+
+ if (status)
+ err = svn_error_compose_create(svn_error_wrap_apr(status, NULL), err);
+
+ ctx->resource_url = svn_urlpath__canonicalize(uri.path, ctx->pool);
+ }
+
+ return err;
+}
+
+static svn_error_t *
+checkout_dir(dir_context_t *dir)
+{
+ checkout_context_t *checkout_ctx;
+ svn_ra_serf__handler_t *handler;
+ svn_error_t *err;
+ dir_context_t *p_dir = dir;
+
+ if (dir->checkout)
+ {
+ 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->checkout = apr_pcalloc(dir->pool, sizeof(*dir->checkout));
+ dir->checkout->pool = dir->pool;
+ dir->checkout->progress.pool = dir->pool;
+ dir->checkout->activity_url = dir->commit->activity_url;
+ dir->checkout->resource_url =
+ svn_path_url_add_component2(dir->parent_dir->checkout->resource_url,
+ dir->name, dir->pool);
+
+ return SVN_NO_ERROR;
+ }
+ p_dir = p_dir->parent_dir;
+ }
+
+ /* Checkout our directory into the activity URL now. */
+ handler = apr_pcalloc(dir->pool, sizeof(*handler));
+ handler->session = dir->commit->session;
+ handler->conn = dir->commit->conn;
+
+ checkout_ctx = apr_pcalloc(dir->pool, sizeof(*checkout_ctx));
+ checkout_ctx->pool = dir->pool;
+ checkout_ctx->progress.pool = dir->pool;
+
+ checkout_ctx->activity_url = dir->commit->activity_url;
+
+ /* 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.
+ */
+ if (!dir->parent_dir && !dir->commit->baseline)
+ {
+ checkout_ctx->checkout_url = dir->commit->vcc_url;
+ dir->commit->baseline = checkout_ctx;
+ }
+ else
+ {
+ checkout_ctx->checkout_url = dir->url;
+ dir->checkout = checkout_ctx;
+ }
+
+ handler->body_delegate = create_checkout_body;
+ handler->body_delegate_baton = checkout_ctx;
+ handler->body_type = "text/xml";
+
+ handler->response_handler = handle_checkout;
+ handler->response_baton = checkout_ctx;
+
+ handler->method = "CHECKOUT";
+ handler->path = checkout_ctx->checkout_url;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&checkout_ctx->progress.done,
+ dir->commit->session,
+ dir->pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CONFLICT)
+ SVN_ERR_W(err, apr_psprintf(dir->pool,
+ _("Directory '%s' is out of date; try updating"),
+ svn_dirent_local_style(dir->relpath, dir->pool)));
+ return err;
+ }
+
+ if (checkout_ctx->progress.status != 201)
+ {
+ return return_response_err(handler, &checkout_ctx->progress);
+ }
+
+ 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 POOL, and use POOL for temporary allocation.
+ */
+static svn_error_t *
+get_version_url(const char **checked_in_url,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *relpath,
+ svn_revnum_t base_revision,
+ const char *parent_vsn_url,
+ apr_pool_t *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, pool));
+
+ if (current_version)
+ {
+ *checked_in_url =
+ svn_urlpath__canonicalize(current_version->data, pool);
+ return SVN_NO_ERROR;
+ }
+ }
+
+ if (parent_vsn_url)
+ {
+ root_checkout = parent_vsn_url;
+ }
+ else
+ {
+ svn_ra_serf__propfind_context_t *propfind_ctx;
+ apr_hash_t *props;
+ const char *propfind_url;
+
+ props = apr_hash_make(pool);
+
+ if (SVN_IS_VALID_REVNUM(base_revision))
+ {
+ const char *bc_url, *bc_relpath;
+
+ /* 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. */
+ SVN_ERR(svn_ra_serf__get_baseline_info(&bc_url, &bc_relpath,
+ session, conn,
+ session->session_url.path,
+ base_revision, NULL, pool));
+ propfind_url = svn_path_url_add_component2(bc_url, bc_relpath, pool);
+ }
+ else
+ {
+ propfind_url = session->session_url.path;
+ }
+
+ /* ### switch to svn_ra_serf__retrieve_props */
+ SVN_ERR(svn_ra_serf__deliver_props(&propfind_ctx, props, session, conn,
+ propfind_url, base_revision, "0",
+ checked_in_props, NULL, pool));
+ SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, session, pool));
+
+ /* We wouldn't get here if the url wasn't found (404), so the checked-in
+ property should have been set. */
+ root_checkout =
+ svn_ra_serf__get_ver_prop(props, propfind_url,
+ base_revision, "DAV:", "checked-in");
+
+ 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, pool);
+ }
+
+ *checked_in_url = svn_path_url_add_component2(root_checkout, relpath, pool);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+checkout_file(file_context_t *file)
+{
+ svn_ra_serf__handler_t *handler;
+ svn_error_t *err;
+ dir_context_t *parent_dir = file->parent_dir;
+
+ /* 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->checkout = apr_pcalloc(file->pool, sizeof(*file->checkout));
+ file->checkout->pool = file->pool;
+ file->checkout->progress.pool = file->pool;
+ file->checkout->activity_url = file->commit->activity_url;
+ file->checkout->resource_url =
+ svn_path_url_add_component2(parent_dir->checkout->resource_url,
+ svn_relpath__is_child(parent_dir->relpath,
+ file->relpath,
+ file->pool),
+ file->pool);
+ return SVN_NO_ERROR;
+ }
+ parent_dir = parent_dir->parent_dir;
+ }
+
+ /* Checkout our file into the activity URL now. */
+ handler = apr_pcalloc(file->pool, sizeof(*handler));
+ handler->session = file->commit->session;
+ handler->conn = file->commit->conn;
+
+ file->checkout = apr_pcalloc(file->pool, sizeof(*file->checkout));
+ file->checkout->pool = file->pool;
+ file->checkout->progress.pool = file->pool;
+
+ file->checkout->activity_url = file->commit->activity_url;
+
+ SVN_ERR(get_version_url(&(file->checkout->checkout_url),
+ file->commit->session, file->commit->conn,
+ file->relpath, file->base_revision,
+ NULL, file->pool));
+
+ handler->body_delegate = create_checkout_body;
+ handler->body_delegate_baton = file->checkout;
+ handler->body_type = "text/xml";
+
+ handler->response_handler = handle_checkout;
+ handler->response_baton = file->checkout;
+
+ handler->method = "CHECKOUT";
+ handler->path = file->checkout->checkout_url;
+
+ svn_ra_serf__request_create(handler);
+
+ /* There's no need to wait here as we only need this when we start the
+ * PROPPATCH or PUT of the file.
+ */
+ err = svn_ra_serf__context_run_wait(&file->checkout->progress.done,
+ file->commit->session,
+ file->pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CONFLICT)
+ SVN_ERR_W(err, apr_psprintf(file->pool,
+ _("File '%s' is out of date; try updating"),
+ svn_dirent_local_style(file->relpath, file->pool)));
+ return err;
+ }
+
+ if (file->checkout->progress.status != 201)
+ {
+ return return_response_err(handler, &file->checkout->progress);
+ }
+
+ 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;
+}
+
+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));
+ }
+
+ if (proppatch->relpath && proppatch->commit->lock_tokens)
+ {
+ const char *token;
+
+ token = apr_hash_get(proppatch->commit->lock_tokens, proppatch->relpath,
+ APR_HASH_KEY_STRING);
+
+ if (token)
+ {
+ const char *token_header;
+
+ token_header = apr_pstrcat(pool, "(<", token, ">)", (char *)NULL);
+
+ serf_bucket_headers_set(headers, "If", token_header);
+ }
+ }
+
+ 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->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 = &proppatch->progress;
+
+ svn_ra_serf__request_create(handler);
+
+ /* If we don't wait for the response, our pool will be gone! */
+ SVN_ERR(svn_ra_serf__context_run_wait(&proppatch->progress.done,
+ commit->session, pool));
+
+ if (proppatch->progress.status != 207 ||
+ proppatch->progress.server_error.error)
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_PROPPATCH_FAILED,
+ return_response_err(handler, &proppatch->progress),
+ _("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);
+ }
+
+ if (ctx->commit->lock_tokens)
+ {
+ const char *token;
+
+ token = apr_hash_get(ctx->commit->lock_tokens, ctx->relpath,
+ APR_HASH_KEY_STRING);
+
+ if (token)
+ {
+ const char *token_header;
+
+ token_header = apr_pstrcat(pool, "(<", token, ">)", (char *)NULL);
+
+ serf_bucket_headers_set(headers, "If", token_header);
+ }
+ }
+
+ 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_set(headers, "Depth", "0");
+ serf_bucket_headers_set(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->checkout->resource_url,
+ dir->name, pool);
+ }
+ absolute_uri = apr_uri_unparse(pool, &uri, 0);
+
+ serf_bucket_headers_set(headers, "Destination", absolute_uri);
+
+ serf_bucket_headers_set(headers, "Depth", "infinity");
+ serf_bucket_headers_set(headers, "Overwrite", "T");
+
+ /* Implicitly checkout this dir now. */
+ dir->checkout = apr_pcalloc(dir->pool, sizeof(*dir->checkout));
+ dir->checkout->pool = dir->pool;
+ dir->checkout->progress.pool = dir->pool;
+ dir->checkout->activity_url = dir->commit->activity_url;
+ dir->checkout->resource_url = apr_pstrdup(dir->checkout->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 = apr_hash_get(ctx->lock_token_hash, ctx->path,
+ APR_HASH_KEY_STRING);
+
+ 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_set(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)
+{
+ *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__simple_request_context_t *request_ctx;
+ 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__handle_status_only -- 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 *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__handle_status_only(request, response,
+ prc->request_ctx, 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;
+
+ if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session))
+ {
+ svn_ra_serf__simple_request_context_t *post_ctx;
+ post_response_ctx_t *prc;
+ const char *rel_path;
+
+ /* Create our activity URL now on the server. */
+ handler = apr_pcalloc(ctx->pool, sizeof(*handler));
+ handler->method = "POST";
+ handler->body_type = SVN_SKEL_MIME_TYPE;
+ handler->body_delegate = create_txn_post_body;
+ handler->body_delegate_baton = 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;
+
+ post_ctx = apr_pcalloc(ctx->pool, sizeof(*post_ctx));
+ post_ctx->pool = ctx->pool;
+
+ prc = apr_pcalloc(ctx->pool, sizeof(*prc));
+ prc->request_ctx = post_ctx;
+ prc->commit_ctx = ctx;
+
+ handler->response_handler = post_response_handler;
+ handler->response_baton = prc;
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&post_ctx->done, ctx->session,
+ ctx->pool));
+
+ if (post_ctx->status != 201)
+ {
+ apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
+ switch(post_ctx->status)
+ {
+ 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,
+ post_ctx->status, post_ctx->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);
+
+ proppatch_target = ctx->txn_url;
+ }
+ else
+ {
+ svn_ra_serf__options_context_t *opt_ctx;
+ svn_ra_serf__simple_request_context_t *mkact_ctx;
+ const char *activity_str;
+
+ SVN_ERR(svn_ra_serf__create_options_req(&opt_ctx, ctx->session,
+ ctx->session->conns[0],
+ ctx->session->session_url.path,
+ ctx->pool));
+
+ SVN_ERR(svn_ra_serf__context_run_wait(
+ svn_ra_serf__get_options_done_ptr(opt_ctx),
+ ctx->session, ctx->pool));
+
+ activity_str = svn_ra_serf__options_get_activity_collection(opt_ctx);
+ if (!activity_str)
+ 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->method = "MKACTIVITY";
+ handler->path = ctx->activity_url;
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ mkact_ctx = apr_pcalloc(ctx->pool, sizeof(*mkact_ctx));
+ mkact_ctx->pool = ctx->pool;
+
+ handler->response_handler = svn_ra_serf__handle_status_only;
+ handler->response_baton = mkact_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&mkact_ctx->done, ctx->session,
+ ctx->pool));
+
+ if (mkact_ctx->status != 201)
+ {
+ apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED;
+ switch(mkact_ctx->status)
+ {
+ 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,
+ mkact_ctx->status, mkact_ctx->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->commit->conn, dir->relpath,
+ dir->base_revision, ctx->checked_in_url,
+ dir->pool));
+ ctx->checked_in_url = dir->url;
+
+ /* Checkout our root dir */
+ SVN_ERR(checkout_dir(dir));
+
+ proppatch_target = ctx->baseline->resource_url;
+ }
+
+
+ /* PROPPATCH our revprops and pass them along. */
+ proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx));
+ proppatch_ctx->pool = dir_pool;
+ proppatch_ctx->progress.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 void *key;
+ void *val;
+ const char *name;
+ svn_string_t *value;
+ const char *ns;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ name = key;
+ value = val;
+
+ 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));
+ delete_target = svn_path_url_add_component2(dir->checkout->resource_url,
+ svn_relpath_basename(path,
+ NULL),
+ pool);
+ }
+
+ /* DELETE our entry */
+ delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
+ delete_ctx->progress.pool = pool;
+ 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->session = dir->commit->session;
+ handler->conn = dir->commit->conn;
+
+ handler->response_handler = svn_ra_serf__handle_status_only;
+ handler->response_baton = &delete_ctx->progress;
+
+ handler->header_delegate = setup_delete_headers;
+ handler->header_delegate_baton = delete_ctx;
+
+ handler->method = "DELETE";
+ handler->path = delete_target;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&delete_ctx->progress.done,
+ dir->commit->session, 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_ra_serf__request_create(handler);
+
+ delete_ctx->progress.done = 0;
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->progress.done,
+ dir->commit->session, pool));
+ }
+ else if (err)
+ {
+ return err;
+ }
+
+ /* 204 No Content: item successfully deleted */
+ if (delete_ctx->progress.status != 204)
+ {
+ return return_response_err(handler, &delete_ctx->progress);
+ }
+
+ apr_hash_set(dir->commit->deleted_entries,
+ apr_pstrdup(dir->commit->pool, path), APR_HASH_KEY_STRING,
+ (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;
+ svn_ra_serf__simple_request_context_t *add_dir_ctx;
+ 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->url = svn_path_url_add_component2(parent->commit->checked_in_url,
+ dir->name, dir->pool);
+ mkcol_target = svn_path_url_add_component2(
+ parent->checkout->resource_url,
+ dir->name, dir->pool);
+ }
+
+ handler = apr_pcalloc(dir->pool, sizeof(*handler));
+ handler->conn = dir->commit->conn;
+ handler->session = dir->commit->session;
+
+ add_dir_ctx = apr_pcalloc(dir->pool, sizeof(*add_dir_ctx));
+ add_dir_ctx->pool = dir->pool;
+
+ handler->response_handler = svn_ra_serf__handle_status_only;
+ handler->response_baton = add_dir_ctx;
+ if (!dir->copy_path)
+ {
+ handler->method = "MKCOL";
+ handler->path = mkcol_target;
+ }
+ else
+ {
+ apr_uri_t uri;
+ const char *rel_copy_path, *basecoll_url, *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);
+ }
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &rel_copy_path,
+ dir->commit->session,
+ dir->commit->conn,
+ uri.path, dir->copy_revision,
+ NULL, dir_pool));
+ req_url = svn_path_url_add_component2(basecoll_url, rel_copy_path,
+ dir->pool);
+
+ handler->method = "COPY";
+ handler->path = req_url;
+
+ handler->header_delegate = setup_copy_dir_headers;
+ handler->header_delegate_baton = dir;
+ }
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&add_dir_ctx->done,
+ dir->commit->session, dir->pool));
+
+ switch (add_dir_ctx->status)
+ {
+ case 201: /* Created: item was successfully copied */
+ case 204: /* No Content: item successfully replaced an existing target */
+ break;
+
+ case 403:
+ SVN_ERR(add_dir_ctx->server_error.error);
+ return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
+ _("Access to '%s' forbidden"),
+ handler->path);
+ default:
+ SVN_ERR(add_dir_ctx->server_error.error);
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Adding directory failed: %s on %s "
+ "(%d %s)"),
+ handler->method, handler->path,
+ add_dir_ctx->status, add_dir_ctx->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->commit->conn,
+ dir->relpath, dir->base_revision,
+ dir->commit->checked_in_url, dir->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));
+
+ proppatch_target = dir->checkout->resource_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("", 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->progress.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->checkout->resource_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->url =
+ svn_path_url_add_component2(dir->checkout->resource_url,
+ new_file->name, new_file->pool);
+ }
+
+ while (deleted_parent && deleted_parent[0] != '\0')
+ {
+ if (apr_hash_get(dir->commit->deleted_entries,
+ deleted_parent, APR_HASH_KEY_STRING))
+ {
+ 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__simple_request_context_t *head_ctx;
+ svn_ra_serf__handler_t *handler;
+
+ head_ctx = apr_pcalloc(new_file->pool, sizeof(*head_ctx));
+ head_ctx->pool = new_file->pool;
+
+ handler = apr_pcalloc(new_file->pool, sizeof(*handler));
+ 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__handle_status_only;
+ handler->response_baton = head_ctx;
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&head_ctx->done,
+ new_file->commit->session,
+ new_file->pool));
+
+ if (head_ctx->status != 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->url = new_file->checkout->resource_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_svndiff2(handler, handler_baton, ctx->stream, 0, 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("", 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 *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;
+ svn_ra_serf__simple_request_context_t *copy_ctx;
+ apr_uri_t uri;
+ const char *rel_copy_path, *basecoll_url, *req_url;
+
+ status = apr_uri_parse(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);
+ }
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &rel_copy_path,
+ ctx->commit->session,
+ ctx->commit->conn,
+ uri.path, ctx->copy_revision,
+ NULL, pool));
+ req_url = svn_path_url_add_component2(basecoll_url, rel_copy_path, pool);
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->method = "COPY";
+ handler->path = req_url;
+ handler->conn = ctx->commit->conn;
+ handler->session = ctx->commit->session;
+
+ copy_ctx = apr_pcalloc(pool, sizeof(*copy_ctx));
+ copy_ctx->pool = pool;
+
+ handler->response_handler = svn_ra_serf__handle_status_only;
+ handler->response_baton = copy_ctx;
+
+ handler->header_delegate = setup_copy_file_headers;
+ handler->header_delegate_baton = ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&copy_ctx->done,
+ ctx->commit->session, pool));
+
+ if (copy_ctx->status != 201 && copy_ctx->status != 204)
+ {
+ return return_response_err(handler, copy_ctx);
+ }
+ }
+
+ /* 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;
+ svn_ra_serf__simple_request_context_t *put_ctx;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->method = "PUT";
+ handler->path = ctx->url;
+ handler->conn = ctx->commit->conn;
+ handler->session = ctx->commit->session;
+
+ put_ctx = apr_pcalloc(pool, sizeof(*put_ctx));
+ put_ctx->pool = pool;
+
+ handler->response_handler = svn_ra_serf__handle_status_only;
+ handler->response_baton = put_ctx;
+
+ 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 = "application/vnd.svn-svndiff";
+ }
+
+ handler->header_delegate = setup_put_headers;
+ handler->header_delegate_baton = ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&put_ctx->done,
+ ctx->commit->session, pool));
+
+ if (put_ctx->status != 204 && put_ctx->status != 201)
+ {
+ return return_response_err(handler, put_ctx);
+ }
+ }
+
+ if (ctx->svndiff)
+ SVN_ERR(svn_io_file_close(ctx->svndiff, 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->progress.pool = 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;
+ svn_ra_serf__merge_context_t *merge_ctx;
+ svn_ra_serf__simple_request_context_t *delete_ctx;
+ svn_ra_serf__handler_t *handler;
+ svn_boolean_t *merge_done;
+ const char *merge_target =
+ ctx->activity_url ? ctx->activity_url : ctx->txn_url;
+
+ /* MERGE our activity */
+ SVN_ERR(svn_ra_serf__merge_create_req(&merge_ctx, ctx->session,
+ ctx->session->conns[0],
+ merge_target,
+ ctx->lock_tokens,
+ ctx->keep_locks,
+ pool));
+
+ merge_done = svn_ra_serf__merge_get_done_ptr(merge_ctx);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(merge_done, ctx->session, pool));
+
+ if (svn_ra_serf__merge_get_status(merge_ctx) != 200)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("MERGE request failed: returned %d "
+ "(during commit)"),
+ svn_ra_serf__merge_get_status(merge_ctx));
+ }
+
+ /* Inform the WC that we did a commit. */
+ if (ctx->callback)
+ SVN_ERR(ctx->callback(svn_ra_serf__merge_get_commit_info(merge_ctx),
+ ctx->callback_baton, pool));
+
+ /* If we're using activities, DELETE our completed activity. */
+ if (ctx->activity_url)
+ {
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->method = "DELETE";
+ handler->path = ctx->activity_url;
+ handler->conn = ctx->conn;
+ handler->session = ctx->session;
+
+ delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
+ delete_ctx->pool = pool;
+
+ handler->response_handler = svn_ra_serf__handle_status_only;
+ handler->response_baton = delete_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->done, ctx->session,
+ pool));
+
+ SVN_ERR_ASSERT(delete_ctx->status == 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;
+ svn_ra_serf__simple_request_context_t *delete_ctx;
+
+ /* 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->method = "DELETE";
+ handler->conn = ctx->session->conns[0];
+ handler->session = ctx->session;
+
+ delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx));
+ delete_ctx->pool = pool;
+
+ handler->response_handler = svn_ra_serf__handle_status_only;
+ handler->response_baton = delete_ctx;
+
+ if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */
+ handler->path = ctx->txn_url;
+ else
+ handler->path = ctx->activity_url;
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&delete_ctx->done, ctx->session,
+ pool));
+
+ /* 204 if deleted,
+ 403 if DELETE was forbidden (indicates MKACTIVITY was forbidden too),
+ 404 if the activity wasn't found. */
+ if (delete_ctx->status != 204 &&
+ delete_ctx->status != 403 &&
+ delete_ctx->status != 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;
+ apr_hash_index_t *hi;
+
+ ctx = apr_pcalloc(pool, sizeof(*ctx));
+
+ ctx->pool = pool;
+
+ ctx->session = session;
+ ctx->conn = session->conns[0];
+
+ ctx->revprop_table = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, revprop_table); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+
+ apr_hash_this(hi, &key, &klen, &val);
+ apr_hash_set(ctx->revprop_table, apr_pstrdup(pool, key), klen,
+ svn_string_dup(val, 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;
+
+ 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 *vcc_url, *proppatch_target, *ns;
+ apr_hash_t *props;
+ 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
+ {
+ svn_ra_serf__propfind_context_t *propfind_ctx;
+
+ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session,
+ commit->conn, pool));
+
+ props = apr_hash_make(pool);
+
+ /* ### switch to svn_ra_serf__retrieve_props */
+ SVN_ERR(svn_ra_serf__deliver_props(&propfind_ctx, props, commit->session,
+ commit->conn, vcc_url, rev, "0",
+ checked_in_props, NULL, pool));
+ SVN_ERR(svn_ra_serf__wait_for_props(propfind_ctx, commit->session, pool));
+
+ proppatch_target = svn_ra_serf__get_ver_prop(props, vcc_url, rev,
+ "DAV:", "href");
+ }
+
+ 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->progress.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("", 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("", 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..09db22c
--- /dev/null
+++ b/subversion/libsvn_ra_serf/get_deleted_rev.c
@@ -0,0 +1,239 @@
+/*
+ * 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.
+ */
+typedef enum drev_state_e {
+ NONE = 0,
+ VERSION_NAME
+} drev_state_e;
+
+typedef struct drev_context_t {
+ apr_pool_t *pool;
+
+ 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;
+
+ /* are we done? */
+ svn_boolean_t done;
+
+} drev_context_t;
+
+
+static void
+push_state(svn_ra_serf__xml_parser_t *parser,
+ drev_context_t *drev_ctx,
+ drev_state_e state)
+{
+ svn_ra_serf__xml_push_state(parser, state);
+
+ if (state == VERSION_NAME)
+ parser->state->private = NULL;
+}
+
+static svn_error_t *
+start_getdrev(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ drev_context_t *drev_ctx = userData;
+ drev_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE &&
+ strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
+ {
+ push_state(parser, drev_ctx, VERSION_NAME);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_getdrev(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ drev_context_t *drev_ctx = userData;
+ drev_state_e state;
+ svn_string_t *info;
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == VERSION_NAME &&
+ strcmp(name.name, SVN_DAV__VERSION_NAME) == 0 &&
+ info)
+ {
+ *drev_ctx->revision_deleted = SVN_STR_TO_REV(info->data);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_getdrev(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ drev_context_t *drev_ctx = userData;
+ drev_state_e state;
+
+ UNUSED_CTX(drev_ctx);
+
+ state = parser->state->current_state;
+ switch (state)
+ {
+ case VERSION_NAME:
+ parser->state->private = svn_string_ncreate(data, len,
+ parser->state->pool);
+ break;
+ default:
+ break;
+ }
+
+ 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_parser_t *parser_ctx;
+ const char *relative_url, *basecoll_url, *req_url;
+ int status_code = 0;
+ 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->pool = pool;
+ drev_ctx->revision_deleted = revision_deleted;
+ drev_ctx->done = FALSE;
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url,
+ ras, NULL, NULL, peg_revision, NULL,
+ pool));
+
+ req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = drev_ctx;
+ parser_ctx->start = start_getdrev;
+ parser_ctx->end = end_getdrev;
+ parser_ctx->cdata = cdata_getdrev;
+ parser_ctx->done = &drev_ctx->done;
+ parser_ctx->status_code = &status_code;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->body_delegate = create_getdrev_body;
+ handler->body_delegate_baton = drev_ctx;
+ handler->conn = ras->conns[0];
+ handler->session = ras;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&drev_ctx->done, ras, pool);
+
+ /* Map status 501: Method Not Implemented to our not implemented error.
+ 1.5.x servers and older don't support this report. */
+ if (status_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..3899ccf
--- /dev/null
+++ b/subversion/libsvn_ra_serf/getdate.c
@@ -0,0 +1,242 @@
+/*
+ * 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 <expat.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.
+ */
+typedef enum date_state_e {
+ NONE = 0,
+ VERSION_NAME
+} date_state_e;
+
+typedef struct date_info_t {
+ /* The currently collected value as we build it up */
+ const char *tmp;
+ apr_size_t tmp_len;
+} date_info_t;
+
+typedef struct date_context_t {
+ apr_pool_t *pool;
+
+ /* The time asked about. */
+ apr_time_t time;
+
+ /* What was the youngest revision at that time? */
+ svn_revnum_t *revision;
+
+ /* are we done? */
+ svn_boolean_t done;
+
+} date_context_t;
+
+
+static date_info_t *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ date_context_t *date_ctx,
+ date_state_e state)
+{
+ svn_ra_serf__xml_push_state(parser, state);
+
+ if (state == VERSION_NAME)
+ {
+ date_info_t *info;
+
+ info = apr_pcalloc(parser->state->pool, sizeof(*info));
+
+ parser->state->private = info;
+ }
+
+ return parser->state->private;
+}
+
+static svn_error_t *
+start_getdate(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ date_context_t *date_ctx = userData;
+ date_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE &&
+ strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
+ {
+ push_state(parser, date_ctx, VERSION_NAME);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_getdate(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ date_context_t *date_ctx = userData;
+ date_state_e state;
+ date_info_t *info;
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == VERSION_NAME &&
+ strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
+ {
+ *date_ctx->revision = SVN_STR_TO_REV(info->tmp);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_getdate(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ date_context_t *date_ctx = userData;
+ date_state_e state;
+ date_info_t *info;
+
+ UNUSED_CTX(date_ctx);
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ switch (state)
+ {
+ case VERSION_NAME:
+ svn_ra_serf__expand_string(&info->tmp, &info->tmp_len,
+ data, len, parser->state->pool);
+ break;
+ default:
+ break;
+ }
+
+ 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_parser_t *parser_ctx;
+ const char *report_target;
+ int status_code;
+
+ date_ctx = apr_pcalloc(pool, sizeof(*date_ctx));
+ date_ctx->pool = pool;
+ date_ctx->time = tm;
+ date_ctx->revision = revision;
+ date_ctx->done = FALSE;
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->method = "REPORT";
+ handler->path = report_target;
+ 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 = date_ctx;
+ parser_ctx->start = start_getdate;
+ parser_ctx->end = end_getdate;
+ parser_ctx->cdata = cdata_getdate;
+ parser_ctx->done = &date_ctx->done;
+ parser_ctx->status_code = &status_code;
+
+ handler->body_delegate = create_getdate_body;
+ handler->body_delegate_baton = date_ctx;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ *date_ctx->revision = SVN_INVALID_REVNUM;
+
+ return svn_ra_serf__context_run_wait(&date_ctx->done, session, pool);
+}
diff --git a/subversion/libsvn_ra_serf/getlocations.c b/subversion/libsvn_ra_serf/getlocations.c
new file mode 100644
index 0000000..6d3fa97
--- /dev/null
+++ b/subversion/libsvn_ra_serf/getlocations.c
@@ -0,0 +1,284 @@
+/*
+ * 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_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.
+ */
+typedef enum loc_state_e {
+ REPORT,
+ LOCATION
+} loc_state_e;
+
+typedef struct loc_state_list_t {
+ /* The current state that we are in now. */
+ loc_state_e state;
+
+ /* The previous state we were in. */
+ struct loc_state_list_t *prev;
+} loc_state_list_t;
+
+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;
+
+ /* Current state we're in */
+ loc_state_list_t *state;
+ loc_state_list_t *free_state;
+
+ int status_code;
+
+ svn_boolean_t done;
+} loc_context_t;
+
+
+static void
+push_state(loc_context_t *loc_ctx, loc_state_e state)
+{
+ loc_state_list_t *new_state;
+
+ if (!loc_ctx->free_state)
+ {
+ new_state = apr_palloc(loc_ctx->pool, sizeof(*loc_ctx->state));
+ }
+ else
+ {
+ new_state = loc_ctx->free_state;
+ loc_ctx->free_state = loc_ctx->free_state->prev;
+ }
+ new_state->state = state;
+
+ /* Add it to the state chain. */
+ new_state->prev = loc_ctx->state;
+ loc_ctx->state = new_state;
+}
+
+static void pop_state(loc_context_t *loc_ctx)
+{
+ loc_state_list_t *free_state;
+ free_state = loc_ctx->state;
+ /* advance the current state */
+ loc_ctx->state = loc_ctx->state->prev;
+ free_state->prev = loc_ctx->free_state;
+ loc_ctx->free_state = free_state;
+}
+
+static svn_error_t *
+start_getloc(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ loc_context_t *loc_ctx = userData;
+
+ if (!loc_ctx->state && strcmp(name.name, "get-locations-report") == 0)
+ {
+ push_state(loc_ctx, REPORT);
+ }
+ else if (loc_ctx->state &&
+ loc_ctx->state->state == REPORT &&
+ strcmp(name.name, "location") == 0)
+ {
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+ const char *revstr, *path;
+
+ revstr = svn_xml_get_attr_value("rev", attrs);
+ if (revstr)
+ {
+ rev = SVN_STR_TO_REV(revstr);
+ }
+
+ path = svn_xml_get_attr_value("path", attrs);
+
+ if (SVN_IS_VALID_REVNUM(rev) && path)
+ {
+ 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;
+}
+
+static svn_error_t *
+end_getloc(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ loc_context_t *loc_ctx = userData;
+ loc_state_list_t *cur_state;
+
+ if (!loc_ctx->state)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ cur_state = loc_ctx->state;
+
+ if (cur_state->state == REPORT &&
+ strcmp(name.name, "get-locations-report") == 0)
+ {
+ pop_state(loc_ctx);
+ }
+ else if (cur_state->state == LOCATION &&
+ strcmp(name.name, "location") == 0)
+ {
+ pop_state(loc_ctx);
+ }
+
+ 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_parser_t *parser_ctx;
+ const char *relative_url, *basecoll_url, *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->done = FALSE;
+ loc_ctx->paths = apr_hash_make(loc_ctx->pool);
+
+ *locations = loc_ctx->paths;
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
+ NULL, NULL, peg_revision, NULL,
+ pool));
+
+ req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ 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;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = loc_ctx;
+ parser_ctx->start = start_getloc;
+ parser_ctx->end = end_getloc;
+ parser_ctx->status_code = &loc_ctx->status_code;
+ parser_ctx->done = &loc_ctx->done;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&loc_ctx->done, session, pool);
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(loc_ctx->status_code,
+ req_url,
+ parser_ctx->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..5f2179d
--- /dev/null
+++ b/subversion/libsvn_ra_serf/getlocationsegments.c
@@ -0,0 +1,252 @@
+/*
+ * 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 <expat.h>
+#include <serf.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;
+
+ /* subpool used only as long as a single receiver invocation */
+ apr_pool_t *subpool;
+
+ /* True iff we're looking at a child of the outer report tag */
+ svn_boolean_t inside_report;
+
+ int status_code;
+
+ svn_boolean_t done;
+} gls_context_t;
+
+
+static svn_error_t *
+start_gls(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ gls_context_t *gls_ctx = userData;
+
+ if ((! gls_ctx->inside_report)
+ && strcmp(name.name, "get-location-segments-report") == 0)
+ {
+ gls_ctx->inside_report = TRUE;
+ }
+ else if (gls_ctx->inside_report
+ && strcmp(name.name, "location-segment") == 0)
+ {
+ const char *rev_str;
+ svn_revnum_t range_start = SVN_INVALID_REVNUM;
+ svn_revnum_t range_end = SVN_INVALID_REVNUM;
+ const char *path = NULL;
+
+ path = svn_xml_get_attr_value("path", attrs);
+ rev_str = svn_xml_get_attr_value("range-start", attrs);
+ if (rev_str)
+ range_start = SVN_STR_TO_REV(rev_str);
+ rev_str = svn_xml_get_attr_value("range-end", attrs);
+ if (rev_str)
+ range_end = SVN_STR_TO_REV(rev_str);
+
+ if (SVN_IS_VALID_REVNUM(range_start) && SVN_IS_VALID_REVNUM(range_end))
+ {
+ svn_location_segment_t *segment = apr_pcalloc(gls_ctx->subpool,
+ sizeof(*segment));
+ segment->path = path;
+ segment->range_start = range_start;
+ segment->range_end = range_end;
+ SVN_ERR(gls_ctx->receiver(segment,
+ gls_ctx->receiver_baton,
+ gls_ctx->subpool));
+ svn_pool_clear(gls_ctx->subpool);
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Expected valid revision range"));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_gls(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ gls_context_t *gls_ctx = userData;
+
+ if (strcmp(name.name, "get-location-segments-report") == 0)
+ gls_ctx->inside_report = FALSE;
+
+ 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_parser_t *parser_ctx;
+ const char *relative_url, *basecoll_url, *req_url;
+ svn_error_t *err, *err2;
+
+ 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;
+ gls_ctx->subpool = svn_pool_create(pool);
+ gls_ctx->inside_report = FALSE;
+ gls_ctx->done = FALSE;
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
+ NULL, NULL, peg_revision, NULL, pool));
+
+ req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ 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;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = gls_ctx;
+ parser_ctx->start = start_gls;
+ parser_ctx->end = end_gls;
+ parser_ctx->status_code = &gls_ctx->status_code;
+ parser_ctx->done = &gls_ctx->done;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&gls_ctx->done, session, pool);
+
+ if (gls_ctx->inside_report)
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
+ _("Location segment report failed on '%s'@'%ld'"),
+ path, peg_revision);
+
+ err2 = svn_ra_serf__error_on_status(gls_ctx->status_code,
+ handler->path,
+ parser_ctx->location);
+ if (err2)
+ {
+ /* Prefer err2 to err. */
+ svn_error_clear(err);
+ return err2;
+ }
+
+ svn_pool_destroy(gls_ctx->subpool);
+
+ if (err && (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, NULL);
+
+ return err;
+}
diff --git a/subversion/libsvn_ra_serf/getlocks.c b/subversion/libsvn_ra_serf/getlocks.c
new file mode 100644
index 0000000..719e4f0
--- /dev/null
+++ b/subversion/libsvn_ra_serf/getlocks.c
@@ -0,0 +1,372 @@
+/*
+ * 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_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.
+ */
+typedef enum lock_state_e {
+ NONE = 0,
+ REPORT,
+ LOCK,
+ PATH,
+ TOKEN,
+ OWNER,
+ COMMENT,
+ CREATION_DATE,
+ EXPIRATION_DATE
+} lock_state_e;
+
+typedef struct lock_info_t {
+ /* Temporary pool */
+ apr_pool_t *pool;
+
+ svn_lock_t *lock;
+
+ /* The currently collected value as we build it up */
+ const char *tmp;
+ apr_size_t tmp_len;
+
+} lock_info_t;
+
+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;
+
+ /* are we done? */
+ svn_boolean_t done;
+
+} lock_context_t;
+
+
+static lock_info_t *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ lock_context_t *lock_ctx,
+ lock_state_e state)
+{
+ svn_ra_serf__xml_push_state(parser, state);
+
+ if (state == LOCK)
+ {
+ lock_info_t *info;
+
+ info = apr_pcalloc(parser->state->pool, sizeof(*info));
+
+ info->pool = lock_ctx->pool;
+ info->lock = svn_lock_create(lock_ctx->pool);
+ info->lock->path =
+
+ parser->state->private = info;
+ }
+
+ return parser->state->private;
+}
+
+static svn_error_t *
+start_getlocks(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ lock_context_t *lock_ctx = userData;
+ lock_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE &&
+ strcmp(name.name, "get-locks-report") == 0)
+ {
+ push_state(parser, lock_ctx, REPORT);
+ }
+ else if (state == REPORT &&
+ strcmp(name.name, "lock") == 0)
+ {
+ push_state(parser, lock_ctx, LOCK);
+ }
+ else if (state == LOCK)
+ {
+ if (strcmp(name.name, "path") == 0)
+ {
+ push_state(parser, lock_ctx, PATH);
+ }
+ else if (strcmp(name.name, "token") == 0)
+ {
+ push_state(parser, lock_ctx, TOKEN);
+ }
+ else if (strcmp(name.name, "owner") == 0)
+ {
+ push_state(parser, lock_ctx, OWNER);
+ }
+ else if (strcmp(name.name, "comment") == 0)
+ {
+ push_state(parser, lock_ctx, COMMENT);
+ }
+ else if (strcmp(name.name, SVN_DAV__CREATIONDATE) == 0)
+ {
+ push_state(parser, lock_ctx, CREATION_DATE);
+ }
+ else if (strcmp(name.name, "expirationdate") == 0)
+ {
+ push_state(parser, lock_ctx, EXPIRATION_DATE);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_getlocks(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ lock_context_t *lock_ctx = userData;
+ lock_state_e state;
+ lock_info_t *info;
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == REPORT &&
+ strcmp(name.name, "get-locks-report") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == LOCK &&
+ strcmp(name.name, "lock") == 0)
+ {
+ /* 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, info->lock->path) == 0)
+ || (lock_ctx->requested_depth == svn_depth_infinity))
+ {
+ apr_hash_set(lock_ctx->hash, info->lock->path,
+ APR_HASH_KEY_STRING, info->lock);
+ }
+ else if ((lock_ctx->requested_depth == svn_depth_files) ||
+ (lock_ctx->requested_depth == svn_depth_immediates))
+ {
+ const char *rel_path = svn_fspath__is_child(lock_ctx->path,
+ info->lock->path,
+ info->pool);
+ if (rel_path && (svn_path_component_count(rel_path) == 1))
+ apr_hash_set(lock_ctx->hash, info->lock->path,
+ APR_HASH_KEY_STRING, info->lock);
+ }
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == PATH &&
+ strcmp(name.name, "path") == 0)
+ {
+ info->lock->path = apr_pstrmemdup(info->pool, info->tmp, info->tmp_len);
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == TOKEN &&
+ strcmp(name.name, "token") == 0)
+ {
+ info->lock->token = apr_pstrmemdup(info->pool, info->tmp, info->tmp_len);
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == OWNER &&
+ strcmp(name.name, "owner") == 0)
+ {
+ info->lock->owner = apr_pstrmemdup(info->pool, info->tmp, info->tmp_len);
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == COMMENT &&
+ strcmp(name.name, "comment") == 0)
+ {
+ info->lock->comment = apr_pstrmemdup(info->pool,
+ info->tmp, info->tmp_len);
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == CREATION_DATE &&
+ strcmp(name.name, SVN_DAV__CREATIONDATE) == 0)
+ {
+ SVN_ERR(svn_time_from_cstring(&info->lock->creation_date,
+ info->tmp, info->pool));
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == EXPIRATION_DATE &&
+ strcmp(name.name, "expirationdate") == 0)
+ {
+ SVN_ERR(svn_time_from_cstring(&info->lock->expiration_date,
+ info->tmp, info->pool));
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_getlocks(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ lock_context_t *lock_ctx = userData;
+ lock_state_e state;
+ lock_info_t *info;
+
+ UNUSED_CTX(lock_ctx);
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ switch (state)
+ {
+ case PATH:
+ case TOKEN:
+ case OWNER:
+ case COMMENT:
+ case CREATION_DATE:
+ case EXPIRATION_DATE:
+ svn_ra_serf__expand_string(&info->tmp, &info->tmp_len,
+ data, len, parser->state->pool);
+ break;
+ default:
+ break;
+ }
+
+ 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_parser_t *parser_ctx;
+ const char *req_url, *rel_path;
+ int status_code;
+
+ 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);
+ lock_ctx->done = FALSE;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ 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 = lock_ctx;
+ parser_ctx->start = start_getlocks;
+ parser_ctx->end = end_getlocks;
+ parser_ctx->cdata = cdata_getlocks;
+ parser_ctx->done = &lock_ctx->done;
+ parser_ctx->status_code = &status_code;
+
+ handler->body_delegate = create_getlocks_body;
+ handler->body_delegate_baton = lock_ctx;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ SVN_ERR(svn_ra_serf__context_run_wait(&lock_ctx->done, session, pool));
+
+ *locks = lock_ctx->hash;
+
+ 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..2aaf2fd
--- /dev/null
+++ b/subversion/libsvn_ra_serf/locks.c
@@ -0,0 +1,778 @@
+/*
+ * 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 <expat.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.
+ */
+typedef enum lock_state_e {
+ NONE = 0,
+ PROP,
+ LOCK_DISCOVERY,
+ ACTIVE_LOCK,
+ LOCK_TYPE,
+ LOCK_SCOPE,
+ DEPTH,
+ TIMEOUT,
+ LOCK_TOKEN,
+ COMMENT
+} lock_state_e;
+
+typedef struct lock_prop_info_t {
+ const char *data;
+ apr_size_t len;
+} lock_prop_info_t;
+
+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;
+
+ /* Our HTTP status code and reason. */
+ int status_code;
+ const char *reason;
+
+ /* The currently collected value as we build it up */
+ const char *tmp;
+ apr_size_t tmp_len;
+
+ /* are we done? */
+ svn_boolean_t done;
+} lock_info_t;
+
+
+static lock_prop_info_t*
+push_state(svn_ra_serf__xml_parser_t *parser,
+ lock_info_t *lock_ctx,
+ lock_state_e state)
+{
+ svn_ra_serf__xml_push_state(parser, state);
+ switch (state)
+ {
+ case LOCK_TYPE:
+ case LOCK_SCOPE:
+ case DEPTH:
+ case TIMEOUT:
+ case LOCK_TOKEN:
+ case COMMENT:
+ parser->state->private = apr_pcalloc(parser->state->pool,
+ sizeof(lock_prop_info_t));
+ break;
+ default:
+ break;
+ }
+
+ return parser->state->private;
+}
+
+/*
+ * Expat callback invoked on a start element tag for a PROPFIND response.
+ */
+static svn_error_t *
+start_lock(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ lock_info_t *ctx = userData;
+ lock_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE && strcmp(name.name, "prop") == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, PROP);
+ }
+ else if (state == PROP &&
+ strcmp(name.name, "lockdiscovery") == 0)
+ {
+ push_state(parser, ctx, LOCK_DISCOVERY);
+ }
+ else if (state == LOCK_DISCOVERY &&
+ strcmp(name.name, "activelock") == 0)
+ {
+ push_state(parser, ctx, ACTIVE_LOCK);
+ }
+ else if (state == ACTIVE_LOCK)
+ {
+ if (strcmp(name.name, "locktype") == 0)
+ {
+ push_state(parser, ctx, LOCK_TYPE);
+ }
+ else if (strcmp(name.name, "lockscope") == 0)
+ {
+ push_state(parser, ctx, LOCK_SCOPE);
+ }
+ else if (strcmp(name.name, "depth") == 0)
+ {
+ push_state(parser, ctx, DEPTH);
+ }
+ else if (strcmp(name.name, "timeout") == 0)
+ {
+ push_state(parser, ctx, TIMEOUT);
+ }
+ else if (strcmp(name.name, "locktoken") == 0)
+ {
+ push_state(parser, ctx, LOCK_TOKEN);
+ }
+ else if (strcmp(name.name, "owner") == 0)
+ {
+ push_state(parser, ctx, COMMENT);
+ }
+ }
+ else if (state == LOCK_TYPE)
+ {
+ if (strcmp(name.name, "write") == 0)
+ {
+ /* Do nothing. */
+ }
+ else
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+ else if (state == LOCK_SCOPE)
+ {
+ if (strcmp(name.name, "exclusive") == 0)
+ {
+ /* Do nothing. */
+ }
+ else
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on an end element tag for a PROPFIND response.
+ */
+static svn_error_t *
+end_lock(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ lock_info_t *ctx = userData;
+ lock_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == PROP &&
+ strcmp(name.name, "prop") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == LOCK_DISCOVERY &&
+ strcmp(name.name, "lockdiscovery") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == ACTIVE_LOCK &&
+ strcmp(name.name, "activelock") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == LOCK_TYPE &&
+ strcmp(name.name, "locktype") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == LOCK_SCOPE &&
+ strcmp(name.name, "lockscope") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == DEPTH &&
+ strcmp(name.name, "depth") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == TIMEOUT &&
+ strcmp(name.name, "timeout") == 0)
+ {
+ lock_prop_info_t *info = parser->state->private;
+
+ if (strcmp(info->data, "Infinite") == 0)
+ {
+ ctx->lock->expiration_date = 0;
+ }
+ else
+ {
+ SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date,
+ info->data, ctx->pool));
+ }
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == LOCK_TOKEN &&
+ strcmp(name.name, "locktoken") == 0)
+ {
+ lock_prop_info_t *info = parser->state->private;
+
+ if (!ctx->lock->token && info->len)
+ {
+ apr_collapse_spaces((char*)info->data, info->data);
+ ctx->lock->token = apr_pstrndup(ctx->pool, info->data, info->len);
+ }
+ /* We don't actually need the lock token. */
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == COMMENT &&
+ strcmp(name.name, "owner") == 0)
+ {
+ lock_prop_info_t *info = parser->state->private;
+
+ if (info->len)
+ {
+ ctx->lock->comment = apr_pstrndup(ctx->pool, info->data, info->len);
+ }
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_lock(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ lock_info_t *lock_ctx = userData;
+ lock_state_e state;
+ lock_prop_info_t *info;
+
+ UNUSED_CTX(lock_ctx);
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ switch (state)
+ {
+ case LOCK_TYPE:
+ case LOCK_SCOPE:
+ case DEPTH:
+ case TIMEOUT:
+ case LOCK_TOKEN:
+ case COMMENT:
+ svn_ra_serf__expand_string(&info->data, &info->len,
+ data, len, parser->state->pool);
+ break;
+ default:
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra_serf__dav_props_t lock_props[] =
+{
+ { "DAV:", "lockdiscovery" },
+ { NULL }
+};
+
+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;
+}
+
+/* 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)
+{
+ svn_ra_serf__xml_parser_t *xml_ctx = handler_baton;
+ lock_info_t *ctx = xml_ctx->user_data;
+ svn_error_t *err;
+
+ if (ctx->read_headers == FALSE)
+ {
+ serf_bucket_t *headers;
+ const char *val;
+
+ serf_status_line sl;
+ apr_status_t status;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_error_wrap_apr(status, NULL);
+ }
+
+ ctx->status_code = sl.code;
+ ctx->reason = sl.reason;
+
+ /* 423 == Locked */
+ if (sl.code == 423)
+ {
+ /* Older servers may not give a descriptive error, so we'll
+ make one of our own if we can't find one in the response. */
+ err = svn_ra_serf__handle_server_error(request, response, pool);
+ if (!err)
+ {
+ err = svn_error_createf(SVN_ERR_FS_PATH_ALREADY_LOCKED,
+ NULL,
+ _("Lock request failed: %d %s"),
+ ctx->status_code, ctx->reason);
+ }
+ return err;
+ }
+
+ 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;
+ }
+
+ /* Forbidden when a lock doesn't exist. */
+ if (ctx->status_code == 403)
+ {
+ /* If we get an "unexpected EOF" error, we'll wrap it with
+ generic request failure error. */
+ err = svn_ra_serf__handle_discard_body(request, response, NULL, pool);
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ {
+ ctx->done = TRUE;
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED,
+ err,
+ _("Lock request failed: %d %s"),
+ ctx->status_code, ctx->reason);
+ }
+ return err;
+ }
+
+ return svn_ra_serf__handle_xml_parser(request, response,
+ handler_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_set(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_parser_t *parser_ctx;
+ lock_info_t *lock_ctx;
+ const char *req_url;
+ svn_error_t *err;
+ int status_code;
+
+ 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 = path;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->method = "PROPFIND";
+ handler->path = req_url;
+ 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 = lock_ctx;
+ parser_ctx->start = start_lock;
+ parser_ctx->end = end_lock;
+ parser_ctx->cdata = cdata_lock;
+ parser_ctx->done = &lock_ctx->done;
+ parser_ctx->status_code = &status_code;
+
+ handler->body_delegate = create_getlock_body;
+ handler->body_delegate_baton = lock_ctx;
+
+ handler->header_delegate = setup_getlock_headers;
+ handler->header_delegate_baton = lock_ctx;
+
+ handler->response_handler = handle_lock;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+ err = svn_ra_serf__context_run_wait(&lock_ctx->done, session, pool);
+
+ if (status_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 *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool;
+
+ subpool = svn_pool_create(pool);
+
+ for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
+ {
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ const char *req_url;
+ lock_info_t *lock_ctx;
+ const void *key;
+ void *val;
+ svn_error_t *err;
+ svn_error_t *new_err = NULL;
+
+ svn_pool_clear(subpool);
+
+ lock_ctx = apr_pcalloc(subpool, sizeof(*lock_ctx));
+
+ apr_hash_this(hi, &key, NULL, &val);
+ lock_ctx->pool = subpool;
+ lock_ctx->path = key;
+ lock_ctx->revision = *((svn_revnum_t*)val);
+ lock_ctx->lock = svn_lock_create(subpool);
+ lock_ctx->lock->path = key;
+ lock_ctx->lock->comment = comment;
+
+ lock_ctx->force = force;
+ req_url = svn_path_url_add_component2(session->session_url.path,
+ lock_ctx->path, subpool);
+
+ handler = apr_pcalloc(subpool, sizeof(*handler));
+
+ handler->method = "LOCK";
+ handler->path = req_url;
+ handler->body_type = "text/xml";
+ handler->conn = session->conns[0];
+ handler->session = session;
+
+ parser_ctx = apr_pcalloc(subpool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = subpool;
+ parser_ctx->user_data = lock_ctx;
+ parser_ctx->start = start_lock;
+ parser_ctx->end = end_lock;
+ parser_ctx->cdata = cdata_lock;
+ parser_ctx->done = &lock_ctx->done;
+
+ handler->header_delegate = set_lock_headers;
+ handler->header_delegate_baton = lock_ctx;
+
+ handler->body_delegate = create_lock_body;
+ handler->body_delegate_baton = lock_ctx;
+
+ handler->response_handler = handle_lock;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+ err = svn_ra_serf__context_run_wait(&lock_ctx->done, session, subpool);
+
+ if (lock_func)
+ new_err = lock_func(lock_baton, lock_ctx->path, TRUE, lock_ctx->lock,
+ err, subpool);
+ svn_error_clear(err);
+
+ SVN_ERR(new_err);
+ }
+
+ 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 *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+ apr_hash_index_t *hi;
+ apr_pool_t *subpool;
+
+ subpool = svn_pool_create(pool);
+
+ for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
+ {
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__simple_request_context_t *ctx;
+ const char *req_url, *path, *token;
+ const void *key;
+ void *val;
+ svn_lock_t *existing_lock = NULL;
+ struct unlock_context_t unlock_ctx;
+ svn_error_t *lock_err = NULL;
+
+ svn_pool_clear(subpool);
+
+ ctx = apr_pcalloc(subpool, sizeof(*ctx));
+ ctx->pool = subpool;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ path = key;
+ token = val;
+
+ if (force && (!token || token[0] == '\0'))
+ {
+ SVN_ERR(svn_ra_serf__get_lock(ra_session, &existing_lock, path,
+ subpool));
+ token = existing_lock->token;
+ if (!token)
+ {
+ svn_error_t *err;
+
+ 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, subpool);
+ svn_error_clear(err);
+ if (err2)
+ return err2;
+ }
+ continue;
+ }
+ }
+
+ unlock_ctx.force = force;
+ unlock_ctx.token = apr_pstrcat(subpool, "<", token, ">", (char *)NULL);
+
+ req_url = svn_path_url_add_component2(session->session_url.path, path,
+ subpool);
+
+ handler = apr_pcalloc(subpool, sizeof(*handler));
+
+ 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__handle_status_only;
+ handler->response_baton = ctx;
+
+ svn_ra_serf__request_create(handler);
+ SVN_ERR(svn_ra_serf__context_run_wait(&ctx->done, session, subpool));
+
+ switch (ctx->status)
+ {
+ case 204:
+ break; /* OK */
+ case 403:
+ /* Api users expect this specific error code to detect failures */
+ lock_err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
+ _("Unlock request failed: %d %s"),
+ ctx->status, ctx->reason);
+ break;
+ default:
+ lock_err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("Unlock request failed: %d %s"),
+ ctx->status, ctx->reason);
+ }
+
+ if (lock_func)
+ {
+ SVN_ERR(lock_func(lock_baton, path, FALSE, existing_lock,
+ lock_err, subpool));
+ svn_error_clear(lock_err);
+ }
+ }
+
+ 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..cd6a6b2
--- /dev/null
+++ b/subversion/libsvn_ra_serf/log.c
@@ -0,0 +1,733 @@
+/*
+ * 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 <expat.h>
+
+#include <serf.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 "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.
+ */
+typedef enum log_state_e {
+ NONE = 0,
+ REPORT,
+ ITEM,
+ VERSION,
+ CREATOR,
+ DATE,
+ COMMENT,
+ REVPROP,
+ HAS_CHILDREN,
+ ADDED_PATH,
+ REPLACED_PATH,
+ DELETED_PATH,
+ MODIFIED_PATH,
+ SUBTRACTIVE_MERGE
+} log_state_e;
+
+typedef struct log_info_t {
+ apr_pool_t *pool;
+
+ /* The currently collected value as we build it up, and its wire
+ * encoding (if any).
+ */
+ const char *tmp;
+ apr_size_t tmp_len;
+ const char *tmp_encoding;
+
+ /* Temporary change path - ultimately inserted into changed_paths hash. */
+ svn_log_changed_path2_t *tmp_path;
+
+ /* Log information */
+ svn_log_entry_t *log_entry;
+
+ /* Place to hold revprop name. */
+ const char *revprop_name;
+} log_info_t;
+
+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 */
+
+ /* are we done? */
+ svn_boolean_t done;
+ int status_code;
+
+ /* 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;
+
+
+static log_info_t *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ log_context_t *log_ctx,
+ log_state_e state,
+ const char **attrs)
+{
+ svn_ra_serf__xml_push_state(parser, state);
+
+ if (state == ITEM)
+ {
+ log_info_t *info;
+ apr_pool_t *info_pool = svn_pool_create(parser->state->pool);
+
+ info = apr_pcalloc(info_pool, sizeof(*info));
+ info->pool = info_pool;
+ info->log_entry = svn_log_entry_create(info_pool);
+
+ info->log_entry->revision = SVN_INVALID_REVNUM;
+
+ parser->state->private = info;
+ }
+
+ if (state == ADDED_PATH || state == REPLACED_PATH ||
+ state == DELETED_PATH || state == MODIFIED_PATH)
+ {
+ log_info_t *info = parser->state->private;
+
+ if (!info->log_entry->changed_paths2)
+ {
+ info->log_entry->changed_paths2 = apr_hash_make(info->pool);
+ info->log_entry->changed_paths = info->log_entry->changed_paths2;
+ }
+
+ info->tmp_path = svn_log_changed_path2_create(info->pool);
+ info->tmp_path->copyfrom_rev = SVN_INVALID_REVNUM;
+ }
+
+ if (state == CREATOR || state == DATE || state == COMMENT
+ || state == REVPROP)
+ {
+ log_info_t *info = parser->state->private;
+
+ info->tmp_encoding = svn_xml_get_attr_value("encoding", attrs);
+ if (info->tmp_encoding)
+ info->tmp_encoding = apr_pstrdup(info->pool, info->tmp_encoding);
+
+ if (!info->log_entry->revprops)
+ {
+ info->log_entry->revprops = apr_hash_make(info->pool);
+ }
+ }
+
+ return parser->state->private;
+}
+
+/* Helper function to parse the common arguments availabe in ATTRS into CHANGE. */
+static svn_error_t *
+read_changed_path_attributes(svn_log_changed_path2_t *change, const char **attrs)
+{
+ /* All these arguments are optional. The *_from_word() functions can handle
+ them for us */
+
+ change->node_kind = svn_node_kind_from_word(
+ svn_xml_get_attr_value("node-kind", attrs));
+ change->text_modified = svn_tristate__from_word(
+ svn_xml_get_attr_value("text-mods", attrs));
+ change->props_modified = svn_tristate__from_word(
+ svn_xml_get_attr_value("prop-mods", attrs));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+start_log(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ log_context_t *log_ctx = userData;
+ log_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE &&
+ strcmp(name.name, "log-report") == 0)
+ {
+ push_state(parser, log_ctx, REPORT, attrs);
+ }
+ else if (state == REPORT &&
+ strcmp(name.name, "log-item") == 0)
+ {
+ push_state(parser, log_ctx, ITEM, attrs);
+ }
+ else if (state == ITEM)
+ {
+ log_info_t *info;
+
+ if (strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
+ {
+ push_state(parser, log_ctx, VERSION, attrs);
+ }
+ else if (strcmp(name.name, "creator-displayname") == 0)
+ {
+ info = push_state(parser, log_ctx, CREATOR, attrs);
+ }
+ else if (strcmp(name.name, "date") == 0)
+ {
+ info = push_state(parser, log_ctx, DATE, attrs);
+ }
+ else if (strcmp(name.name, "comment") == 0)
+ {
+ info = push_state(parser, log_ctx, COMMENT, attrs);
+ }
+ else if (strcmp(name.name, "revprop") == 0)
+ {
+ const char *revprop_name =
+ svn_xml_get_attr_value("name", attrs);
+ if (revprop_name == NULL)
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Missing name attr in revprop element"));
+ info = push_state(parser, log_ctx, REVPROP, attrs);
+ info->revprop_name = apr_pstrdup(info->pool, revprop_name);
+ }
+ else if (strcmp(name.name, "has-children") == 0)
+ {
+ push_state(parser, log_ctx, HAS_CHILDREN, attrs);
+ }
+ else if (strcmp(name.name, "subtractive-merge") == 0)
+ {
+ push_state(parser, log_ctx, SUBTRACTIVE_MERGE, attrs);
+ }
+ else if (strcmp(name.name, "added-path") == 0)
+ {
+ const char *copy_path, *copy_rev_str;
+
+ info = push_state(parser, log_ctx, ADDED_PATH, attrs);
+ info->tmp_path->action = 'A';
+
+ copy_path = svn_xml_get_attr_value("copyfrom-path", attrs);
+ copy_rev_str = svn_xml_get_attr_value("copyfrom-rev", attrs);
+ if (copy_path && copy_rev_str)
+ {
+ svn_revnum_t copy_rev;
+
+ copy_rev = SVN_STR_TO_REV(copy_rev_str);
+ if (SVN_IS_VALID_REVNUM(copy_rev))
+ {
+ info->tmp_path->copyfrom_path = apr_pstrdup(info->pool,
+ copy_path);
+ info->tmp_path->copyfrom_rev = copy_rev;
+ }
+ }
+
+ SVN_ERR(read_changed_path_attributes(info->tmp_path, attrs));
+ }
+ else if (strcmp(name.name, "replaced-path") == 0)
+ {
+ const char *copy_path, *copy_rev_str;
+
+ info = push_state(parser, log_ctx, REPLACED_PATH, attrs);
+ info->tmp_path->action = 'R';
+
+ copy_path = svn_xml_get_attr_value("copyfrom-path", attrs);
+ copy_rev_str = svn_xml_get_attr_value("copyfrom-rev", attrs);
+ if (copy_path && copy_rev_str)
+ {
+ svn_revnum_t copy_rev;
+
+ copy_rev = SVN_STR_TO_REV(copy_rev_str);
+ if (SVN_IS_VALID_REVNUM(copy_rev))
+ {
+ info->tmp_path->copyfrom_path = apr_pstrdup(info->pool,
+ copy_path);
+ info->tmp_path->copyfrom_rev = copy_rev;
+ }
+ }
+
+ SVN_ERR(read_changed_path_attributes(info->tmp_path, attrs));
+ }
+ else if (strcmp(name.name, "deleted-path") == 0)
+ {
+ info = push_state(parser, log_ctx, DELETED_PATH, attrs);
+ info->tmp_path->action = 'D';
+
+ SVN_ERR(read_changed_path_attributes(info->tmp_path, attrs));
+ }
+ else if (strcmp(name.name, "modified-path") == 0)
+ {
+ info = push_state(parser, log_ctx, MODIFIED_PATH, attrs);
+ info->tmp_path->action = 'M';
+
+ SVN_ERR(read_changed_path_attributes(info->tmp_path, attrs));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Set *DECODED_CDATA to a copy of current CDATA being tracked in INFO,
+ * decoded as necessary, and allocated from INFO->pool..
+ */
+static svn_error_t *
+maybe_decode_log_cdata(const svn_string_t **decoded_cdata,
+ log_info_t *info)
+{
+ if (info->tmp_encoding)
+ {
+ svn_string_t in;
+ in.data = info->tmp;
+ in.len = info->tmp_len;
+
+ /* Check for a known encoding type. This is easy -- there's
+ only one. */
+ if (strcmp(info->tmp_encoding, "base64") != 0)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unsupported encoding '%s'"),
+ info->tmp_encoding);
+ }
+
+ *decoded_cdata = svn_base64_decode_string(&in, info->pool);
+ }
+ else
+ {
+ *decoded_cdata = svn_string_ncreate(info->tmp, info->tmp_len,
+ info->pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_log(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ log_context_t *log_ctx = userData;
+ log_state_e state;
+ log_info_t *info;
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == REPORT &&
+ strcmp(name.name, "log-report") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == ITEM &&
+ strcmp(name.name, "log-item") == 0)
+ {
+ if (log_ctx->limit && (log_ctx->nest_level == 0)
+ && (++log_ctx->count > log_ctx->limit))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* Give the info to the reporter */
+ SVN_ERR(log_ctx->receiver(log_ctx->receiver_baton,
+ info->log_entry,
+ info->pool));
+
+ if (info->log_entry->has_children)
+ {
+ log_ctx->nest_level++;
+ }
+ if (! SVN_IS_VALID_REVNUM(info->log_entry->revision))
+ {
+ SVN_ERR_ASSERT(log_ctx->nest_level);
+ log_ctx->nest_level--;
+ }
+
+ svn_pool_destroy(info->pool);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == VERSION &&
+ strcmp(name.name, SVN_DAV__VERSION_NAME) == 0)
+ {
+ info->log_entry->revision = SVN_STR_TO_REV(info->tmp);
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == CREATOR &&
+ strcmp(name.name, "creator-displayname") == 0)
+ {
+ if (log_ctx->want_author)
+ {
+ const svn_string_t *decoded_cdata;
+ SVN_ERR(maybe_decode_log_cdata(&decoded_cdata, info));
+ apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_AUTHOR,
+ APR_HASH_KEY_STRING, decoded_cdata);
+ }
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == DATE &&
+ strcmp(name.name, "date") == 0)
+ {
+ if (log_ctx->want_date)
+ {
+ const svn_string_t *decoded_cdata;
+ SVN_ERR(maybe_decode_log_cdata(&decoded_cdata, info));
+ apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_DATE,
+ APR_HASH_KEY_STRING, decoded_cdata);
+ }
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == COMMENT &&
+ strcmp(name.name, "comment") == 0)
+ {
+ if (log_ctx->want_message)
+ {
+ const svn_string_t *decoded_cdata;
+ SVN_ERR(maybe_decode_log_cdata(&decoded_cdata, info));
+ apr_hash_set(info->log_entry->revprops, SVN_PROP_REVISION_LOG,
+ APR_HASH_KEY_STRING, decoded_cdata);
+ }
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == REVPROP)
+ {
+ const svn_string_t *decoded_cdata;
+ SVN_ERR(maybe_decode_log_cdata(&decoded_cdata, info));
+ apr_hash_set(info->log_entry->revprops, info->revprop_name,
+ APR_HASH_KEY_STRING, decoded_cdata);
+ info->tmp_len = 0;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == HAS_CHILDREN &&
+ strcmp(name.name, "has-children") == 0)
+ {
+ info->log_entry->has_children = TRUE;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == SUBTRACTIVE_MERGE &&
+ strcmp(name.name, "subtractive-merge") == 0)
+ {
+ info->log_entry->subtractive_merge = TRUE;
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if ((state == ADDED_PATH &&
+ strcmp(name.name, "added-path") == 0) ||
+ (state == DELETED_PATH &&
+ strcmp(name.name, "deleted-path") == 0) ||
+ (state == MODIFIED_PATH &&
+ strcmp(name.name, "modified-path") == 0) ||
+ (state == REPLACED_PATH &&
+ strcmp(name.name, "replaced-path") == 0))
+ {
+ char *path;
+
+ path = apr_pstrmemdup(info->pool, info->tmp, info->tmp_len);
+ info->tmp_len = 0;
+
+ apr_hash_set(info->log_entry->changed_paths2, path, APR_HASH_KEY_STRING,
+ info->tmp_path);
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_log(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ log_context_t *log_ctx = userData;
+ log_state_e state;
+ log_info_t *info;
+
+ UNUSED_CTX(log_ctx);
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ switch (state)
+ {
+ case VERSION:
+ case CREATOR:
+ case DATE:
+ case COMMENT:
+ case REVPROP:
+ case ADDED_PATH:
+ case REPLACED_PATH:
+ case DELETED_PATH:
+ case MODIFIED_PATH:
+ svn_ra_serf__expand_string(&info->tmp, &info->tmp_len,
+ data, len, info->pool);
+ break;
+ default:
+ break;
+ }
+
+ 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_parser_t *parser_ctx;
+ svn_boolean_t want_custom_revprops;
+ svn_revnum_t peg_rev;
+ svn_error_t *err;
+ const char *relative_url, *basecoll_url, *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;
+ log_ctx->done = FALSE;
+
+ 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_baseline_info(&basecoll_url, &relative_url, session,
+ NULL, NULL, peg_rev, NULL, pool));
+
+ req_url = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ 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;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = log_ctx;
+ parser_ctx->start = start_log;
+ parser_ctx->end = end_log;
+ parser_ctx->cdata = cdata_log;
+ parser_ctx->done = &log_ctx->done;
+ parser_ctx->status_code = &log_ctx->status_code;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&log_ctx->done, session, pool);
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(log_ctx->status_code,
+ req_url,
+ parser_ctx->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..1d3bd1a
--- /dev/null
+++ b/subversion/libsvn_ra_serf/merge.c
@@ -0,0 +1,589 @@
+/*
+ * 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_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 {
+ NONE = 0,
+ MERGE_RESPONSE,
+ UPDATED_SET,
+ RESPONSE,
+ HREF,
+ PROPSTAT,
+ PROP,
+ RESOURCE_TYPE,
+ AUTHOR,
+ NAME,
+ DATE,
+ IGNORE_PROP_NAME,
+ NEED_PROP_NAME,
+ PROP_VAL
+} merge_state_e;
+
+typedef enum resource_type_e {
+ UNSET,
+ BASELINE,
+ COLLECTION,
+ CHECKED_IN
+} resource_type_e;
+
+typedef struct merge_info_t {
+ /* Temporary allocations here please */
+ apr_pool_t *pool;
+
+ resource_type_e type;
+
+ apr_hash_t *props;
+
+ const char *prop_ns;
+ const char *prop_name;
+ const char *prop_val;
+ apr_size_t prop_val_len;
+} merge_info_t;
+
+/* Structure associated with a MERGE request. */
+struct svn_ra_serf__merge_context_t
+{
+ apr_pool_t *pool;
+
+ svn_ra_serf__session_t *session;
+
+ 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. */
+
+ int status;
+
+ svn_boolean_t done;
+
+ svn_commit_info_t *commit_info;
+};
+
+
+static merge_info_t *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__merge_context_t *ctx,
+ merge_state_e state)
+{
+ merge_info_t *info;
+
+ svn_ra_serf__xml_push_state(parser, state);
+
+ if (state == RESPONSE)
+ {
+ info = apr_palloc(parser->state->pool, sizeof(*info));
+ info->pool = parser->state->pool;
+ info->props = apr_hash_make(info->pool);
+
+ parser->state->private = info;
+ }
+
+ return parser->state->private;
+}
+
+static svn_error_t *
+start_merge(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ svn_ra_serf__merge_context_t *ctx = userData;
+ merge_state_e state;
+ merge_info_t *info;
+
+ state = parser->state->current_state;
+
+ if (state == NONE &&
+ strcmp(name.name, "merge-response") == 0)
+ {
+ push_state(parser, ctx, MERGE_RESPONSE);
+ }
+ else if (state == NONE)
+ {
+ /* do nothing as we haven't seen our valid start tag yet. */
+ }
+ else if (state == MERGE_RESPONSE &&
+ strcmp(name.name, "updated-set") == 0)
+ {
+ push_state(parser, ctx, UPDATED_SET);
+ }
+ else if (state == UPDATED_SET &&
+ strcmp(name.name, "response") == 0)
+ {
+ push_state(parser, ctx, RESPONSE);
+ }
+ else if (state == RESPONSE &&
+ strcmp(name.name, "href") == 0)
+ {
+ info = push_state(parser, ctx, PROP_VAL);
+
+ info->prop_ns = name.namespace;
+ info->prop_name = apr_pstrdup(info->pool, name.name);
+ info->prop_val = NULL;
+ info->prop_val_len = 0;
+ }
+ else if (state == RESPONSE &&
+ strcmp(name.name, "propstat") == 0)
+ {
+ push_state(parser, ctx, PROPSTAT);
+ }
+ else if (state == PROPSTAT &&
+ strcmp(name.name, "prop") == 0)
+ {
+ push_state(parser, ctx, PROP);
+ }
+ else if (state == PROPSTAT &&
+ strcmp(name.name, "status") == 0)
+ {
+ /* Do nothing for now. */
+ }
+ else if (state == PROP &&
+ strcmp(name.name, "resourcetype") == 0)
+ {
+ info = push_state(parser, ctx, RESOURCE_TYPE);
+ info->type = UNSET;
+ }
+ else if (state == RESOURCE_TYPE &&
+ strcmp(name.name, "baseline") == 0)
+ {
+ info = parser->state->private;
+
+ info->type = BASELINE;
+ }
+ else if (state == RESOURCE_TYPE &&
+ strcmp(name.name, "collection") == 0)
+ {
+ info = parser->state->private;
+
+ info->type = COLLECTION;
+ }
+ else if (state == PROP &&
+ strcmp(name.name, "checked-in") == 0)
+ {
+ info = push_state(parser, ctx, IGNORE_PROP_NAME);
+
+ info->prop_ns = name.namespace;
+ info->prop_name = apr_pstrdup(info->pool, name.name);
+ info->prop_val = NULL;
+ info->prop_val_len = 0;
+ }
+ else if (state == PROP)
+ {
+ push_state(parser, ctx, PROP_VAL);
+ }
+ else if (state == IGNORE_PROP_NAME)
+ {
+ push_state(parser, ctx, PROP_VAL);
+ }
+ else if (state == NEED_PROP_NAME)
+ {
+ info = push_state(parser, ctx, PROP_VAL);
+ info->prop_ns = name.namespace;
+ info->prop_name = apr_pstrdup(info->pool, name.name);
+ info->prop_val = NULL;
+ info->prop_val_len = 0;
+ }
+ else
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_merge(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ svn_ra_serf__merge_context_t *ctx = userData;
+ merge_state_e state;
+ merge_info_t *info;
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == NONE)
+ {
+ /* nothing to close yet. */
+ return SVN_NO_ERROR;
+ }
+
+ if (state == RESPONSE &&
+ strcmp(name.name, "response") == 0)
+ {
+ if (info->type == BASELINE)
+ {
+ const char *str;
+
+ str = apr_hash_get(info->props, SVN_DAV__VERSION_NAME,
+ APR_HASH_KEY_STRING);
+ if (str)
+ {
+ ctx->commit_info->revision = SVN_STR_TO_REV(str);
+ }
+ else
+ {
+ ctx->commit_info->revision = SVN_INVALID_REVNUM;
+ }
+
+ ctx->commit_info->date =
+ apr_pstrdup(ctx->pool,
+ apr_hash_get(info->props, SVN_DAV__CREATIONDATE,
+ APR_HASH_KEY_STRING));
+
+ ctx->commit_info->author =
+ apr_pstrdup(ctx->pool,
+ apr_hash_get(info->props, "creator-displayname",
+ APR_HASH_KEY_STRING));
+
+ ctx->commit_info->post_commit_err =
+ apr_pstrdup(ctx->pool,
+ apr_hash_get(info->props,
+ "post-commit-err", APR_HASH_KEY_STRING));
+ }
+ else
+ {
+ const char *href;
+
+ href = apr_hash_get(info->props, "href", APR_HASH_KEY_STRING);
+ if (! svn_urlpath__is_ancestor(ctx->merge_url, href))
+ {
+ 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, 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(ctx->session))
+ && ctx->session->wc_callbacks->push_wc_prop)
+ {
+ svn_string_t checked_in_str;
+ const char *checked_in;
+
+ /* From the above check, we know that CTX->MERGE_URL is
+ an ancestor of HREF. All that remains is to
+ determine of HREF is the same as CTX->MERGE_URL, or --
+ if not -- is relative value as a child thereof. */
+ href = svn_urlpath__is_child(ctx->merge_url, href, NULL);
+ if (! href)
+ href = "";
+
+ checked_in = apr_hash_get(info->props, "checked-in",
+ APR_HASH_KEY_STRING);
+ checked_in_str.data = checked_in;
+ checked_in_str.len = strlen(checked_in);
+
+ SVN_ERR(ctx->session->wc_callbacks->push_wc_prop(
+ ctx->session->wc_callback_baton, href,
+ SVN_RA_SERF__WC_CHECKED_IN_URL, &checked_in_str, info->pool));
+ }
+ }
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == PROPSTAT &&
+ strcmp(name.name, "propstat") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == PROP &&
+ strcmp(name.name, "prop") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == RESOURCE_TYPE &&
+ strcmp(name.name, "resourcetype") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == PROP_VAL)
+ {
+ if (!info->prop_name)
+ {
+ info->prop_name = apr_pstrdup(info->pool, name.name);
+ }
+ info->prop_val = apr_pstrmemdup(info->pool, info->prop_val,
+ info->prop_val_len);
+ if (strcmp(info->prop_name, "href") == 0)
+ info->prop_val = svn_urlpath__canonicalize(info->prop_val,
+ info->pool);
+
+ /* Set our property. */
+ apr_hash_set(info->props, info->prop_name, APR_HASH_KEY_STRING,
+ info->prop_val);
+
+ info->prop_ns = NULL;
+ info->prop_name = NULL;
+ info->prop_val = NULL;
+ info->prop_val_len = 0;
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_merge(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ svn_ra_serf__merge_context_t *ctx = userData;
+ merge_state_e state;
+ merge_info_t *info;
+
+ UNUSED_CTX(ctx);
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == PROP_VAL)
+ {
+ svn_ra_serf__expand_string(&info->prop_val, &info->prop_val_len,
+ data, len, parser->state->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_merge_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__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__is_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)
+{
+ svn_ra_serf__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__merge_create_req(svn_ra_serf__merge_context_t **ret_ctx,
+ 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 *pool)
+{
+ svn_ra_serf__merge_context_t *merge_ctx;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+
+ merge_ctx = apr_pcalloc(pool, sizeof(*merge_ctx));
+
+ merge_ctx->pool = 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(pool);
+
+ merge_ctx->merge_url = session->session_url.path;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ 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;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = merge_ctx;
+ parser_ctx->start = start_merge;
+ parser_ctx->end = end_merge;
+ parser_ctx->cdata = cdata_merge;
+ parser_ctx->done = &merge_ctx->done;
+ parser_ctx->status_code = &merge_ctx->status;
+
+ handler->header_delegate = setup_merge_headers;
+ handler->header_delegate_baton = merge_ctx;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ *ret_ctx = merge_ctx;
+
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t*
+svn_ra_serf__merge_get_done_ptr(svn_ra_serf__merge_context_t *ctx)
+{
+ return &ctx->done;
+}
+
+svn_commit_info_t*
+svn_ra_serf__merge_get_commit_info(svn_ra_serf__merge_context_t *ctx)
+{
+ return ctx->commit_info;
+}
+
+int
+svn_ra_serf__merge_get_status(svn_ra_serf__merge_context_t *ctx)
+{
+ return ctx->status;
+}
diff --git a/subversion/libsvn_ra_serf/mergeinfo.c b/subversion/libsvn_ra_serf/mergeinfo.c
new file mode 100644
index 0000000..7584d99
--- /dev/null
+++ b/subversion/libsvn_ra_serf/mergeinfo.c
@@ -0,0 +1,309 @@
+/*
+ * 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_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 {
+ NONE = 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. curr_path and curr_info contain the value of the
+ CDATA from the mergeinfo items as we get them from the server. */
+
+typedef struct mergeinfo_context_t {
+ apr_pool_t *pool;
+ svn_stringbuf_t *curr_path;
+ svn_stringbuf_t *curr_info;
+ svn_mergeinfo_t result_catalog;
+ svn_boolean_t done;
+ const apr_array_header_t *paths;
+ svn_revnum_t revision;
+ svn_mergeinfo_inheritance_t inherit;
+ svn_boolean_t include_descendants;
+} mergeinfo_context_t;
+
+static svn_error_t *
+start_element(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ mergeinfo_context_t *mergeinfo_ctx = userData;
+ mergeinfo_state_e state;
+
+ state = parser->state->current_state;
+ if (state == NONE && strcmp(name.name, SVN_DAV__MERGEINFO_REPORT) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, MERGEINFO_REPORT);
+ }
+ else if (state == MERGEINFO_REPORT &&
+ strcmp(name.name, SVN_DAV__MERGEINFO_ITEM) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, MERGEINFO_ITEM);
+ svn_stringbuf_setempty(mergeinfo_ctx->curr_path);
+ svn_stringbuf_setempty(mergeinfo_ctx->curr_info);
+ }
+ else if (state == MERGEINFO_ITEM &&
+ strcmp(name.name, SVN_DAV__MERGEINFO_PATH) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, MERGEINFO_PATH);
+ }
+ else if (state == MERGEINFO_ITEM &&
+ strcmp(name.name, SVN_DAV__MERGEINFO_INFO) == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, MERGEINFO_INFO);
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_element(svn_ra_serf__xml_parser_t *parser, void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ mergeinfo_context_t *mergeinfo_ctx = userData;
+ mergeinfo_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == MERGEINFO_REPORT &&
+ strcmp(name.name, SVN_DAV__MERGEINFO_REPORT) == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == MERGEINFO_ITEM
+ && strcmp(name.name, SVN_DAV__MERGEINFO_ITEM) == 0)
+ {
+ if (mergeinfo_ctx->curr_info && mergeinfo_ctx->curr_path)
+ {
+ svn_mergeinfo_t path_mergeinfo;
+ const char *path;
+
+ SVN_ERR_ASSERT(mergeinfo_ctx->curr_path->data);
+ path = apr_pstrdup(mergeinfo_ctx->pool,
+ mergeinfo_ctx->curr_path->data);
+ SVN_ERR(svn_mergeinfo_parse(&path_mergeinfo,
+ mergeinfo_ctx->curr_info->data,
+ mergeinfo_ctx->pool));
+ /* Correct for naughty servers that send "relative" paths
+ with leading slashes! */
+ apr_hash_set(mergeinfo_ctx->result_catalog,
+ path[0] == '/' ? path + 1 : path,
+ APR_HASH_KEY_STRING, path_mergeinfo);
+ }
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == MERGEINFO_PATH
+ && strcmp(name.name, SVN_DAV__MERGEINFO_PATH) == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == MERGEINFO_INFO
+ && strcmp(name.name, SVN_DAV__MERGEINFO_INFO) == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+cdata_handler(svn_ra_serf__xml_parser_t *parser, void *userData,
+ const char *data, apr_size_t len)
+{
+ mergeinfo_context_t *mergeinfo_ctx = userData;
+ mergeinfo_state_e state;
+
+ state = parser->state->current_state;
+ switch (state)
+ {
+ case MERGEINFO_PATH:
+ if (mergeinfo_ctx->curr_path)
+ svn_stringbuf_appendbytes(mergeinfo_ctx->curr_path, data, len);
+ break;
+
+ case MERGEINFO_INFO:
+ if (mergeinfo_ctx->curr_info)
+ svn_stringbuf_appendbytes(mergeinfo_ctx->curr_info, data, len);
+ break;
+
+ default:
+ break;
+ }
+
+ 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;
+}
+
+/* Request a mergeinfo-report from the URL attached to SESSION,
+ and fill in the MERGEINFO hash with the results. */
+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;
+ int status_code;
+
+ mergeinfo_context_t *mergeinfo_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 *relative_url, *basecoll_url;
+ const char *path;
+
+ *catalog = NULL;
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
+ NULL, NULL, revision, NULL, pool));
+
+ path = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+
+ mergeinfo_ctx = apr_pcalloc(pool, sizeof(*mergeinfo_ctx));
+ mergeinfo_ctx->pool = pool;
+ mergeinfo_ctx->curr_path = svn_stringbuf_create("", pool);
+ mergeinfo_ctx->curr_info = svn_stringbuf_create("", pool);
+ mergeinfo_ctx->done = FALSE;
+ 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;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ 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";
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = mergeinfo_ctx;
+ parser_ctx->start = start_element;
+ parser_ctx->end = end_element;
+ parser_ctx->cdata = cdata_handler;
+ parser_ctx->done = &mergeinfo_ctx->done;
+ parser_ctx->status_code = &status_code;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&mergeinfo_ctx->done, session, pool);
+
+ err2 = svn_ra_serf__error_on_status(status_code, handler->path,
+ parser_ctx->location);
+ if (err2)
+ {
+ svn_error_clear(err);
+ return err2;
+ }
+
+ SVN_ERR(err);
+
+ if (mergeinfo_ctx->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..8bdc8fd
--- /dev/null
+++ b/subversion/libsvn_ra_serf/options.c
@@ -0,0 +1,637 @@
+/*
+ * 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_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.
+ */
+typedef enum options_state_e {
+ OPTIONS,
+ ACTIVITY_COLLECTION,
+ HREF
+} options_state_e;
+
+typedef struct options_state_list_t {
+ /* The current state that we are in now. */
+ options_state_e state;
+
+ /* The previous state we were in. */
+ struct options_state_list_t *prev;
+} options_state_list_t;
+
+struct svn_ra_serf__options_context_t {
+ /* pool to allocate memory from */
+ apr_pool_t *pool;
+
+ const char *attr_val;
+ apr_size_t attr_val_len;
+ svn_boolean_t collect_cdata;
+
+ /* Current state we're in */
+ options_state_list_t *state;
+ options_state_list_t *free_state;
+
+ /* HTTP Status code */
+ int status_code;
+
+ /* are we done? */
+ svn_boolean_t done;
+
+ svn_ra_serf__session_t *session;
+ svn_ra_serf__connection_t *conn;
+
+ const char *path;
+
+ const char *activity_collection;
+ svn_revnum_t youngest_rev;
+
+ serf_response_acceptor_t acceptor;
+ serf_response_handler_t handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+
+};
+
+static void
+push_state(svn_ra_serf__options_context_t *options_ctx, options_state_e state)
+{
+ options_state_list_t *new_state;
+
+ if (!options_ctx->free_state)
+ {
+ new_state = apr_palloc(options_ctx->pool, sizeof(*options_ctx->state));
+ }
+ else
+ {
+ new_state = options_ctx->free_state;
+ options_ctx->free_state = options_ctx->free_state->prev;
+ }
+ new_state->state = state;
+
+ /* Add it to the state chain. */
+ new_state->prev = options_ctx->state;
+ options_ctx->state = new_state;
+}
+
+static void pop_state(svn_ra_serf__options_context_t *options_ctx)
+{
+ options_state_list_t *free_state;
+ free_state = options_ctx->state;
+ /* advance the current state */
+ options_ctx->state = options_ctx->state->prev;
+ free_state->prev = options_ctx->free_state;
+ options_ctx->free_state = free_state;
+}
+
+static svn_error_t *
+start_options(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ svn_ra_serf__options_context_t *options_ctx = userData;
+
+ if (!options_ctx->state && strcmp(name.name, "options-response") == 0)
+ {
+ push_state(options_ctx, OPTIONS);
+ }
+ else if (!options_ctx->state)
+ {
+ /* Nothing to do. */
+ return SVN_NO_ERROR;
+ }
+ else if (options_ctx->state->state == OPTIONS &&
+ strcmp(name.name, "activity-collection-set") == 0)
+ {
+ push_state(options_ctx, ACTIVITY_COLLECTION);
+ }
+ else if (options_ctx->state->state == ACTIVITY_COLLECTION &&
+ strcmp(name.name, "href") == 0)
+ {
+ options_ctx->collect_cdata = TRUE;
+ push_state(options_ctx, HREF);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_options(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ svn_ra_serf__options_context_t *options_ctx = userData;
+ options_state_list_t *cur_state;
+
+ if (!options_ctx->state)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ cur_state = options_ctx->state;
+
+ if (cur_state->state == OPTIONS &&
+ strcmp(name.name, "options-response") == 0)
+ {
+ pop_state(options_ctx);
+ }
+ else if (cur_state->state == ACTIVITY_COLLECTION &&
+ strcmp(name.name, "activity-collection-set") == 0)
+ {
+ pop_state(options_ctx);
+ }
+ else if (cur_state->state == HREF &&
+ strcmp(name.name, "href") == 0)
+ {
+ options_ctx->collect_cdata = FALSE;
+ options_ctx->activity_collection =
+ svn_urlpath__canonicalize(options_ctx->attr_val, options_ctx->pool);
+ pop_state(options_ctx);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+cdata_options(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ svn_ra_serf__options_context_t *ctx = userData;
+ if (ctx->collect_cdata)
+ {
+ svn_ra_serf__expand_string(&ctx->attr_val, &ctx->attr_val_len,
+ data, len, 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;
+}
+
+svn_boolean_t*
+svn_ra_serf__get_options_done_ptr(svn_ra_serf__options_context_t *ctx)
+{
+ return &ctx->done;
+}
+
+const char *
+svn_ra_serf__options_get_activity_collection(svn_ra_serf__options_context_t *ctx)
+{
+ return ctx->activity_collection;
+}
+
+svn_revnum_t
+svn_ra_serf__options_get_youngest_rev(svn_ra_serf__options_context_t *ctx)
+{
+ return ctx->youngest_rev;
+}
+
+/* Context for both options_response_handler() and capabilities callback. */
+struct options_response_ctx_t {
+ /* Baton for __handle_xml_parser() */
+ svn_ra_serf__xml_parser_t *parser_ctx;
+
+ /* Session into which we'll store server capabilities */
+ svn_ra_serf__session_t *session;
+
+ /* For temporary work only. */
+ apr_pool_t *pool;
+};
+
+
+/* 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 *capability_yes = "yes";
+/* Either server or repository does not support the capability. */
+static const char *capability_no = "no";
+/* Server supports the capability, but don't yet know if repository does. */
+static const char *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)
+{
+ struct options_response_ctx_t *orc = baton;
+
+ 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, orc->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))
+ {
+ apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_DEPTH,
+ APR_HASH_KEY_STRING, 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. */
+ apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ APR_HASH_KEY_STRING, capability_server_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
+ {
+ apr_hash_set(orc->session->capabilities,
+ SVN_RA_CAPABILITY_LOG_REVPROPS,
+ APR_HASH_KEY_STRING, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
+ {
+ apr_hash_set(orc->session->capabilities,
+ SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ APR_HASH_KEY_STRING, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
+ {
+ apr_hash_set(orc->session->capabilities,
+ SVN_RA_CAPABILITY_PARTIAL_REPLAY,
+ APR_HASH_KEY_STRING, capability_yes);
+ }
+ }
+
+ /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
+ else if (strncmp(key, "SVN", 3) == 0)
+ {
+ if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
+ {
+ orc->session->repos_root = orc->session->session_url;
+ orc->session->repos_root.path = apr_pstrdup(orc->session->pool, val);
+ orc->session->repos_root_str =
+ svn_urlpath__canonicalize(
+ apr_uri_unparse(orc->session->pool,
+ &orc->session->repos_root,
+ 0),
+ orc->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))
+ orc->session->me_resource = apr_pstrdup(orc->session->pool, val);
+#else
+ orc->session->me_resource = apr_pstrdup(orc->session->pool, val);
+#endif
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
+ {
+ orc->session->rev_stub = apr_pstrdup(orc->session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
+ {
+ orc->session->rev_root_stub = apr_pstrdup(orc->session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
+ {
+ orc->session->txn_stub = apr_pstrdup(orc->session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
+ {
+ orc->session->txn_root_stub = apr_pstrdup(orc->session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
+ {
+ orc->session->vtxn_stub = apr_pstrdup(orc->session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
+ {
+ orc->session->vtxn_root_stub = apr_pstrdup(orc->session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
+ {
+ orc->session->uuid = apr_pstrdup(orc->session->pool, val);
+ }
+ else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
+ {
+ struct svn_ra_serf__options_context_t *user_data =
+ orc->parser_ctx->user_data;
+ user_data->youngest_rev = SVN_STR_TO_REV(val);
+ }
+ }
+
+ return 0;
+}
+
+
+/* A custom serf_response_handler_t which is mostly a wrapper around
+ svn_ra_serf__handle_xml_parser -- 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)
+{
+ struct options_response_ctx_t *orc = baton;
+ serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
+
+ /* Start out assuming all capabilities are unsupported. */
+ apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
+ APR_HASH_KEY_STRING, capability_no);
+ apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_DEPTH,
+ APR_HASH_KEY_STRING, capability_no);
+ apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
+ APR_HASH_KEY_STRING, capability_no);
+ apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
+ APR_HASH_KEY_STRING, capability_no);
+ apr_hash_set(orc->session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
+ APR_HASH_KEY_STRING, capability_no);
+
+ /* Then see which ones we can discover. */
+ serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback, orc);
+
+ /* Execute the 'real' response handler to XML-parse the repsonse body. */
+ return svn_ra_serf__handle_xml_parser(request, response,
+ orc->parser_ctx, pool);
+}
+
+
+svn_error_t *
+svn_ra_serf__create_options_req(svn_ra_serf__options_context_t **opt_ctx,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__options_context_t *new_ctx;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+ struct options_response_ctx_t *options_response_ctx;
+
+ new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
+
+ new_ctx->pool = pool;
+
+ new_ctx->path = path;
+ new_ctx->youngest_rev = SVN_INVALID_REVNUM;
+
+ new_ctx->session = session;
+ new_ctx->conn = conn;
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->method = "OPTIONS";
+ handler->path = path;
+ handler->body_delegate = create_options_body;
+ handler->body_type = "text/xml";
+ handler->conn = conn;
+ handler->session = session;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = new_ctx;
+ parser_ctx->start = start_options;
+ parser_ctx->end = end_options;
+ parser_ctx->cdata = cdata_options;
+ parser_ctx->done = &new_ctx->done;
+ parser_ctx->status_code = &new_ctx->status_code;
+
+ options_response_ctx = apr_pcalloc(pool, sizeof(*options_response_ctx));
+ options_response_ctx->parser_ctx = parser_ctx;
+ options_response_ctx->session = session;
+ options_response_ctx->pool = pool;
+
+ handler->response_handler = options_response_handler;
+ handler->response_baton = options_response_ctx;
+
+ svn_ra_serf__request_create(handler);
+
+ new_ctx->parser_ctx = parser_ctx;
+
+ *opt_ctx = new_ctx;
+
+ 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)
+{
+ svn_ra_serf__options_context_t *opt_ctx;
+ svn_error_t *err;
+
+ /* This routine automatically fills in serf_sess->capabilities */
+ SVN_ERR(svn_ra_serf__create_options_req(&opt_ctx, serf_sess,
+ serf_sess->conns[0],
+ serf_sess->session_url.path, pool));
+
+ err = svn_ra_serf__context_run_wait(
+ svn_ra_serf__get_options_done_ptr(opt_ctx), serf_sess, 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->status_code == 301))
+ {
+ svn_error_clear(err);
+ *corrected_url = opt_ctx->parser_ctx->location;
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_compose_create(
+ svn_ra_serf__error_on_status(opt_ctx->status_code,
+ serf_sess->session_url.path,
+ opt_ctx->parser_ctx->location),
+ err);
+}
+
+
+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 = apr_hash_get(serf_sess->capabilities,
+ capability,
+ APR_HASH_KEY_STRING);
+
+ /* 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 = apr_hash_get(serf_sess->capabilities,
+ capability, APR_HASH_KEY_STRING);
+
+ /* Some capabilities depend on the repository as well as the server.
+ NOTE: svn_ra_neon__has_capability() has a very similar code block. If
+ you change something here, check there as well. */
+ 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;
+
+ apr_hash_set(serf_sess->capabilities,
+ SVN_RA_CAPABILITY_MERGEINFO, APR_HASH_KEY_STRING,
+ 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..727f78b
--- /dev/null
+++ b/subversion/libsvn_ra_serf/property.c
@@ -0,0 +1,1126 @@
+/*
+ * 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_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 "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+/* Our current parsing state we're in for the PROPFIND response. */
+typedef enum prop_state_e {
+ NONE = 0,
+ RESPONSE,
+ PROP,
+ PROPVAL
+} prop_state_e;
+
+typedef struct prop_info_t {
+ apr_pool_t *pool;
+
+ /* Current ns, attribute name, and value of the property we're parsing */
+ const char *ns;
+
+ const char *name;
+
+ const char *val;
+ apr_size_t val_len;
+
+ const char *encoding;
+
+} prop_info_t;
+
+/*
+ * This structure represents a pending PROPFIND response.
+ */
+struct svn_ra_serf__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 svn_ra_serf__propfind_context_t
+ * structures
+ */
+ apr_hash_t *ret_props;
+
+ /* If we're dealing with a Depth: 1 response,
+ * we may be dealing with multiple paths.
+ */
+ const char *current_path;
+
+ /* Returned status code. */
+ int status_code;
+
+ /* Are we done issuing the PROPFIND? */
+ svn_boolean_t done;
+
+ /* Context from XML stream */
+ svn_ra_serf__xml_parser_t *parser_ctx;
+
+ /* 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;
+};
+
+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 = apr_hash_get(ver_props, path, APR_HASH_KEY_STRING);
+
+ if (path_props)
+ {
+ ns_props = apr_hash_get(path_props, ns, APR_HASH_KEY_STRING);
+ if (ns_props)
+ {
+ val = apr_hash_get(ns_props, name, APR_HASH_KEY_STRING);
+ }
+ }
+ }
+
+ 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 = apr_hash_get(ver_props, path, APR_HASH_KEY_STRING);
+
+ if (!path_props)
+ {
+ path_props = apr_hash_make(pool);
+ path = apr_pstrdup(pool, path);
+ apr_hash_set(ver_props, path, APR_HASH_KEY_STRING, path_props);
+
+ /* todo: we know that we'll fail the next check, but fall through
+ * for now for simplicity's sake.
+ */
+ }
+
+ ns_props = apr_hash_get(path_props, ns, APR_HASH_KEY_STRING);
+ if (!ns_props)
+ {
+ ns_props = apr_hash_make(pool);
+ ns = apr_pstrdup(pool, ns);
+ apr_hash_set(path_props, ns, APR_HASH_KEY_STRING, ns_props);
+ }
+
+ apr_hash_set(ns_props, name, APR_HASH_KEY_STRING, 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 prop_info_t *
+push_state(svn_ra_serf__xml_parser_t *parser,
+ svn_ra_serf__propfind_context_t *propfind,
+ prop_state_e state)
+{
+ svn_ra_serf__xml_push_state(parser, state);
+
+ if (state == PROPVAL)
+ {
+ prop_info_t *info;
+
+ info = apr_pcalloc(parser->state->pool, sizeof(*info));
+ info->pool = parser->state->pool;
+
+ parser->state->private = info;
+ }
+
+ return parser->state->private;
+}
+
+/*
+ * Expat callback invoked on a start element tag for a PROPFIND response.
+ */
+static svn_error_t *
+start_propfind(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ svn_ra_serf__propfind_context_t *ctx = userData;
+ prop_state_e state;
+ prop_info_t *info;
+
+ state = parser->state->current_state;
+
+ if (state == NONE && strcmp(name.name, "response") == 0)
+ {
+ svn_ra_serf__xml_push_state(parser, RESPONSE);
+ }
+ else if (state == RESPONSE && strcmp(name.name, "href") == 0)
+ {
+ info = push_state(parser, ctx, PROPVAL);
+ info->ns = name.namespace;
+ info->name = apr_pstrdup(info->pool, name.name);
+ }
+ else if (state == RESPONSE && strcmp(name.name, "prop") == 0)
+ {
+ push_state(parser, ctx, PROP);
+ }
+ else if (state == PROP)
+ {
+ info = push_state(parser, ctx, PROPVAL);
+ info->ns = name.namespace;
+ info->name = apr_pstrdup(info->pool, name.name);
+ info->encoding = apr_pstrdup(info->pool,
+ svn_xml_get_attr_value("V:encoding", attrs));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on an end element tag for a PROPFIND response.
+ */
+static svn_error_t *
+end_propfind(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ svn_ra_serf__propfind_context_t *ctx = userData;
+ prop_state_e state;
+ prop_info_t *info;
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == RESPONSE && strcmp(name.name, "response") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == PROP && strcmp(name.name, "prop") == 0)
+ {
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == PROPVAL)
+ {
+ const char *ns, *pname, *val;
+ svn_string_t *val_str;
+
+ /* if we didn't see a CDATA element, we may want the tag name
+ * as long as it isn't equivalent to the property name.
+ */
+ if (!info->val)
+ {
+ if (strcmp(info->name, name.name) != 0)
+ {
+ info->val = name.name;
+ info->val_len = strlen(info->val);
+ }
+ else
+ {
+ info->val = "";
+ info->val_len = 0;
+ }
+ }
+
+ if (parser->state->prev->current_state == RESPONSE &&
+ strcmp(name.name, "href") == 0)
+ {
+ if (strcmp(ctx->depth, "1") == 0)
+ {
+ ctx->current_path =
+ svn_urlpath__canonicalize(info->val, ctx->pool);
+ }
+ else
+ {
+ ctx->current_path = ctx->path;
+ }
+ }
+ else if (info->encoding)
+ {
+ if (strcmp(info->encoding, "base64") == 0)
+ {
+ svn_string_t encoded;
+ const svn_string_t *decoded;
+
+ encoded.data = info->val;
+ encoded.len = info->val_len;
+
+ decoded = svn_base64_decode_string(&encoded, parser->state->pool);
+ info->val = decoded->data;
+ info->val_len = decoded->len;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ NULL,
+ _("Got unrecognized encoding '%s'"),
+ info->encoding);
+ }
+ }
+
+ ns = apr_pstrdup(ctx->pool, info->ns);
+ pname = apr_pstrdup(ctx->pool, info->name);
+ val = apr_pmemdup(ctx->pool, info->val, info->val_len);
+ val_str = svn_string_ncreate(val, info->val_len, ctx->pool);
+
+ /* set the return props and update our cache too. */
+ svn_ra_serf__set_ver_prop(ctx->ret_props,
+ ctx->current_path, ctx->rev,
+ ns, pname, val_str,
+ ctx->pool);
+
+ svn_ra_serf__xml_pop_state(parser);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Expat callback invoked on CDATA elements in a PROPFIND response.
+ *
+ * This callback can be called multiple times.
+ */
+static svn_error_t *
+cdata_propfind(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ svn_ra_serf__propfind_context_t *ctx = userData;
+ prop_state_e state;
+ prop_info_t *info;
+
+ UNUSED_CTX(ctx);
+
+ state = parser->state->current_state;
+ info = parser->state->private;
+
+ if (state == PROPVAL)
+ {
+ svn_ra_serf__expand_string(&info->val, &info->val_len, data, len,
+ info->pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+setup_propfind_headers(serf_bucket_t *headers,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__propfind_context_t *ctx = setup_baton;
+
+ if (ctx->conn->using_compression)
+ {
+ serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
+ }
+ 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)
+{
+ svn_ra_serf__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 == FALSE)
+ {
+ 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 == FALSE)
+ {
+ 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__propfind_context_t **prop_ctx,
+ 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)
+{
+ svn_ra_serf__propfind_context_t *new_prop_ctx;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_parser_t *parser_ctx;
+
+ 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->done = FALSE;
+ 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;
+ }
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ 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;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx->parser_ctx));
+ parser_ctx->pool = pool;
+ parser_ctx->user_data = new_prop_ctx;
+ parser_ctx->start = start_propfind;
+ parser_ctx->end = end_propfind;
+ parser_ctx->cdata = cdata_propfind;
+ parser_ctx->status_code = &new_prop_ctx->status_code;
+ parser_ctx->done = &new_prop_ctx->done;
+ parser_ctx->done_list = new_prop_ctx->done_list;
+ parser_ctx->done_item = &new_prop_ctx->done_item;
+
+ new_prop_ctx->parser_ctx = parser_ctx;
+
+ handler->response_handler = svn_ra_serf__handle_xml_parser;
+ handler->response_baton = parser_ctx;
+
+ /* create request */
+ svn_ra_serf__request_create(new_prop_ctx->handler);
+
+ *prop_ctx = new_prop_ctx;
+
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_ra_serf__propfind_is_done(svn_ra_serf__propfind_context_t *ctx)
+{
+ return ctx->done;
+}
+
+int
+svn_ra_serf__propfind_status_code(svn_ra_serf__propfind_context_t *ctx)
+{
+ return ctx->status_code;
+}
+
+/*
+ * 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__propfind_context_t *prop_ctx,
+ svn_ra_serf__session_t *sess,
+ apr_pool_t *pool)
+{
+ svn_error_t *err, *err2;
+
+ err = svn_ra_serf__context_run_wait(&prop_ctx->done, sess, pool);
+
+ err2 = svn_ra_serf__error_on_status(prop_ctx->status_code,
+ prop_ctx->path, NULL);
+ if (err2)
+ {
+ svn_error_clear(err);
+ return err2;
+ }
+
+ return 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__propfind_context_t *prop_ctx;
+
+ *results = apr_hash_make(result_pool);
+
+ SVN_ERR(svn_ra_serf__deliver_props(&prop_ctx, *results, sess, conn, url,
+ rev, depth, props, NULL, result_pool));
+ SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, sess, result_pool));
+
+ 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_index_t *ns_hi;
+ apr_hash_t *ver_props;
+ apr_hash_t *path_props;
+ apr_pool_t *iterpool;
+
+ ver_props = apr_hash_get(props, &rev, sizeof(rev));
+ if (!ver_props)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ path_props = apr_hash_get(ver_props, name, APR_HASH_KEY_STRING);
+ if (!path_props)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (ns_hi = apr_hash_first(scratch_pool, path_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_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)
+ apr_hash_set(props, prop_name, APR_HASH_KEY_STRING, value);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__flatten_props(apr_hash_t **flat_props,
+ apr_hash_t *props,
+ const char *path,
+ svn_revnum_t revision,
+ 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_all_props(
+ props, path, revision,
+ 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;
+ }
+
+ apr_hash_set(revprops, prop_name, APR_HASH_KEY_STRING, 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 SESSION and 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__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *baseline_url,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ apr_hash_t *props;
+ const char *basecoll_url;
+ const char *version_name;
+
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session, conn,
+ baseline_url, revision, "0",
+ baseline_props,
+ pool, pool));
+
+ basecoll_url = svn_ra_serf__get_ver_prop(props, baseline_url, revision,
+ "DAV:", "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, pool);
+
+ version_name = svn_ra_serf__get_ver_prop(props, baseline_url, revision,
+ "DAV:", 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"));
+ }
+
+ if (actual_revision)
+ {
+ *actual_revision = SVN_STR_TO_REV(version_name);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_ra_serf__get_baseline_info(const char **bc_url,
+ const char **bc_relative,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool)
+{
+ const char *vcc_url, *relative_url, *basecoll_url, *baseline_url;
+
+ /* 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 one. */
+ if (! conn)
+ conn = session->conns[0];
+
+ /* 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))
+ {
+ svn_revnum_t actual_revision;
+
+ if (SVN_IS_VALID_REVNUM(revision))
+ {
+ actual_revision = revision;
+ }
+ else
+ {
+ svn_ra_serf__options_context_t *opt_ctx;
+
+ SVN_ERR(svn_ra_serf__create_options_req(&opt_ctx, session, conn,
+ session->session_url.path,
+ pool));
+ SVN_ERR(svn_ra_serf__context_run_wait(
+ svn_ra_serf__get_options_done_ptr(opt_ctx), session, pool));
+
+ actual_revision = svn_ra_serf__options_get_youngest_rev(opt_ctx);
+ if (! SVN_IS_VALID_REVNUM(actual_revision))
+ return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include "
+ "the youngest revision"));
+ }
+
+ basecoll_url = apr_psprintf(pool, "%s/%ld",
+ session->rev_root_stub, actual_revision);
+ if (latest_revnum)
+ *latest_revnum = actual_revision;
+ }
+
+ /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */
+ else
+ {
+ 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(&basecoll_url,
+ session->blncache,
+ revision, pool));
+
+ if (!basecoll_url)
+ {
+ SVN_ERR(retrieve_baseline_info(NULL, &basecoll_url, session,
+ conn, vcc_url, revision, pool));
+ SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
+ revision, basecoll_url, pool));
+ }
+
+ if (latest_revnum)
+ {
+ *latest_revnum = revision;
+ }
+ }
+ else
+ {
+ apr_hash_t *props;
+ svn_revnum_t actual_revision;
+
+ SVN_ERR(svn_ra_serf__retrieve_props(&props, session, conn,
+ vcc_url, revision, "0",
+ checked_in_props,
+ pool, pool));
+ baseline_url = svn_ra_serf__get_ver_prop(props, vcc_url, revision,
+ "DAV:", "checked-in");
+ 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, pool);
+
+ /* First check baseline information cache. */
+ SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&basecoll_url,
+ &actual_revision,
+ session->blncache,
+ baseline_url,
+ pool));
+ if (!basecoll_url)
+ {
+ SVN_ERR(retrieve_baseline_info(&actual_revision, &basecoll_url,
+ session, conn,
+ baseline_url, revision, pool));
+ SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
+ baseline_url, actual_revision,
+ basecoll_url, pool));
+ }
+
+ if (latest_revnum)
+ {
+ *latest_revnum = actual_revision;
+ }
+ }
+ }
+
+ /* And let's not forget to calculate our relative path. */
+ SVN_ERR(svn_ra_serf__get_relative_path(&relative_url, url, session,
+ conn, pool));
+
+ *bc_url = basecoll_url;
+ *bc_relative = relative_url;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
+ apr_hash_t *props,
+ const char *url,
+ svn_revnum_t revision)
+{
+ const char *res_type;
+
+ res_type = svn_ra_serf__get_ver_prop(props, url, revision,
+ "DAV:", "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;
+}
diff --git a/subversion/libsvn_ra_serf/ra_serf.h b/subversion/libsvn_ra_serf/ra_serf.h
new file mode 100644
index 0000000..5c2116f
--- /dev/null
+++ b/subversion/libsvn_ra_serf/ra_serf.h
@@ -0,0 +1,1458 @@
+/*
+ * 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>
+#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 "blncache.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Enforce the minimum version of serf. */
+#if !SERF_VERSION_AT_LEAST(0, 7, 1)
+#error Please update your version of serf to at least 0.7.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 " serf/" \
+ APR_STRINGIFY(SERF_MAJOR_VERSION) "." \
+ APR_STRINGIFY(SERF_MINOR_VERSION) "." \
+ APR_STRINGIFY(SERF_PATCH_VERSION)
+
+
+/* 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;
+
+ /* Host name */
+ const char *hostname;
+
+ /* Are we using ssl */
+ svn_boolean_t using_ssl;
+ int server_cert_failures; /* Collected cert failures in chain */
+
+ /* Should we ask for compressed responses? */
+ svn_boolean_t using_compression;
+
+ /* 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;
+
+ /* user agent string */
+ const char *useragent;
+
+} svn_ra_serf__connection_t;
+
+/*
+ * 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;
+
+ /* Are we using ssl */
+ svn_boolean_t using_ssl;
+
+ /* Should we ask for compressed responses? */
+ svn_boolean_t using_compression;
+
+ /* The current connection */
+ svn_ra_serf__connection_t **conns;
+ 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;
+
+ /* 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;
+
+ /* 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;
+
+ /* 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 */
+ long 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 */
+
+ /*** End HTTP v2 stuff ***/
+
+ svn_ra_serf__blncache_t *blncache;
+};
+
+#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 vcc_props[] =
+{
+ { "DAV:", "version-controlled-configuration" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t check_path_props[] =
+{
+ { "DAV:", "resourcetype" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t uuid_props[] =
+{
+ { SVN_DAV_PROP_NS_DAV, "repository-uuid" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t repos_root_props[] =
+{
+ { SVN_DAV_PROP_NS_DAV, "baseline-relative-path" },
+ { NULL }
+};
+
+static const svn_ra_serf__dav_props_t href_props[] =
+{
+ { "DAV:", "href" },
+ { NULL }
+};
+
+/* WC props compatibility with ra_neon. */
+#define SVN_RA_SERF__WC_NAMESPACE SVN_PROP_WC_PREFIX "ra_dav:"
+#define SVN_RA_SERF__WC_ACTIVITY_URL SVN_RA_SERF__WC_NAMESPACE "activity-url"
+#define SVN_RA_SERF__WC_CHECKED_IN_URL SVN_RA_SERF__WC_NAMESPACE "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);
+
+serf_bucket_t*
+svn_ra_serf__accept_response(serf_request_t *request,
+ serf_bucket_t *stream,
+ void *acceptor_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);
+
+/*
+ * Create a REQUEST with an associated REQ_BKT in the SESSION.
+ *
+ * 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.
+ */
+svn_error_t *
+svn_ra_serf__setup_serf_req(serf_request_t *request,
+ serf_bucket_t **req_bkt, serf_bucket_t **hdrs_bkt,
+ svn_ra_serf__connection_t *conn,
+ const char *method, const char *url,
+ serf_bucket_t *body_bkt, const char *content_type);
+
+/*
+ * 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 *pool);
+
+/* Callback for setting up a complete serf request */
+typedef svn_error_t *
+(*svn_ra_serf__request_setup_t)(serf_request_t *request,
+ void *setup_baton,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ svn_ra_serf__response_handler_t *handler,
+ void **handler_baton,
+ apr_pool_t *pool);
+
+/* Callback for when a request body is needed. */
+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 *pool);
+
+/* Callback for when request headers are needed. */
+typedef svn_error_t *
+(*svn_ra_serf__request_header_delegate_t)(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *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);
+
+/*
+ * 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;
+
+ /* The handler and baton pair for our handler. */
+ svn_ra_serf__response_handler_t response_handler;
+ void *response_baton;
+
+ /* 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 will be executed when the request is about
+ * to be delivered by serf.
+ *
+ * This just passes through serf's raw request creation parameters.
+ * None of the other parameters will be utilized if this field is set.
+ */
+ svn_ra_serf__request_setup_t setup;
+ void *setup_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;
+} svn_ra_serf__handler_t;
+
+/*
+ * 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,
+ void *baton,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs);
+
+/* 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,
+ void *baton,
+ svn_ra_serf__dav_props_t name);
+
+/* 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.
+ *
+ * @see svn_ra_serf__expand_string
+ */
+typedef svn_error_t *
+(*svn_ra_serf__xml_cdata_chunk_handler_t)(svn_ra_serf__xml_parser_t *parser,
+ void *baton,
+ const char *data,
+ apr_size_t len);
+
+/*
+ * 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;
+
+ /* 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, the status code of the response will be stored here.
+ *
+ * If this is NULL and an error is received, an abort will be triggered.
+ */
+ int *status_code;
+
+ /* If non-NULL, this is the value of the response's Location header.
+ */
+ const char *location;
+
+ /* 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 */
+};
+
+/*
+ * Parses a server-side error message into a local Subversion error.
+ */
+typedef struct svn_ra_serf__server_error_t {
+ /* Our local representation of the error. */
+ svn_error_t *error;
+
+ /* Have we checked to see if there's an XML error in this response? */
+ svn_boolean_t init;
+
+ /* Was there an XML error response? */
+ svn_boolean_t has_xml_response;
+
+ /* 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;
+} svn_ra_serf__server_error_t;
+
+/* A simple request context that can be passed to handle_status_only. */
+typedef struct svn_ra_serf__simple_request_context_t {
+ apr_pool_t *pool;
+
+ /* The HTTP status code of the response */
+ int status;
+
+ /* The HTTP status line of the response */
+ const char *reason;
+
+ /* The Location header value of the response, or NULL if there
+ wasn't one. */
+ const char *location;
+
+ /* This value is set to TRUE when the response is completed. */
+ svn_boolean_t done;
+
+ /* If an error occurred, this value will be initialized. */
+ svn_ra_serf__server_error_t server_error;
+} svn_ra_serf__simple_request_context_t;
+
+/*
+ * Serf handler for @a request / @a response pair that takes in a
+ * @a baton (@see svn_ra_serf__simple_request_context_t).
+ * Implements svn_ra_serf__response_handler_t.
+ *
+ * Temporary allocations are made in @a pool.
+ */
+svn_error_t *
+svn_ra_serf__handle_status_only(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool);
+
+/*
+ * 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 error response from the
+ * the @a response body associated with a @a request.
+ * Implements svn_ra_serf__response_handler_t.
+ *
+ * All temporary allocations will be made in a @a pool.
+ */
+svn_error_t *
+svn_ra_serf__handle_server_error(serf_request_t *request,
+ serf_bucket_t *response,
+ apr_pool_t *pool);
+
+/*
+ * Handler that retrieves the embedded XML multistatus response from the
+ * the @a RESPONSE body associated with a @a REQUEST. *DONE is set to TRUE.
+ * Implements svn_ra_serf__response_handler_t.
+ *
+ * The @a BATON should be of type svn_ra_serf__simple_request_context_t.
+ *
+ * All temporary allocations will be made in a @a pool.
+ */
+svn_error_t *
+svn_ra_serf__handle_multistatus_only(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *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);
+
+/* Return the value of the RESPONSE's Location header if any, or NULL
+ * otherwise. All allocations will be made in POOL.
+ */
+const char *
+svn_ra_serf__response_get_location(serf_bucket_t *response,
+ 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,
+ 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 xml tag open
+ * 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. Do xml-escaping
+ * on each val. Attribute will be ignored if it's value is NULL.
+ *
+ * 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 @a pool.
+ */
+void
+svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
+ const char **attrs,
+ apr_pool_t *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,
+ svn_ra_serf__ns_t *ns_list,
+ const char *name);
+
+/*
+ * Expand the string represented by @a cur with a current size of @a
+ * cur_len by appending @a new with a size of @a new_len.
+ *
+ * The reallocated string is made in @a pool.
+ */
+void
+svn_ra_serf__expand_string(const char **cur, apr_size_t *cur_len,
+ const char *new, apr_size_t new_len,
+ apr_pool_t *pool);
+
+/** PROPFIND-related functions **/
+
+/* Opaque structure representing PROPFINDs. */
+typedef struct svn_ra_serf__propfind_context_t svn_ra_serf__propfind_context_t;
+
+/*
+ * Returns a flag representing whether the PROPFIND @a ctx is completed.
+ */
+svn_boolean_t
+svn_ra_serf__propfind_is_done(svn_ra_serf__propfind_context_t *ctx);
+
+/*
+ * Returns the response status code of the PROPFIND @a ctx.
+ */
+int
+svn_ra_serf__propfind_status_code(svn_ra_serf__propfind_context_t *ctx);
+
+/*
+ * 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__propfind_context_t **prop_ctx,
+ 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 the PROP_CTX indicates that is done
+ * or another error is returned.
+ */
+svn_error_t *
+svn_ra_serf__wait_for_props(svn_ra_serf__propfind_context_t *prop_ctx,
+ svn_ra_serf__session_t *sess,
+ apr_pool_t *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);
+
+/* 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);
+
+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 REV -> PATH -> NS -> NAME -> VALUE.
+ This function takes the tree of tables identified by PATH and REVISION
+ (resulting in 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,
+ const char *path,
+ svn_revnum_t revision,
+ 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,
+ const char *url,
+ svn_revnum_t revision);
+
+
+/** MERGE-related functions **/
+
+typedef struct svn_ra_serf__merge_context_t svn_ra_serf__merge_context_t;
+
+svn_boolean_t*
+svn_ra_serf__merge_get_done_ptr(svn_ra_serf__merge_context_t *ctx);
+
+svn_commit_info_t*
+svn_ra_serf__merge_get_commit_info(svn_ra_serf__merge_context_t *ctx);
+
+int
+svn_ra_serf__merge_get_status(svn_ra_serf__merge_context_t *ctx);
+
+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__merge_create_req(svn_ra_serf__merge_context_t **merge_ctx,
+ 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 *pool);
+
+/** OPTIONS-related functions **/
+
+typedef struct svn_ra_serf__options_context_t svn_ra_serf__options_context_t;
+
+/* Is this OPTIONS-request done yet? */
+svn_boolean_t*
+svn_ra_serf__get_options_done_ptr(svn_ra_serf__options_context_t *ctx);
+
+const char *
+svn_ra_serf__options_get_activity_collection(svn_ra_serf__options_context_t *ctx);
+
+svn_revnum_t
+svn_ra_serf__options_get_youngest_rev(svn_ra_serf__options_context_t *ctx);
+
+/* Create an OPTIONS request. When run, ask for an
+ activity-collection-set in the request body (retrievable via
+ accessor above) and also parse the server's capability headers into
+ the SESSION->capabilites hash. */
+svn_error_t *
+svn_ra_serf__create_options_req(svn_ra_serf__options_context_t **opt_ctx,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *path,
+ apr_pool_t *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);
+
+/* Set *BC_URL to the baseline collection url, and set *BC_RELATIVE to
+ * the path relative to that url for URL in REVISION using SESSION.
+ * BC_RELATIVE will be URI decoded.
+ *
+ * REVISION may be SVN_INVALID_REVNUM (to mean "the current HEAD
+ * revision"). If URL is NULL, use SESSION's session url.
+ *
+ * If LATEST_REVNUM is not NULL, set it to the baseline revision. If
+ * REVISION was set to SVN_INVALID_REVNUM, this will return the current
+ * HEAD revision.
+ *
+ * If non-NULL, use CONN for communications with the server;
+ * otherwise, use the default connection.
+ *
+ * Use POOL for all allocations.
+ */
+svn_error_t *
+svn_ra_serf__get_baseline_info(const char **bc_url,
+ const char **bc_relative,
+ svn_ra_serf__session_t *session,
+ svn_ra_serf__connection_t *conn,
+ const char *url,
+ svn_revnum_t revision,
+ svn_revnum_t *latest_revnum,
+ apr_pool_t *pool);
+
+/** RA functions **/
+
+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);
+
+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);
+
+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);
+
+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);
+
+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_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,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool);
+
+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,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *pool);
+
+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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+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);
+
+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_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);
+
+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);
+
+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_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);
+
+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 the has_capability RA layer function. */
+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 the get_deleted_rev RA layer function. */
+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);
+
+/*** 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);
+
+#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..36b3dca
--- /dev/null
+++ b/subversion/libsvn_ra_serf/replay.c
@@ -0,0 +1,888 @@
+/*
+ * 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 <expat.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 "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;
+
+ const char *data;
+ apr_size_t len;
+
+ 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;
+
+ /* current revision */
+ svn_revnum_t revision;
+
+ /* Information needed to create the replay report body */
+ svn_revnum_t low_water_mark;
+ svn_boolean_t send_deltas;
+
+ /* Cached report target url */
+ const char *report_target;
+
+ /* 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;
+
+ /* The propfind for the revision properties of the current revision */
+ svn_ra_serf__propfind_context_t *prop_ctx;
+
+} 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;
+
+ parser->state->private = info;
+ }
+
+ return parser->state->private;
+}
+
+static svn_error_t *
+start_replay(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ replay_context_t *ctx = userData;
+ 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->prop_ctx
+ || svn_ra_serf__propfind_is_done(ctx->prop_ctx));
+
+ /* 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);
+
+ /* ### it would be nice to have a proper scratch_pool. */
+ SVN_ERR(svn_ra_serf__select_revprops(&ctx->props,
+ ctx->revprop_target,
+ ctx->revprop_rev,
+ ctx->revs_props,
+ ctx->dst_rev_pool,
+ ctx->dst_rev_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),
+ ctx->dst_rev_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, ctx->dst_rev_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, ctx->dst_rev_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,
+ ctx->file_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,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ replay_context_t *ctx = userData;
+ replay_state_e state;
+
+ UNUSED_CTX(ctx);
+
+ 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
+ {
+ svn_string_t tmp_prop;
+
+ tmp_prop.data = info->data;
+ tmp_prop.len = info->len;
+
+ if (strcmp(name.name, "change-file-prop") == 0)
+ prop_val = svn_base64_decode_string(&tmp_prop, ctx->file_pool);
+ else
+ prop_val = svn_base64_decode_string(&tmp_prop, 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,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ replay_context_t *replay_ctx = userData;
+ 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_ra_serf__expand_string(&info->data, &info->len,
+ data, len, parser->state->pool);
+ }
+
+ 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);
+
+ 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;
+ /* We're not really interested in the status code here in replay, but
+ the XML parsing code will abort on error if it doesn't have a place
+ to store the response status code. */
+ int status_code;
+
+ 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->report_target = report_target;
+ replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool);
+
+ handler = apr_pcalloc(pool, sizeof(*handler));
+
+ handler->method = "REPORT";
+ handler->path = session->session_url_str;
+ 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->status_code = &status_code;
+ 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;
+
+ svn_ra_serf__request_create(handler);
+
+ err = svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool);
+
+ SVN_ERR(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 serf_context_run 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;
+
+ SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool));
+
+ while (active_reports || rev <= end_revision)
+ {
+ apr_status_t status;
+ svn_error_t *err;
+ svn_ra_serf__list_t *done_list;
+ svn_ra_serf__list_t *done_reports = NULL;
+ replay_context_t *replay_ctx;
+ /* We're not really interested in the status code here in replay, but
+ the XML parsing code will abort on error if it doesn't have a place
+ to store the response status code. */
+ int status_code;
+
+ 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);
+
+ 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->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->report_target = report_target;
+ 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->prop_ctx,
+ 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));
+
+ /* Send the replay report request. */
+ handler = apr_pcalloc(replay_ctx->src_rev_pool, sizeof(*handler));
+
+ handler->method = "REPORT";
+ handler->path = session->session_url_str;
+ 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->status_code = &status_code;
+ 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;
+
+ /* 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, send outgoing and process incoming requests.
+ This request will block when there are no more requests to send or
+ responses to receive, so we have to be careful on our bookkeeping. */
+ status = serf_context_run(session->context, session->timeout,
+ pool);
+
+ err = session->pending_error;
+ session->pending_error = NULL;
+
+ if (APR_STATUS_IS_TIMEUP(status))
+ {
+ svn_error_clear(err);
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT,
+ NULL,
+ _("Connection timed out"));
+ }
+
+ /* 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;
+
+ done_list = done_list->next;
+ svn_pool_destroy(ctx->src_rev_pool);
+ active_reports--;
+ }
+
+ SVN_ERR(err);
+ if (status)
+ {
+ return svn_error_wrap_apr(status,
+ _("Error retrieving replay REPORT (%d)"),
+ status);
+ }
+ done_reports = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+#undef MAX_OUTSTANDING_REQUESTS
diff --git a/subversion/libsvn_ra_serf/serf.c b/subversion/libsvn_ra_serf/serf.c
new file mode 100644
index 0000000..391f031
--- /dev/null
+++ b/subversion/libsvn_ra_serf/serf.c
@@ -0,0 +1,1217 @@
+/*
+ * 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 <expat.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 "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+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.")
+
+static const char *
+ra_serf_get_description(void)
+{
+ return _(RA_SERF_DESCRIPTION);
+}
+
+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, *last;
+ char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
+ apr_collapse_spaces(auth_types_list, http_auth_types);
+ while ((token = apr_strtok(auth_types_list, ";", &last)) != NULL)
+ {
+ 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 http auth"
+ "type '%s'"), token);
+ }
+ }
+ else
+ {
+ /* Nothing specified by the user, so accept all types. */
+ *authn_types = SERF_AUTHN_ALL;
+ }
+
+ return SVN_NO_ERROR;
+}
+#define DEFAULT_HTTP_TIMEOUT 3600
+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;
+ svn_boolean_t is_exception = FALSE;
+
+ if (config_hash)
+ {
+ config = apr_hash_get(config_hash, SVN_CONFIG_CATEGORY_SERVERS,
+ APR_HASH_KEY_STRING);
+ config_client = apr_hash_get(config_hash, SVN_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING);
+ }
+ 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, NULL);
+ if (exceptions)
+ {
+ apr_array_header_t *l = svn_cstring_split(exceptions, ",", TRUE, pool);
+ is_exception = svn_cstring_match_glob_list(session->session_url.hostname,
+ l);
+ }
+ if (! is_exception)
+ {
+ /* Load the global proxy server settings, if set. */
+ 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 (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. */
+ svn_config_get(config, &proxy_host, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
+ svn_config_get(config, &port_str, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
+ svn_config_get(config, &session->proxy_username, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
+ svn_config_get(config, &session->proxy_password, server_group,
+ SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
+
+ /* 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,
+ TRUE));
+ svn_config_get(config, &session->ssl_authorities, server_group,
+ SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
+ }
+
+ /* Parse the connection timeout value, if any. */
+ 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);
+ }
+ else
+ session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
+
+ /* 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_error_wrap_apr(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);
+ }
+}
+
+static svn_error_t *
+svn_ra_serf__open(svn_ra_session_t *session,
+ const char **corrected_url,
+ const char *repos_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, repos_URL, &url);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal repository URL '%s'"),
+ repos_URL);
+ }
+ /* Contrary to what the comment for apr_uri_t.path says in apr-util 1.2.12 and
+ older, for root paths url.path will be "", 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, repos_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);
+
+ SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
+
+
+ serf_sess->conns = apr_palloc(serf_sess->pool, sizeof(*serf_sess->conns) * 4);
+
+ 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;
+
+ serf_sess->conns[0]->using_ssl = serf_sess->using_ssl;
+ serf_sess->conns[0]->server_cert_failures = 0;
+ serf_sess->conns[0]->using_compression = serf_sess->using_compression;
+ serf_sess->conns[0]->hostname = url.hostname;
+ serf_sess->conns[0]->useragent = NULL;
+
+ /* create the user agent string */
+ if (callbacks->get_client_string)
+ callbacks->get_client_string(callback_baton, &client_string, pool);
+
+ if (client_string)
+ serf_sess->conns[0]->useragent = apr_pstrcat(pool, USER_AGENT, "/",
+ client_string, (char *)NULL);
+ else
+ serf_sess->conns[0]->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_error_wrap_apr(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);
+}
+
+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(session->pool, url, &new_url);
+ if (status)
+ {
+ return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
+ _("Illegal repository URL '%s'"), url);
+ }
+
+ session->session_url.path = new_url.path;
+ session->session_url_str = apr_pstrdup(session->pool, url);
+
+ return SVN_NO_ERROR;
+}
+
+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;
+}
+
+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)
+{
+ const char *relative_url, *basecoll_url;
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ return svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url, session,
+ NULL, session->session_url.path,
+ SVN_INVALID_REVNUM, latest_revnum,
+ pool);
+}
+
+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));
+ }
+
+ 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;
+}
+
+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 = apr_hash_get(props, name, APR_HASH_KEY_STRING);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fetch_path_props(svn_ra_serf__propfind_context_t **ret_prop_ctx,
+ apr_hash_t **ret_props,
+ const char **ret_path,
+ svn_revnum_t *ret_revision,
+ svn_ra_serf__session_t *session,
+ const char *rel_path,
+ svn_revnum_t revision,
+ const svn_ra_serf__dav_props_t *desired_props,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__propfind_context_t *prop_ctx;
+ apr_hash_t *props;
+ const char *path;
+
+ path = session->session_url.path;
+
+ /* If we have a relative path, append it. */
+ if (rel_path)
+ {
+ path = svn_path_url_add_component2(path, rel_path, pool);
+ }
+
+ props = apr_hash_make(pool);
+
+ /* If we were given a specific revision, we have to fetch the VCC and
+ * do a PROPFIND off of that.
+ */
+ if (!SVN_IS_VALID_REVNUM(revision))
+ {
+ SVN_ERR(svn_ra_serf__deliver_props(&prop_ctx, props, session,
+ session->conns[0], path, revision,
+ "0", desired_props, NULL,
+ pool));
+ }
+ else
+ {
+ const char *relative_url, *basecoll_url;
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url,
+ session, NULL, path,
+ revision, NULL, pool));
+
+ /* We will try again with our new path; however, we're now
+ * technically an unversioned resource because we are accessing
+ * the revision's baseline-collection.
+ */
+ path = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+ revision = SVN_INVALID_REVNUM;
+ SVN_ERR(svn_ra_serf__deliver_props(&prop_ctx, props, session,
+ session->conns[0], path, revision,
+ "0", desired_props, NULL,
+ pool));
+ }
+
+ /* ### switch to svn_ra_serf__retrieve_props? */
+ SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, session, pool));
+
+ *ret_path = path;
+ *ret_prop_ctx = prop_ctx;
+ *ret_props = props;
+ *ret_revision = revision;
+
+ return SVN_NO_ERROR;
+}
+
+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_ra_serf__propfind_context_t *prop_ctx;
+ const char *path;
+ svn_revnum_t fetched_rev;
+
+ svn_error_t *err = fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
+ session, rel_path,
+ revision, check_path_props, 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 err;
+
+ SVN_ERR(svn_ra_serf__get_resource_type(kind, props, path, fetched_rev));
+ }
+
+ 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 = apr_pcalloc(pool, sizeof(*entry));
+
+ apr_hash_set(dirents->full_paths, path, path_len, entry);
+
+ base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool),
+ pool);
+
+ apr_hash_set(dirents->base_paths, base_name, APR_HASH_KEY_STRING, 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;
+}
+
+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_ra_serf__propfind_context_t *prop_ctx;
+ const char *path;
+ svn_revnum_t fetched_rev;
+ svn_error_t *err;
+ struct dirent_walker_baton_t dwb;
+ svn_tristate_t deadprop_count = svn_tristate_unknown;
+
+ err = fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
+ session, rel_path, revision,
+ get_dirent_props(SVN_DIRENT_ALL, session, 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 = apr_pcalloc(pool, sizeof(*dwb.entry));
+ dwb.supports_deadprop_count = &deadprop_count;
+ dwb.result_pool = pool;
+ SVN_ERR(svn_ra_serf__walk_all_props(props, path, fetched_rev,
+ 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(&prop_ctx, &props, &path, &fetched_rev,
+ session, rel_path, fetched_rev,
+ get_dirent_props(SVN_DIRENT_ALL, session, pool),
+ pool));
+
+ SVN_ERR(svn_ra_serf__walk_all_props(props, path, fetched_rev,
+ 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,
+ const char *path,
+ svn_revnum_t revision)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_ra_serf__get_resource_type(&kind, props, path, revision));
+
+ 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;
+}
+
+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)
+ {
+ const char *relative_url, *basecoll_url;
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&basecoll_url, &relative_url,
+ session, NULL, path, revision,
+ fetched_rev, pool));
+
+ path = svn_path_url_add_component2(basecoll_url, relative_url, pool);
+ revision = SVN_INVALID_REVNUM;
+ }
+
+ /* If we're asked for children, fetch them now. */
+ if (dirents)
+ {
+ struct path_dirent_visitor_t dirent_walk;
+ apr_hash_t *props;
+
+ /* 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, revision, "1",
+ get_dirent_props(dirent_fields,
+ session, pool),
+ pool, pool));
+
+ /* Check if the path is really a directory. */
+ SVN_ERR(resource_is_directory(props, path, revision));
+
+ /* 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, revision, 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, revision, "1",
+ get_dirent_props(dirent_fields,
+ session, pool),
+ pool, pool));
+
+ SVN_ERR(svn_hash__clear(dirent_walk.full_paths, pool));
+ SVN_ERR(svn_hash__clear(dirent_walk.base_paths, pool));
+
+ SVN_ERR(svn_ra_serf__walk_all_paths(props, revision,
+ 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__retrieve_props(&props, session, session->conns[0],
+ path, revision, "0", all_props,
+ pool, pool));
+ /* Check if the path is really a directory. */
+ SVN_ERR(resource_is_directory(props, path, revision));
+
+ SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, path, revision,
+ pool, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static 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.
+ */
+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_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(
+ 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..f82b778
--- /dev/null
+++ b/subversion/libsvn_ra_serf/update.c
@@ -0,0 +1,2885 @@
+/*
+ * 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_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 "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,
+ OPEN_DIR,
+ ADD_DIR,
+ OPEN_FILE,
+ ADD_FILE,
+ PROP,
+ IGNORE_PROP_NAME,
+ NEED_PROP_NAME
+} 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 ACTIVE_FETCHES and
+ ACTIVE_PROPFINDS in the report_context_t structure. */
+#define REQUEST_COUNT_TO_PAUSE 1000
+#define REQUEST_COUNT_TO_RESUME 100
+
+
+/* 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 workingcopy)
+ 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;
+
+ /* The target revision we're retrieving. */
+ svn_revnum_t target_rev;
+
+ /* controlling dir baton - this is only created in open_dir() */
+ void *dir_baton;
+ apr_pool_t *dir_baton_pool;
+
+ /* Our master update editor and baton. */
+ const svn_delta_editor_t *update_editor;
+ void *update_baton;
+
+ /* 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__propfind_context_t *propfind;
+
+ /* 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;
+
+ /* The target revision we're retrieving. */
+ svn_revnum_t target_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__propfind_context_t *propfind;
+
+ /* 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; /* ### currently unused */
+ svn_txdelta_window_handler_t textdelta;
+ void *textdelta_baton;
+
+ /* Checksum for close_file */
+ const char *final_checksum;
+
+ /* 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;
+ const char *prop_val;
+ apr_size_t prop_val_len;
+ 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 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;
+
+ /* 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 active_fetches;
+
+ /* completed fetches (contains report_fetch_t) */
+ svn_ra_serf__list_t *done_fetches;
+
+ /* number of pending PROPFIND requests */
+ unsigned int active_propfinds;
+
+ /* completed PROPFIND requests (contains propfind_context_t) */
+ svn_ra_serf__list_t *done_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;
+
+ /* The XML parser context for the REPORT response. */
+ svn_ra_serf__xml_parser_t *parser_ctx;
+};
+
+
+/** 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->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);
+
+ /* Point to the update_editor */
+ new_info->dir->update_editor = ctx->update_editor;
+ new_info->dir->update_baton = ctx->update_baton;
+ 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;
+
+ /* 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->update_editor;
+ const char *prop_name;
+
+ if (strcmp(name, "md5-checksum") == 0
+ && strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
+ info->final_checksum = apr_pstrdup(info->pool, val->data);
+
+ 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->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->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->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*
+open_dir(report_dir_t *dir)
+{
+ /* 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 (dir->report_context->destination &&
+ dir->report_context->sess->wc_callbacks->invalidate_wc_props)
+ {
+ SVN_ERR(dir->report_context->sess->wc_callbacks->invalidate_wc_props(
+ dir->report_context->sess->wc_callback_baton,
+ dir->report_context->update_target,
+ SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool));
+ }
+
+ SVN_ERR(dir->update_editor->open_root(dir->update_baton, dir->base_rev,
+ dir->dir_baton_pool,
+ &dir->dir_baton));
+ }
+ else
+ {
+ SVN_ERR(open_dir(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(dir->update_editor->open_directory(dir->name,
+ dir->parent_dir->dir_baton,
+ dir->base_rev,
+ dir->dir_baton_pool,
+ &dir->dir_baton));
+ }
+ else
+ {
+ SVN_ERR(dir->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->target_rev,
+ set_dir_props, dir,
+ scratch_pool));
+ }
+
+ SVN_ERR(dir->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(open_dir(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->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->conn->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 == FALSE && 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;
+}
+
+/* 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;
+ serf_status_line sl;
+
+ if (fetch_ctx->read_headers == FALSE)
+ {
+ 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;
+
+ err = open_dir(info->dir);
+ if (err)
+ {
+ return error_fetch(request, fetch_ctx, err);
+ }
+
+ 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);
+ }
+
+ if (SVN_IS_VALID_REVNUM(info->base_rev))
+ {
+ err = info->dir->update_editor->open_file(info->name,
+ info->dir->dir_baton,
+ info->base_rev,
+ info->editor_pool,
+ &info->file_baton);
+ }
+ else
+ {
+ err = info->dir->update_editor->add_file(info->name,
+ info->dir->dir_baton,
+ info->copyfrom_path,
+ info->copyfrom_rev,
+ info->editor_pool,
+ &info->file_baton);
+ }
+
+ if (err)
+ {
+ return error_fetch(request, fetch_ctx, err);
+ }
+
+ err = info->dir->update_editor->apply_textdelta(info->file_baton,
+ info->base_checksum,
+ info->editor_pool,
+ &info->textdelta,
+ &info->textdelta_baton);
+
+ if (err)
+ {
+ return error_fetch(request, fetch_ctx, err);
+ }
+
+ if (val && svn_cstring_casecmp(val, "application/vnd.svn-svndiff") == 0)
+ {
+ fetch_ctx->delta_stream =
+ svn_txdelta_parse_svndiff(info->textdelta,
+ info->textdelta_baton,
+ TRUE, info->editor_pool);
+ }
+ 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. */
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_error_wrap_apr(status, NULL);
+ }
+ if (sl.code != 200)
+ {
+ err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
+ _("GET request failed: %d %s"),
+ sl.code, sl.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_error_wrap_apr(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_error_wrap_apr(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;
+
+ /* ### this doesn't feel quite right. but it gets tossed at the
+ ### end of this block, so it will work for now. */
+ apr_pool_t *scratch_pool = info->editor_pool;
+
+ 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);
+ }
+
+ if (info->lock_token)
+ check_lock(info);
+
+ /* set all of the properties we received */
+ err = svn_ra_serf__walk_all_props(info->props,
+ info->base_name,
+ info->base_rev,
+ set_file_props, info,
+ scratch_pool);
+
+ if (!err)
+ err = svn_ra_serf__walk_all_props(info->dir->removed_props,
+ info->base_name,
+ info->base_rev,
+ remove_file_props, info,
+ scratch_pool);
+ if (!err && info->fetch_props)
+ {
+ err = svn_ra_serf__walk_all_props(info->props,
+ info->url,
+ info->target_rev,
+ set_file_props, info,
+ scratch_pool);
+ }
+
+ if (!err)
+ err = info->dir->update_editor->close_file(info->file_baton,
+ info->final_checksum,
+ scratch_pool);
+
+ if (err)
+ {
+ return 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 pools. */
+ svn_pool_destroy(info->editor_pool);
+ svn_pool_destroy(info->pool);
+
+ if (status)
+ return svn_error_wrap_apr(status, NULL);
+ }
+ if (APR_STATUS_IS_EAGAIN(status))
+ {
+ return svn_error_wrap_apr(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;
+ serf_status_line sl;
+ const char *location;
+ svn_error_t *err;
+ apr_status_t status;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_error_wrap_apr(status, NULL);
+ }
+
+ /* Woo-hoo. Nothing here to see. */
+ location = svn_ra_serf__response_get_location(response, pool);
+
+ err = svn_ra_serf__error_on_status(sl.code,
+ fetch_ctx->info->name,
+ location);
+ if (err)
+ {
+ fetch_ctx->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_error_wrap_apr(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_error_wrap_apr(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_error_wrap_apr(status, NULL);
+ }
+ }
+ /* not reached */
+}
+
+static svn_error_t *
+handle_propchange_only(report_info_t *info,
+ apr_pool_t *scratch_pool)
+{
+ /* Ensure our parent is open. */
+ SVN_ERR(open_dir(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);
+ }
+
+ if (SVN_IS_VALID_REVNUM(info->base_rev))
+ {
+ SVN_ERR(info->dir->update_editor->open_file(info->name,
+ info->dir->dir_baton,
+ info->base_rev,
+ info->editor_pool,
+ &info->file_baton));
+ }
+ else
+ {
+ SVN_ERR(info->dir->update_editor->add_file(info->name,
+ info->dir->dir_baton,
+ info->copyfrom_path,
+ info->copyfrom_rev,
+ info->editor_pool,
+ &info->file_baton));
+ }
+
+ if (info->fetch_file)
+ {
+ SVN_ERR(info->dir->update_editor->apply_textdelta(info->file_baton,
+ info->base_checksum,
+ info->editor_pool,
+ &info->textdelta,
+ &info->textdelta_baton));
+ }
+
+ if (info->lock_token)
+ check_lock(info);
+
+ /* 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,
+ info->target_rev,
+ set_file_props, info,
+ scratch_pool));
+ }
+
+ SVN_ERR(info->dir->update_editor->close_file(info->file_baton,
+ info->final_checksum,
+ scratch_pool));
+
+ /* We're done with our pools. */
+ svn_pool_destroy(info->editor_pool);
+ svn_pool_destroy(info->pool);
+
+ info->dir->ref_count--;
+
+ 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 = ctx->sess->conns[ctx->sess->cur_conn];
+
+ /* 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_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS response did not include the "
+ "requested checked-in value"));
+ }
+
+ /* If needed, create the PROPFIND to retrieve the file's properties. */
+ info->propfind = NULL;
+ if (info->fetch_props)
+ {
+ SVN_ERR(svn_ra_serf__deliver_props(&info->propfind, info->props,
+ ctx->sess, conn, info->url,
+ info->target_rev, "0", all_props,
+ &ctx->done_propfinds,
+ info->dir->pool));
+ SVN_ERR_ASSERT(info->propfind);
+
+ ctx->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)
+ {
+ 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->method = "GET";
+ handler->path = fetch_ctx->info->url;
+
+ handler->conn = conn;
+ handler->session = ctx->sess;
+
+ 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;
+
+ svn_ra_serf__request_create(handler);
+
+ ctx->active_fetches++;
+ }
+ else if (info->propfind)
+ {
+ 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.
+
+ Note: we'll use INFO->POOL for the scratch_pool here since it will
+ be destroyed at the end of handle_propchange_only(). That pool
+ would be quite fine, but it is unclear how long INFO->POOL will
+ stick around since its lifetime and usage are unclear. */
+ SVN_ERR(handle_propchange_only(info, info->pool));
+ }
+
+ if (ctx->active_fetches + ctx->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,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ report_context_t *ctx = userData;
+ report_state_e state;
+
+ state = parser->state->current_state;
+
+ 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->dir->target_rev = ctx->target_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 = apr_hash_get(ctx->switched_paths, "",
+ APR_HASH_KEY_STRING);
+
+ 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;
+ dir->target_rev = ctx->target_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 = apr_hash_get(ctx->switched_paths, dir->name,
+ APR_HASH_KEY_STRING);
+
+ 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;
+ dir->target_rev = ctx->target_rev;
+ 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->target_rev = ctx->target_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;
+ info->target_rev = ctx->target_rev;
+ info->fetch_props = TRUE;
+ info->fetch_file = 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(open_dir(info->dir));
+
+ tmppool = svn_pool_create(info->dir->dir_baton_pool);
+
+ full_path = svn_relpath_join(info->dir->name, file_name, tmppool);
+
+ SVN_ERR(info->dir->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(open_dir(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(open_dir(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;
+ info->prop_val = NULL;
+ info->prop_val_len = 0;
+ }
+ 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);
+ info->prop_val = NULL;
+ info->prop_val_len = 0;
+ }
+ 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;
+ info->prop_val = NULL;
+ info->prop_val_len = 0;
+ }
+ 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);
+ info->prop_val = NULL;
+ info->prop_val_len = 0;
+ }
+ 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. */
+ ;
+ }
+ 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);
+ info->prop_val = NULL;
+ info->prop_val_len = 0;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+end_report(svn_ra_serf__xml_parser_t *parser,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ report_context_t *ctx = userData;
+ report_state_e state;
+
+ state = parser->state->current_state;
+
+ if (state == NONE)
+ {
+ /* 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_OPTIONS_REQ_FAILED, NULL,
+ _("The OPTIONS 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 (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props)
+ {
+ /* Unconditionally set fetch_props now. */
+ info->dir->fetch_props = TRUE;
+
+ SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind,
+ info->dir->props, ctx->sess,
+ ctx->sess->conns[ctx->sess->cur_conn],
+ info->dir->url,
+ info->dir->target_rev, "0",
+ all_props,
+ &ctx->done_propfinds,
+ info->dir->pool));
+ SVN_ERR_ASSERT(info->dir->propfind);
+
+ ctx->active_propfinds++;
+
+ if (ctx->active_fetches + ctx->active_propfinds
+ > REQUEST_COUNT_TO_PAUSE)
+ ctx->parser_ctx->paused = TRUE;
+ }
+ else
+ {
+ info->dir->propfind = NULL;
+ }
+
+ 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 = apr_hash_get(ctx->lock_path_tokens, info->name,
+ APR_HASH_KEY_STRING);
+
+ if (info->lock_token && info->fetch_props == FALSE)
+ 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 = apr_hash_get(ctx->switched_paths, info->name,
+ APR_HASH_KEY_STRING);
+
+ 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 = apr_hash_get(ctx->switched_paths, "",
+ APR_HASH_KEY_STRING);
+ }
+ 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;
+ }
+
+ SVN_ERR(fetch_file(ctx, info));
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0)
+ {
+ /* We should have everything we need to fetch the file. */
+ SVN_ERR(fetch_file(ctx, parser->state->private));
+ svn_ra_serf__xml_pop_state(parser);
+ }
+ else if (state == PROP)
+ {
+ /* We need to move the prop_ns, prop_name, and prop_val 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 char *set_val;
+ 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;
+ info->prop_val = "";
+ info->prop_val_len = 1;
+ }
+
+ if (info->prop_encoding)
+ {
+ if (strcmp(info->prop_encoding, "base64") == 0)
+ {
+ svn_string_t encoded;
+ const svn_string_t *decoded;
+
+ encoded.data = info->prop_val;
+ encoded.len = info->prop_val_len;
+
+ decoded = svn_base64_decode_string(&encoded, parser->state->pool);
+
+ info->prop_val = decoded->data;
+ info->prop_val_len = decoded->len;
+ }
+ else
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ NULL,
+ _("Got unrecognized encoding '%s'"),
+ info->prop_encoding);
+ }
+
+ }
+
+ set_val = apr_pmemdup(pool, info->prop_val, info->prop_val_len);
+ set_val_str = svn_string_ncreate(set_val, info->prop_val_len, pool);
+
+ svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev,
+ ns->namespace, ns->url, set_val_str, pool);
+ 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,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ report_context_t *ctx = userData;
+
+ UNUSED_CTX(ctx);
+
+ if (parser->state->current_state == PROP)
+ {
+ report_info_t *info = parser->state->private;
+
+ svn_ra_serf__expand_string(&info->prop_val, &info->prop_val_len,
+ data, len, parser->state->pool);
+ }
+
+ 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)
+ {
+ apr_hash_set(report->lock_path_tokens,
+ apr_pstrdup(report->pool, path),
+ APR_HASH_KEY_STRING,
+ 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);
+ apr_hash_set(report->switched_paths, path, APR_HASH_KEY_STRING,
+ apr_pstrdup(report->pool, link+1));
+
+ if (!*path)
+ report->root_is_switched = TRUE;
+
+ if (lock_token)
+ {
+ apr_hash_set(report->lock_path_tokens, path, APR_HASH_KEY_STRING,
+ apr_pstrdup(report->pool, lock_token));
+ }
+
+ return APR_SUCCESS;
+}
+
+/** Max. number of connctions we'll open to the server. */
+#define MAX_NR_OF_CONNS 4
+/** 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 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 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 ||
+ ((active_reqs / REQS_PER_CONN) > sess->num_conns))
+ {
+ int cur = sess->num_conns;
+ apr_status_t status;
+
+ sess->conns[cur] = apr_palloc(sess->pool, sizeof(*sess->conns[cur]));
+ sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool,
+ NULL, NULL);
+ sess->conns[cur]->hostname = sess->conns[0]->hostname;
+ sess->conns[cur]->using_ssl = sess->conns[0]->using_ssl;
+ sess->conns[cur]->using_compression = sess->conns[0]->using_compression;
+ sess->conns[cur]->useragent = sess->conns[0]->useragent;
+ sess->conns[cur]->last_status_code = -1;
+ sess->conns[cur]->ssl_context = NULL;
+ 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_error_wrap_apr(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;
+}
+
+static svn_error_t *
+headers_report(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *pool)
+{
+ report_context_t *report = baton;
+
+ if (report->conn->using_compression)
+ {
+ serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
+ }
+
+ 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_boolean_t closed_root;
+ int status_code;
+ svn_stringbuf_t *buf = NULL;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ svn_error_t *err;
+
+ 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->method = "REPORT";
+ handler->path = report->path;
+ handler->body_delegate = create_update_report_body;
+ handler->body_delegate_baton = report;
+ handler->body_type = "text/xml";
+ handler->conn = sess->conns[0];
+ handler->session = sess;
+ handler->header_delegate = headers_report;
+ handler->header_delegate_baton = report;
+
+ parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx));
+
+ parser_ctx->pool = pool;
+ 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;
+ /* While we provide a location here to store the status code, we don't
+ do anything with it. The error in parser_ctx->error is sufficient. */
+ parser_ctx->status_code = &status_code;
+
+ 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;
+ closed_root = FALSE;
+
+ /* 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->active_fetches || report->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, sess->timeout, iterpool_inner);
+
+ err = sess->pending_error;
+ sess->pending_error = SVN_NO_ERROR;
+
+ if (APR_STATUS_IS_TIMEUP(status))
+ {
+ svn_error_clear(err);
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT,
+ NULL,
+ _("Connection timed out"));
+ }
+
+ SVN_ERR(err);
+ if (status)
+ {
+ return svn_error_wrap_apr(status, _("Error retrieving REPORT (%d)"),
+ status);
+ }
+
+ /* Open extra connections if we have enough requests to send. */
+ if (sess->num_conns < MAX_NR_OF_CONNS)
+ SVN_ERR(open_connection_if_needed(sess, report->active_fetches +
+ report->active_propfinds));
+
+ /* Switch our connection. */
+ if (!report->done)
+ if (++sess->cur_conn == sess->num_conns)
+ sess->cur_conn = 1;
+
+ /* prune our propfind list if they are done. */
+ done_list = report->done_propfinds;
+ while (done_list)
+ {
+ svn_pool_clear(iterpool_inner);
+
+ report->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 == 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)
+ {
+ SVN_ERR(handle_propchange_only(cur->data, iterpool_inner));
+
+ if (!prev)
+ {
+ report->file_propchanges_only = cur->next;
+ }
+ else
+ {
+ prev->next = cur->next;
+ }
+ }
+ }
+
+ done_list = done_list->next;
+ }
+ report->done_propfinds = NULL;
+
+ /* prune our fetches list if they are done. */
+ done_list = report->done_fetches;
+ while (done_list)
+ {
+ report_fetch_t *done_fetch = done_list->data;
+ report_dir_t *cur_dir;
+
+ /* decrease our parent's directory refcount. */
+ cur_dir = done_fetch->info->dir;
+ cur_dir->ref_count--;
+
+ /* Decrement our active fetch count. */
+ report->active_fetches--;
+
+ done_list = done_list->next;
+
+ /* If we have a valid directory and
+ * we have no open items in this dir and
+ * we've closed the directory tag (no more children can be added)
+ * and either:
+ * we know we won't be fetching props or
+ * we've already completed the propfind
+ * then, we know it's time for us to close this directory.
+ */
+ while (cur_dir && !cur_dir->ref_count && cur_dir->tag_closed &&
+ (!cur_dir->fetch_props ||
+ svn_ra_serf__propfind_is_done(cur_dir->propfind)))
+ {
+ report_dir_t *parent = cur_dir->parent_dir;
+
+ SVN_ERR(close_dir(cur_dir));
+ if (parent)
+ {
+ parent->ref_count--;
+ }
+ else
+ {
+ closed_root = TRUE;
+ }
+ cur_dir = parent;
+ }
+ }
+ report->done_fetches = NULL;
+
+ /* If the parser is paused, and the number of active requests has
+ dropped far enough, then resume parsing. */
+ if (parser_ctx->paused
+ && (report->active_fetches + report->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, iterpool_inner));
+
+ /* Debugging purposes only! */
+ for (i = 0; i < sess->num_conns; i++)
+ {
+ serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
+ }
+ }
+
+ /* Ensure that we opened and closed our root dir and that we closed
+ * all of our children. */
+ if (closed_root == FALSE && report->root_dir != NULL)
+ {
+ SVN_ERR(close_all_dirs(report->root_dir));
+ }
+
+ err = report->update_editor->close_edit(report->update_baton, iterpool);
+
+ 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)
+{
+ /* ### would be nice to get a SCRATCH_POOL passed to us. */
+ apr_pool_t *scratch_pool = svn_pool_create(result_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_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));
+
+ svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, "S:update-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ NULL);
+
+ 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);
+ }
+
+ 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));
+
+ svn_pool_destroy(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,
+ const svn_delta_editor_t *update_editor,
+ void *update_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__session_t *session = ra_session->priv;
+
+ return make_update_reporter(ra_session, reporter, report_baton,
+ revision_to_update_to,
+ session->session_url.path, NULL, update_target,
+ depth, FALSE, TRUE, send_copyfrom_args,
+ update_editor, update_baton, pool);
+}
+
+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;
+
+ return 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);
+}
+
+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;
+
+ return 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);
+}
+
+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,
+ const svn_delta_editor_t *switch_editor,
+ void *switch_baton,
+ apr_pool_t *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, TRUE, TRUE, FALSE /* TODO(sussman) */,
+ switch_editor, switch_baton, pool);
+}
+
+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;
+
+ /* 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)
+ {
+ const char *baseline_url, *rel_path;
+
+ SVN_ERR(svn_ra_serf__get_baseline_info(&baseline_url, &rel_path,
+ session, conn, fetch_url,
+ revision, fetched_rev, pool));
+ fetch_url = svn_path_url_add_component2(baseline_url, rel_path, pool);
+ revision = SVN_INVALID_REVNUM;
+ }
+
+ SVN_ERR(svn_ra_serf__retrieve_props(&fetch_props, session, conn, fetch_url,
+ revision, "0",
+ props ? all_props : check_path_props,
+ pool, pool));
+
+ /* Verify that resource type is not colelction. */
+ SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props, fetch_url,
+ revision));
+ 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)
+ {
+ SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props, fetch_url,
+ revision, pool, pool));
+ }
+
+ if (stream)
+ {
+ 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->method = "GET";
+ handler->path = fetch_url;
+ handler->conn = conn;
+ handler->session = session;
+
+ 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;
+
+ 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..180ff7a
--- /dev/null
+++ b/subversion/libsvn_ra_serf/util.c
@@ -0,0 +1,2265 @@
+/*
+ * 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 "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_private_config.h"
+#include "svn_string.h"
+#include "svn_xml.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_fspath.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
+
+
+#define PARSE_CHUNK_SIZE 8000
+
+/* As chunks of content arrive from the server, and we need to hold them
+ in memory (because the XML parser is paused), they are copied into
+ these buffers. The buffers are arranged into a linked list. */
+struct pending_buffer_t {
+ apr_size_t size;
+ char data[PARSE_CHUNK_SIZE];
+
+ struct pending_buffer_t *next;
+};
+
+
+/* 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 amount of content in memory. */
+ apr_size_t memory_size;
+
+ /* HEAD points to the first block of the linked list of buffers.
+ TAIL points to the last block, for quickly appending more blocks
+ to the overall list. */
+ struct pending_buffer_t *head;
+ struct pending_buffer_t *tail;
+
+ /* Available blocks for storing pending data. These were allocated
+ previously, then the data consumed and returned to this list. */
+ struct pending_buffer_t *avail;
+
+ /* Once MEMORY_SIZE exceeds SPILL_SIZE, then arriving content will be
+ appended to the (temporary) file indicated by SPILL. */
+ apr_file_t *spill;
+
+ /* As we consume content from SPILL, this value indicates where we
+ will begin reading. */
+ apr_off_t spill_start;
+
+ /* 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)->head != NULL || (p)->spill != NULL))
+
+/* We will store one megabyte in memory, before switching to store content
+ into a temporary file. */
+#define SPILL_SIZE 1000000
+
+/* See notes/ra-serf-testing.txt for some information on testing this
+ new "paused" feature. */
+
+
+
+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;
+}
+
+/* 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*)apr_hash_get(org, "OU", APR_HASH_KEY_STRING),
+ (char*)apr_hash_get(org, "O", APR_HASH_KEY_STRING),
+ (char*)apr_hash_get(org, "L", APR_HASH_KEY_STRING),
+ (char*)apr_hash_get(org, "ST", APR_HASH_KEY_STRING),
+ (char*)apr_hash_get(org, "C", APR_HASH_KEY_STRING),
+ (char*)apr_hash_get(org, "E", APR_HASH_KEY_STRING));
+}
+
+/* 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 = apr_hash_get(subject, "CN", APR_HASH_KEY_STRING);
+ san = apr_hash_get(serf_cert, "subjectAltName", APR_HASH_KEY_STRING);
+ cert_info.fingerprint = apr_hash_get(serf_cert, "sha1", APR_HASH_KEY_STRING);
+ if (! cert_info.fingerprint)
+ cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
+ cert_info.valid_from = apr_hash_get(serf_cert, "notBefore",
+ APR_HASH_KEY_STRING);
+ if (! cert_info.valid_from)
+ cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
+ cert_info.valid_until = apr_hash_get(serf_cert, "notAfter",
+ APR_HASH_KEY_STRING);
+ 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->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->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 = ssl_server_cert(baton, failures, cert, subpool);
+
+ svn_pool_destroy(subpool);
+
+ 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;
+
+}
+
+static svn_error_t *
+load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
+ apr_pool_t *pool)
+{
+ char *files, *file, *last;
+ files = apr_pstrdup(pool, authorities);
+
+ while ((file = apr_strtok(files, ";", &last)) != NULL)
+ {
+ 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));
+ }
+ files = NULL;
+ }
+
+ 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->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->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;
+ apr_status_t status = SVN_NO_ERROR;
+
+ svn_error_t *err = conn_setup(sock,
+ read_bkt,
+ write_bkt,
+ baton,
+ pool);
+
+ if (err || session->pending_error)
+ {
+ session->pending_error = svn_error_compose_create(
+ session->pending_error,
+ err);
+
+ status = session->pending_error->apr_err;
+ }
+
+ return status;
+}
+
+serf_bucket_t*
+svn_ra_serf__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);
+}
+
+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 = svn_ra_serf__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(serf_connection_t *conn,
+ svn_ra_serf__connection_t *sc,
+ apr_status_t why,
+ apr_pool_t *pool)
+{
+ if (why)
+ {
+ SVN_ERR_MALFUNCTION();
+ }
+
+ if (sc->using_ssl)
+ sc->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 *sc = closed_baton;
+ svn_error_t *err;
+
+ err = connection_closed(conn, sc, why, pool);
+
+ if (err)
+ sc->session->pending_error = svn_error_compose_create(
+ sc->session->pending_error,
+ 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 = handle_client_cert(data,
+ cert_path,
+ session->pool);
+
+ 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;
+}
+
+/* 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 = handle_client_cert_pw(data,
+ cert_path,
+ password,
+ session->pool);
+
+ 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;
+}
+
+
+svn_error_t *
+svn_ra_serf__setup_serf_req(serf_request_t *request,
+ serf_bucket_t **req_bkt,
+ serf_bucket_t **ret_hdrs_bkt,
+ svn_ra_serf__connection_t *conn,
+ const char *method, const char *url,
+ serf_bucket_t *body_bkt, const char *content_type)
+{
+ serf_bucket_t *hdrs_bkt;
+
+ /* 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,
+ serf_request_get_alloc(request));
+
+ hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
+ serf_bucket_headers_setn(hdrs_bkt, "User-Agent", conn->useragent);
+
+ if (content_type)
+ {
+ serf_bucket_headers_setn(hdrs_bkt, "Content-Type", content_type);
+ }
+
+ /* 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_set(hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
+ serf_bucket_headers_set(hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
+ serf_bucket_headers_set(hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
+
+ if (ret_hdrs_bkt)
+ {
+ *ret_hdrs_bkt = hdrs_bkt;
+ }
+
+ 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;
+
+ 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, sess->timeout, iterpool);
+
+ err = sess->pending_error;
+ sess->pending_error = SVN_NO_ERROR;
+
+ if (APR_STATUS_IS_TIMEUP(status))
+ {
+ svn_error_clear(err);
+ return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT,
+ NULL,
+ _("Connection timed out"));
+ }
+
+ 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_error_wrap_apr(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;
+}
+
+
+/*
+ * 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,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ svn_ra_serf__server_error_t *ctx = userData;
+
+ 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;
+ }
+ else
+ {
+ 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,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ svn_ra_serf__server_error_t *ctx = userData;
+
+ 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. */
+ apr_size_t len;
+ const char *cd = ctx->cdata->data;
+ if (*cd == '\n')
+ ++cd;
+ len = strlen(cd);
+ if (len > 0 && cd[len-1] == '\n')
+ --len;
+
+ ctx->error->message = apr_pstrmemdup(ctx->error->pool, cd, 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,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ svn_ra_serf__server_error_t *ctx = userData;
+
+ 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_discard_body(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ apr_status_t status;
+ svn_ra_serf__server_error_t *server_err = baton;
+
+ if (server_err)
+ {
+ if (!server_err->init)
+ {
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ server_err->init = TRUE;
+ 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)
+ {
+ server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err->has_xml_response = TRUE;
+ server_err->contains_precondition_error = FALSE;
+ server_err->cdata = svn_stringbuf_create("", 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;
+ }
+ else
+ {
+ server_err->error = SVN_NO_ERROR;
+ }
+ }
+
+ if (server_err->has_xml_response)
+ {
+ svn_error_t *err = svn_ra_serf__handle_xml_parser(
+ request,
+ response,
+ &server_err->parser,
+ pool);
+
+ 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(err);
+ }
+
+ }
+
+ status = svn_ra_serf__response_discard_handler(request, response,
+ NULL, pool);
+
+ if (status)
+ return svn_error_wrap_apr(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)
+{
+ /* Just loop through and discard the body. */
+ while (1)
+ {
+ apr_status_t status;
+ const char *data;
+ apr_size_t len;
+
+ status = serf_bucket_read(response, SERF_READ_ALL_AVAIL, &data, &len);
+
+ if (status)
+ {
+ return status;
+ }
+
+ /* feed me */
+ }
+}
+
+const char *
+svn_ra_serf__response_get_location(serf_bucket_t *response,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *headers;
+ const char *val;
+
+ headers = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(headers, "Location");
+ return val ? svn_urlpath__canonicalize(val, pool) : NULL;
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_status_only(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_ra_serf__simple_request_context_t *ctx = baton;
+
+ SVN_ERR_ASSERT(ctx->pool);
+
+ err = svn_ra_serf__handle_discard_body(request, response,
+ &ctx->server_error, pool);
+
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ {
+ serf_status_line sl;
+ apr_status_t status;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_error_wrap_apr(status, NULL);
+ }
+
+ ctx->status = sl.code;
+ ctx->reason = sl.reason ? apr_pstrdup(ctx->pool, sl.reason) : NULL;
+ ctx->location = svn_ra_serf__response_get_location(response, ctx->pool);
+ ctx->done = TRUE;
+ }
+
+ return svn_error_trace(err);
+}
+
+/* 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,
+ void *userData,
+ svn_ra_serf__dav_props_t name,
+ const char **attrs)
+{
+ svn_ra_serf__server_error_t *ctx = userData;
+
+ 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,
+ void *userData,
+ svn_ra_serf__dav_props_t name)
+{
+ svn_ra_serf__server_error_t *ctx = userData;
+
+ 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)
+ {
+ 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,
+ void *userData,
+ const char *data,
+ apr_size_t len)
+{
+ svn_ra_serf__server_error_t *ctx = userData;
+
+ 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 *pool)
+{
+ svn_error_t *err;
+ svn_ra_serf__simple_request_context_t *ctx = baton;
+ svn_ra_serf__server_error_t *server_err = &ctx->server_error;
+
+ SVN_ERR_ASSERT(ctx->pool);
+
+ /* If necessary, initialize our XML parser. */
+ if (server_err && !server_err->init)
+ {
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ server_err->init = TRUE;
+ 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)
+ {
+ server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
+ server_err->has_xml_response = TRUE;
+ server_err->contains_precondition_error = FALSE;
+ server_err->cdata = svn_stringbuf_create("", 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_207;
+ server_err->parser.end = end_207;
+ server_err->parser.cdata = cdata_207;
+ server_err->parser.done = &ctx->done;
+ server_err->parser.ignore_errors = TRUE;
+ }
+ else
+ {
+ ctx->done = TRUE;
+ server_err->error = SVN_NO_ERROR;
+ }
+ }
+
+ /* If server_err->error still contains APR_SUCCESS, it means that we
+ have not successfully parsed the XML yet. */
+ if (server_err && server_err->error
+ && server_err->error->apr_err == APR_SUCCESS)
+ {
+ err = svn_ra_serf__handle_xml_parser(request, response,
+ &server_err->parser, pool);
+
+ /* APR_EOF will be returned when parsing is complete. If we see
+ any other error, return it immediately. In practice the only
+ other error we expect to see is APR_EAGAIN, which indicates that
+ we could not parse the XML because the contents are not yet
+ available to be read. */
+ if (!err || !APR_STATUS_IS_EOF(err->apr_err))
+ {
+ return svn_error_trace(err);
+ }
+ else if (ctx->done && server_err->error->apr_err == APR_SUCCESS)
+ {
+ svn_error_clear(server_err->error);
+ server_err->error = SVN_NO_ERROR;
+ }
+
+ svn_error_clear(err);
+ }
+
+ err = svn_ra_serf__handle_discard_body(request, response, NULL, pool);
+
+ if (err && APR_STATUS_IS_EOF(err->apr_err))
+ {
+ serf_status_line sl;
+ apr_status_t status;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_error_wrap_apr(status, NULL);
+ }
+
+ ctx->status = sl.code;
+ ctx->reason = sl.reason ? apr_pstrdup(ctx->pool, sl.reason) : NULL;
+ ctx->location = svn_ra_serf__response_get_location(response, ctx->pool);
+ }
+
+ return svn_error_trace(err);
+}
+
+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;
+
+ if (parser->error)
+ return;
+
+ if (!parser->state)
+ svn_ra_serf__xml_push_state(parser, 0);
+
+ 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);
+
+ parser->error = parser->start(parser, parser->user_data, name, attrs);
+}
+
+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;
+
+ if (parser->error)
+ return;
+
+ svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
+
+ parser->error = parser->end(parser, parser->user_data, name);
+}
+
+static void
+cdata_xml(void *userData, const char *data, int len)
+{
+ svn_ra_serf__xml_parser_t *parser = userData;
+
+ if (parser->error)
+ return;
+
+ if (!parser->state)
+ svn_ra_serf__xml_push_state(parser, 0);
+
+ parser->error = parser->cdata(parser, parser->user_data, data, len);
+}
+
+/* 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;
+ }
+ }
+}
+
+
+/* Get a buffer from the parsing context. It will come from the free list,
+ or allocated as necessary. */
+static struct pending_buffer_t *
+get_buffer(svn_ra_serf__xml_parser_t *parser)
+{
+ struct pending_buffer_t *pb;
+
+ if (parser->pending->avail == NULL)
+ return apr_palloc(parser->pool, sizeof(*pb));
+
+ pb = parser->pending->avail;
+ parser->pending->avail = pb->next;
+ return pb;
+}
+
+
+/* Return PB to the list of available buffers in PARSER. */
+static void
+return_buffer(svn_ra_serf__xml_parser_t *parser,
+ struct pending_buffer_t *pb)
+{
+ pb->next = parser->pending->avail;
+ parser->pending->avail = pb;
+}
+
+
+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)
+{
+ struct pending_buffer_t *pb;
+
+ /* The caller should not have provided us more than we can store into
+ a single memory block. */
+ SVN_ERR_ASSERT(len <= PARSE_CHUNK_SIZE);
+
+ if (ctx->pending == NULL)
+ ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
+
+ /* We do not (yet) have a spill file, but the amount stored in memory
+ has grown too large. Create the file and place the pending data into
+ the temporary file. */
+ if (ctx->pending->spill == NULL
+ && ctx->pending->memory_size > SPILL_SIZE)
+ {
+ SVN_ERR(svn_io_open_unique_file3(&ctx->pending->spill,
+ NULL /* temp_path */,
+ NULL /* dirpath */,
+ svn_io_file_del_on_pool_cleanup,
+ ctx->pool, scratch_pool));
+ }
+
+ /* Once a spill file has been constructed, then we need to put all
+ arriving data into the file. We will no longer attempt to hold it
+ in memory. */
+ if (ctx->pending->spill != NULL)
+ {
+ /* NOTE: we assume the file position is at the END. The caller should
+ ensure this, so that we will append. */
+ SVN_ERR(svn_io_file_write_full(ctx->pending->spill, data, len,
+ NULL, scratch_pool));
+ return SVN_NO_ERROR;
+ }
+
+ /* We're still within bounds of holding the pending information in
+ memory. Get a buffer, copy the data there, and link it into our
+ pending data. */
+ pb = get_buffer(ctx);
+ /* NOTE: *pb is uninitialized. All fields must be stored. */
+
+ pb->size = len;
+ memcpy(pb->data, data, len);
+ pb->next = NULL;
+
+ /* Start a list of buffers, or append to the end of the linked list
+ of buffers. */
+ if (ctx->pending->tail == NULL)
+ {
+ ctx->pending->head = pb;
+ ctx->pending->tail = pb;
+ }
+ else
+ {
+ ctx->pending->tail->next = pb;
+ ctx->pending->tail = pb;
+ }
+
+ /* We need to record how much is buffered in memory. Once we reach
+ SPILL_SIZE (or thereabouts, it doesn't have to be precise), then
+ we'll switch to putting the content into a file. */
+ ctx->pending->memory_size += len;
+
+ return SVN_NO_ERROR;
+}
+
+
+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, 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)
+ {
+ XML_ParserFree(*xmlp);
+ *xmlp = NULL;
+ }
+
+ return APR_SUCCESS;
+}
+
+svn_error_t *
+svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
+ apr_pool_t *scratch_pool)
+{
+ struct pending_buffer_t *pb;
+ svn_error_t *err;
+ apr_off_t output_unused;
+
+ /* Fast path exit: already paused, nothing to do, or already done. */
+ if (parser->paused || parser->pending == NULL || *parser->done)
+ return SVN_NO_ERROR;
+
+ /* ### it is possible that the XML parsing of the pending content is
+ ### so slow, and that we don't return to reading the connection
+ ### fast enough... that the server will disconnect us. right now,
+ ### that is highly improbably, but is noted for future's sake.
+ ### should that ever happen, the loops in this function can simply
+ ### terminate after N seconds. */
+
+ /* Empty out memory buffers until we run out, or we get paused again. */
+ while (parser->pending->head != NULL)
+ {
+ /* Pull the HEAD buffer out of the list. */
+ pb = parser->pending->head;
+ if (parser->pending->tail == pb)
+ parser->pending->head = parser->pending->tail = NULL;
+ else
+ parser->pending->head = pb->next;
+
+ /* We're using less memory now. If we haven't hit the spill file,
+ then we may be able to keep using memory. */
+ parser->pending->memory_size -= pb->size;
+
+ err = inject_to_parser(parser, pb->data, pb->size, NULL);
+
+ return_buffer(parser, pb);
+
+ if (err)
+ return svn_error_trace(err);
+
+ /* If the callbacks paused us, then we're done for now. */
+ if (parser->paused)
+ return SVN_NO_ERROR;
+ }
+
+ /* If we don't have a spill file, then we've exhausted all
+ pending content. */
+ if (parser->pending->spill == NULL)
+ goto pending_empty;
+
+ /* Seek once to where we left off reading. */
+ output_unused = parser->pending->spill_start; /* ### stupid API */
+ SVN_ERR(svn_io_file_seek(parser->pending->spill,
+ APR_SET, &output_unused,
+ scratch_pool));
+
+ /* We need a buffer for reading out of the file. One of these will always
+ exist by the time we start reading from the spill file. */
+ pb = get_buffer(parser);
+
+ /* Keep reading until we hit EOF, or get paused again. */
+ while (TRUE)
+ {
+ apr_size_t len = sizeof(pb->data);
+ apr_status_t status;
+
+ /* Read some data and remember where we left off. */
+ status = apr_file_read(parser->pending->spill, pb->data, &len);
+ if (status && !APR_STATUS_IS_EOF(status))
+ {
+ err = svn_error_wrap_apr(status, NULL);
+ break;
+ }
+ parser->pending->spill_start += len;
+
+ err = inject_to_parser(parser, pb->data, len, NULL);
+ if (err)
+ break;
+
+ /* If we just consumed everything in the spill file, then we may
+ be done with the parsing. */
+ /* ### future change: when we hit EOF, then remove the spill file.
+ ### we could go back to using memory for a while. */
+ if (APR_STATUS_IS_EOF(status))
+ goto pending_empty;
+
+ /* If the callbacks paused the parsing, then we're done for now. */
+ if (parser->paused)
+ break;
+ }
+
+ return_buffer(parser, pb);
+ return svn_error_trace(err); /* may be SVN_NO_ERROR */
+
+ pending_empty:
+ /* If the PENDING structures are empty *and* we consumed all content from
+ the network, then we're completely done with the parsing. */
+ if (parser->pending->network_eof)
+ {
+ /* 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);
+ add_done_item(parser);
+ }
+ 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;
+
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ return svn_error_wrap_apr(status, NULL);
+ }
+
+ if (ctx->status_code)
+ {
+ *ctx->status_code = sl.code;
+ }
+
+ if (sl.code == 301 || sl.code == 302 || sl.code == 307)
+ {
+ ctx->location = svn_ra_serf__response_get_location(response, ctx->pool);
+ }
+
+ /* Woo-hoo. Nothing here to see. */
+ if (sl.code == 404 && ctx->ignore_errors == FALSE)
+ {
+ /* If our caller won't know about the 404, abort() for now. */
+ SVN_ERR_ASSERT(ctx->status_code);
+
+ add_done_item(ctx);
+
+ err = svn_ra_serf__handle_server_error(request, response, pool);
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__handle_discard_body(request, response, NULL, pool),
+ err));
+ return SVN_NO_ERROR;
+ }
+
+ 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);
+ }
+ }
+
+ /* If we are storing content into a spill file, then move to the end of
+ the file. We need to pre-position the file so that write_to_pending()
+ will always append the content. */
+ if (ctx->pending != NULL && ctx->pending->spill != NULL)
+ {
+ apr_off_t output_unused = 0; /* ### stupid API */
+
+ SVN_ERR(svn_io_file_seek(ctx->pending->spill,
+ APR_END, &output_unused,
+ pool));
+ }
+
+ 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_error_wrap_apr(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_error_wrap_apr(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)
+ {
+ 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_error_wrap_apr(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))
+ {
+ /* 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_error_wrap_apr(status, NULL);
+ }
+
+ /* feed me! */
+ }
+ /* not reached */
+}
+
+/* Implements svn_ra_serf__response_handler_t */
+svn_error_t *
+svn_ra_serf__handle_server_error(serf_request_t *request,
+ serf_bucket_t *response,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__server_error_t server_err = { 0 };
+
+ svn_error_clear(svn_ra_serf__handle_discard_body(request, response,
+ &server_err, pool));
+
+ return server_err.error;
+}
+
+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 *ctx = baton;
+ svn_ra_serf__session_t *session = ctx->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)
+ {
+ session->pending_error
+ = svn_error_compose_create(session->pending_error, err);
+ return err->apr_err;
+ }
+
+ session->auth_attempts++;
+
+ if (!creds || session->auth_attempts > 4)
+ {
+ /* No more credentials. */
+ session->pending_error
+ = svn_error_compose_create(
+ session->pending_error,
+ svn_error_create(
+ SVN_ERR_AUTHN_FAILED, NULL,
+ _("No more credentials or we tried too many times.\n"
+ "Authentication 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. */
+ session->pending_error
+ = svn_error_compose_create(
+ ctx->session->pending_error,
+ svn_error_create(SVN_ERR_AUTHN_FAILED, NULL,
+ _("Proxy authentication failed")));
+ return SVN_ERR_AUTHN_FAILED;
+ }
+ }
+
+ ctx->conn->last_status_code = code;
+
+ return APR_SUCCESS;
+}
+
+/* Wait for HTTP response status and headers, and invoke CTX->
+ 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 *ctx,
+ apr_status_t *serf_status,
+ apr_pool_t *pool)
+{
+ serf_status_line sl;
+ apr_status_t status;
+
+ if (!response)
+ {
+ /* Uh-oh. Our connection died. Requeue. */
+ if (ctx->response_error)
+ SVN_ERR(ctx->response_error(request, response, 0,
+ ctx->response_error_baton));
+
+ svn_ra_serf__request_create(ctx);
+
+ return APR_SUCCESS;
+ }
+
+ status = serf_bucket_response_status(response, &sl);
+ if (SERF_BUCKET_READ_ERROR(status))
+ {
+ *serf_status = status;
+ return SVN_NO_ERROR; /* Handled by serf */
+ }
+ if (!sl.version && (APR_STATUS_IS_EOF(status) ||
+ APR_STATUS_IS_EAGAIN(status)))
+ {
+ *serf_status = status;
+ return SVN_NO_ERROR; /* Handled by serf */
+ }
+
+ status = serf_bucket_response_wait_for_headers(response);
+ if (status)
+ {
+ if (!APR_STATUS_IS_EOF(status))
+ {
+ *serf_status = status;
+ return SVN_NO_ERROR;
+ }
+
+ /* 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(ctx->method, "HEAD") != 0 && sl.code != 204 && sl.code != 304)
+ {
+ svn_error_t *err =
+ svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
+ svn_error_wrap_apr(status, NULL),
+ _("Premature EOF seen from server "
+ "(http status=%d)"), sl.code);
+ /* This discard may be no-op, but let's preserve the algorithm
+ used elsewhere in this function for clarity's sake. */
+ svn_ra_serf__response_discard_handler(request, response, NULL, pool);
+ return err;
+ }
+ }
+
+ if (ctx->conn->last_status_code == 401 && sl.code < 400)
+ {
+ SVN_ERR(svn_auth_save_credentials(ctx->session->auth_state,
+ ctx->session->pool));
+ ctx->session->auth_attempts = 0;
+ ctx->session->auth_state = NULL;
+ }
+
+ ctx->conn->last_status_code = sl.code;
+
+ if (sl.code == 405 || sl.code == 409 || sl.code >= 500)
+ {
+ /* 405 Method Not allowed.
+ 409 Conflict: can indicate a hook error.
+ 5xx (Internal) Server error. */
+ SVN_ERR(svn_ra_serf__handle_server_error(request, response, pool));
+
+ if (!ctx->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 (sl.code == 405 && !strcmp(ctx->method, "LOCK"))
+ apr_err = SVN_ERR_FS_OUT_OF_DATE;
+
+ return
+ svn_error_createf(apr_err, NULL,
+ _("%s request on '%s' failed: %d %s"),
+ ctx->method, ctx->path, sl.code, sl.reason);
+ }
+
+ return SVN_NO_ERROR; /* Error is set in caller */
+ }
+ else
+ {
+ svn_error_t *err;
+
+ err = ctx->response_handler(request,response, ctx->response_baton, pool);
+
+ if (err
+ && (!SERF_BUCKET_READ_ERROR(err->apr_err)
+ || APR_STATUS_IS_ECONNRESET(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 ctx->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 *pool)
+{
+ svn_ra_serf__handler_t *ctx = baton;
+ svn_ra_serf__session_t *session = ctx->session;
+ svn_error_t *err;
+ apr_status_t serf_status = APR_SUCCESS;
+
+ err = svn_error_trace(
+ handle_response(request, response, ctx, &serf_status, pool));
+
+ if (err || session->pending_error)
+ {
+ session->pending_error = svn_error_compose_create(session->pending_error,
+ err);
+
+ serf_status = session->pending_error->apr_err;
+ }
+
+ return serf_status;
+}
+
+/* If the CTX->setup() callback is non-NULL, invoke it to carry out the
+ majority of the serf_request_setup_t implementation. Otherwise, perform
+ default 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 *ctx,
+ serf_bucket_t **req_bkt,
+ serf_response_acceptor_t *acceptor,
+ void **acceptor_baton,
+ serf_response_handler_t *handler,
+ void **handler_baton,
+ apr_pool_t *pool)
+{
+ serf_bucket_t *headers_bkt;
+
+ *acceptor = svn_ra_serf__accept_response;
+ *acceptor_baton = ctx->session;
+
+ if (ctx->setup)
+ {
+ svn_ra_serf__response_handler_t response_handler;
+ void *response_baton;
+
+ SVN_ERR(ctx->setup(request, ctx->setup_baton, req_bkt,
+ acceptor, acceptor_baton,
+ &response_handler, &response_baton,
+ pool));
+
+ ctx->response_handler = response_handler;
+ ctx->response_baton = response_baton;
+ }
+ else
+ {
+ serf_bucket_t *body_bkt;
+ serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
+
+ if (strcmp(ctx->method, "HEAD") == 0)
+ {
+ *acceptor = accept_head;
+ }
+
+ if (ctx->body_delegate)
+ {
+ SVN_ERR(ctx->body_delegate(&body_bkt, ctx->body_delegate_baton,
+ bkt_alloc, pool));
+ }
+ else
+ {
+ body_bkt = NULL;
+ }
+
+ SVN_ERR(svn_ra_serf__setup_serf_req(request, req_bkt, &headers_bkt,
+ ctx->conn, ctx->method, ctx->path,
+ body_bkt, ctx->body_type));
+
+ if (ctx->header_delegate)
+ {
+ SVN_ERR(ctx->header_delegate(headers_bkt, ctx->header_delegate_baton,
+ pool));
+ }
+ }
+
+ *handler = handle_response_cb;
+ *handler_baton = ctx;
+
+ 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 *handler,
+ void **handler_baton,
+ apr_pool_t *pool)
+{
+ svn_ra_serf__handler_t *ctx = setup_baton;
+ svn_error_t *err;
+
+ err = setup_request(request, ctx,
+ req_bkt,
+ acceptor, acceptor_baton,
+ handler, handler_baton,
+ pool);
+
+ if (err)
+ {
+ ctx->session->pending_error
+ = svn_error_compose_create(ctx->session->pending_error,
+ err);
+
+ return err->apr_err;
+ }
+
+ return APR_SUCCESS;
+}
+
+void
+svn_ra_serf__request_create(svn_ra_serf__handler_t *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__retrieve_props(&props, session, conn,
+ path, SVN_INVALID_REVNUM,
+ "0", base_props, pool, pool);
+ if (! err)
+ {
+ *vcc_url =
+ svn_ra_serf__get_ver_prop(props, path,
+ SVN_INVALID_REVNUM,
+ "DAV:",
+ "version-controlled-configuration");
+
+ relative_path = svn_ra_serf__get_ver_prop(props, path,
+ SVN_INVALID_REVNUM,
+ SVN_DAV_PROP_NS_DAV,
+ "baseline-relative-path");
+
+ uuid = svn_ra_serf__get_ver_prop(props, path,
+ SVN_INVALID_REVNUM,
+ SVN_DAV_PROP_NS_DAV,
+ "repository-uuid");
+ break;
+ }
+ else
+ {
+ if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
+ (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
+ {
+ return 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 = apr_pstrdup(session->pool, url_buf->data);
+ 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);
+ if (strcmp(decoded_root, decoded_orig) == 0)
+ {
+ *rel_path = "";
+ }
+ else
+ {
+ *rel_path = svn_urlpath__is_child(decoded_root, decoded_orig, pool);
+ 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;
+}
diff --git a/subversion/libsvn_ra_serf/xml.c b/subversion/libsvn_ra_serf/xml.c
new file mode 100644
index 0000000..ad8a1a1
--- /dev/null
+++ b/subversion/libsvn_ra_serf/xml.c
@@ -0,0 +1,342 @@
+/*
+ * 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 <expat.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_path.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+
+
+void
+svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list,
+ const char **attrs,
+ apr_pool_t *pool)
+{
+ const char **tmp_attrs = attrs;
+
+ while (*tmp_attrs)
+ {
+ if (strncmp(*tmp_attrs, "xmlns", 5) == 0)
+ {
+ svn_ra_serf__ns_t *new_ns, *cur_ns;
+ int found = 0;
+
+ /* Have we already defined this ns previously? */
+ for (cur_ns = *ns_list; cur_ns; cur_ns = cur_ns->next)
+ {
+ if (strcmp(cur_ns->namespace, tmp_attrs[0] + 6) == 0)
+ {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ new_ns = apr_palloc(pool, sizeof(*new_ns));
+ new_ns->namespace = apr_pstrdup(pool, tmp_attrs[0] + 6);
+ new_ns->url = apr_pstrdup(pool, tmp_attrs[1]);
+
+ new_ns->next = *ns_list;
+
+ *ns_list = new_ns;
+ }
+ }
+ tmp_attrs += 2;
+ }
+}
+
+/*
+ * 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,
+ svn_ra_serf__ns_t *ns_list,
+ const char *name)
+{
+ const char *colon;
+
+ colon = strchr(name, ':');
+ if (colon)
+ {
+ 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;
+ }
+ }
+ }
+
+ /* If there is no prefix, or if the prefix is not found, then the
+ name is NOT within a namespace. */
+ returned_prop_name->namespace = "";
+ returned_prop_name->name = name;
+}
+
+void
+svn_ra_serf__expand_string(const char **cur, apr_size_t *cur_len,
+ const char *new, apr_size_t new_len,
+ apr_pool_t *pool)
+{
+ if (!*cur)
+ {
+ *cur = apr_pstrmemdup(pool, new, new_len);
+ *cur_len = new_len;
+ }
+ else
+ {
+ char *new_cur;
+
+ /* append the data we received before. */
+ new_cur = apr_palloc(pool, *cur_len + new_len + 1);
+
+ memcpy(new_cur, *cur, *cur_len);
+ memcpy(new_cur + *cur_len, new, new_len);
+
+ /* NULL-term our new string */
+ new_cur[*cur_len + new_len] = '\0';
+
+ /* update our length */
+ *cur_len += new_len;
+ *cur = new_cur;
+ }
+}
+
+#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;
+}