summaryrefslogtreecommitdiff
path: root/subversion/libsvn_ra_serf
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_ra_serf')
-rw-r--r--subversion/libsvn_ra_serf/README84
-rw-r--r--subversion/libsvn_ra_serf/blame.c20
-rw-r--r--subversion/libsvn_ra_serf/commit.c356
-rw-r--r--subversion/libsvn_ra_serf/eagain_bucket.c7
-rw-r--r--subversion/libsvn_ra_serf/get_file.c33
-rw-r--r--subversion/libsvn_ra_serf/getlocations.c5
-rw-r--r--subversion/libsvn_ra_serf/getlocationsegments.c8
-rw-r--r--subversion/libsvn_ra_serf/libsvn_ra_serf.pc.in2
-rw-r--r--subversion/libsvn_ra_serf/list.c301
-rw-r--r--subversion/libsvn_ra_serf/lock.c28
-rw-r--r--subversion/libsvn_ra_serf/log.c10
-rw-r--r--subversion/libsvn_ra_serf/merge.c21
-rw-r--r--subversion/libsvn_ra_serf/mergeinfo.c4
-rw-r--r--subversion/libsvn_ra_serf/multistatus.c11
-rw-r--r--subversion/libsvn_ra_serf/options.c27
-rw-r--r--subversion/libsvn_ra_serf/property.c35
-rw-r--r--subversion/libsvn_ra_serf/ra_serf.h106
-rw-r--r--subversion/libsvn_ra_serf/replay.c71
-rw-r--r--subversion/libsvn_ra_serf/request_body.c232
-rw-r--r--subversion/libsvn_ra_serf/sb_bucket.c7
-rw-r--r--subversion/libsvn_ra_serf/serf.c86
-rw-r--r--subversion/libsvn_ra_serf/stat.c63
-rw-r--r--subversion/libsvn_ra_serf/stream_bucket.c120
-rw-r--r--subversion/libsvn_ra_serf/update.c217
-rw-r--r--subversion/libsvn_ra_serf/util.c165
-rw-r--r--subversion/libsvn_ra_serf/xml.c173
26 files changed, 1524 insertions, 668 deletions
diff --git a/subversion/libsvn_ra_serf/README b/subversion/libsvn_ra_serf/README
deleted file mode 100644
index 98a48a636af82..0000000000000
--- a/subversion/libsvn_ra_serf/README
+++ /dev/null
@@ -1,84 +0,0 @@
-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 interchangeable 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
index d915a19104cd4..3ea45ed20dfbe 100644
--- a/subversion/libsvn_ra_serf/blame.c
+++ b/subversion/libsvn_ra_serf/blame.c
@@ -81,6 +81,8 @@ typedef struct blame_context_t {
svn_stream_t *stream;
+ svn_ra_serf__session_t *session;
+
} blame_context_t;
@@ -318,6 +320,20 @@ create_file_revs_body(serf_bucket_t **body_bkt,
return SVN_NO_ERROR;
}
+/* Implements svn_ra_serf__request_header_delegate_t */
+static svn_error_t *
+setup_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
+{
+ blame_context_t *blame_ctx = baton;
+
+ svn_ra_serf__setup_svndiff_accept_encoding(headers, blame_ctx->session);
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
const char *path,
@@ -343,6 +359,7 @@ svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
blame_ctx->start = start;
blame_ctx->end = end;
blame_ctx->include_merged_revisions = include_merged_revisions;
+ blame_ctx->session = session;
/* Since Subversion 1.8 we allow retrieving blames backwards. So we can't
just unconditionally use end_rev as the peg revision as before */
@@ -369,6 +386,9 @@ svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session,
handler->body_type = "text/xml";
handler->body_delegate = create_file_revs_body;
handler->body_delegate_baton = blame_ctx;
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = setup_headers;
+ handler->header_delegate_baton = blame_ctx;
SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
diff --git a/subversion/libsvn_ra_serf/commit.c b/subversion/libsvn_ra_serf/commit.c
index b1e81c7870e3d..63a6f0bfa45b0 100644
--- a/subversion/libsvn_ra_serf/commit.c
+++ b/subversion/libsvn_ra_serf/commit.c
@@ -71,6 +71,7 @@ typedef struct commit_context_t {
const char *checked_in_url; /* checked-in root to base CHECKOUTs from */
const char *vcc_url; /* vcc url */
+ int open_batons; /* Number of open batons */
} commit_context_t;
#define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL)
@@ -117,9 +118,6 @@ typedef struct dir_context_t {
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;
@@ -172,11 +170,14 @@ typedef struct file_context_t {
const char *copy_path;
svn_revnum_t copy_revision;
- /* stream */
+ /* Stream for collecting the svndiff. */
svn_stream_t *stream;
- /* Temporary file containing the svndiff. */
- apr_file_t *svndiff;
+ /* Buffer holding the svndiff (can spill to disk). */
+ svn_ra_serf__request_body_t *svndiff;
+
+ /* Did we send the svndiff in apply_textdelta_stream()? */
+ svn_boolean_t svndiff_sent;
/* Our base checksum as reported by the WC. */
const char *base_checksum;
@@ -184,6 +185,9 @@ typedef struct file_context_t {
/* Our resulting checksum as reported by the WC. */
const char *result_checksum;
+ /* Our resulting checksum as reported by the server. */
+ svn_checksum_t *remote_result_checksum;
+
/* Changed properties (const char * -> svn_prop_t *) */
apr_hash_t *prop_changes;
@@ -683,7 +687,7 @@ maybe_set_lock_token_header(serf_bucket_t *headers,
{
const char *token;
- if (! (*relpath && commit_ctx->lock_tokens))
+ if (! commit_ctx->lock_tokens)
return SVN_NO_ERROR;
if (! svn_hash_gets(commit_ctx->deleted_entries, relpath))
@@ -871,35 +875,6 @@ proppatch_resource(svn_ra_serf__session_t *session,
/* 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 /* request pool */,
- apr_pool_t *scratch_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.
- */
- SVN_ERR(svn_io_file_flush(ctx->svndiff, pool));
- apr_file_buffer_set(ctx->svndiff, NULL, 0);
- offset = 0;
- SVN_ERR(svn_io_file_seek(ctx->svndiff, APR_SET, &offset, pool));
-
- *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,
@@ -1259,6 +1234,8 @@ open_root(void *edit_baton,
const char *proppatch_target = NULL;
apr_pool_t *scratch_pool = svn_pool_create(dir_pool);
+ commit_ctx->open_batons++;
+
if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(commit_ctx->session))
{
post_response_ctx_t *prc;
@@ -1545,6 +1522,8 @@ add_directory(const char *path,
dir->name = svn_relpath_basename(dir->relpath, NULL);
dir->prop_changes = apr_hash_make(dir->pool);
+ dir->commit_ctx->open_batons++;
+
if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
{
dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
@@ -1635,6 +1614,8 @@ open_directory(const char *path,
dir->name = svn_relpath_basename(dir->relpath, NULL);
dir->prop_changes = apr_hash_make(dir->pool);
+ dir->commit_ctx->open_batons++;
+
if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx))
{
dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
@@ -1713,6 +1694,8 @@ close_directory(void *dir_baton,
proppatch_ctx, dir->pool));
}
+ dir->commit_ctx->open_batons--;
+
return SVN_NO_ERROR;
}
@@ -1732,8 +1715,6 @@ add_file(const char *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_ctx = dir->commit_ctx;
new_file->relpath = apr_pstrdup(new_file->pool, path);
@@ -1744,6 +1725,8 @@ add_file(const char *path,
new_file->copy_revision = copy_revision;
new_file->prop_changes = apr_hash_make(new_file->pool);
+ dir->commit_ctx->open_batons++;
+
/* 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. */
@@ -1854,8 +1837,6 @@ open_file(const char *path,
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_ctx = parent->commit_ctx;
new_file->relpath = apr_pstrdup(new_file->pool, path);
@@ -1864,6 +1845,8 @@ open_file(const char *path,
new_file->base_revision = base_revision;
new_file->prop_changes = apr_hash_make(new_file->pool);
+ parent->commit_ctx->open_batons++;
+
if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx))
{
new_file->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url,
@@ -1882,21 +1865,73 @@ open_file(const char *path,
return SVN_NO_ERROR;
}
-/* Implements svn_stream_lazyopen_func_t for apply_textdelta */
-static svn_error_t *
-delayed_commit_stream_open(svn_stream_t **stream,
- void *baton,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+static void
+negotiate_put_encoding(int *svndiff_version_p,
+ int *svndiff_compression_level_p,
+ svn_ra_serf__session_t *session)
{
- file_context_t *file_ctx = baton;
-
- SVN_ERR(svn_io_open_unique_file3(&file_ctx->svndiff, NULL, NULL,
- svn_io_file_del_on_pool_cleanup,
- file_ctx->pool, scratch_pool));
+ int svndiff_version;
+ int compression_level;
+
+ if (session->using_compression == svn_tristate_unknown)
+ {
+ /* With http-compression=auto, prefer svndiff2 to svndiff1 with a
+ * low latency connection (assuming the underlying network has high
+ * bandwidth), as it is faster and in this case, we don't care about
+ * worse compression ratio.
+ *
+ * Note: For future compatibility, we also handle a theoretically
+ * possible case where the server has advertised only svndiff2 support.
+ */
+ if (session->supports_svndiff2 &&
+ svn_ra_serf__is_low_latency_connection(session))
+ svndiff_version = 2;
+ else if (session->supports_svndiff1)
+ svndiff_version = 1;
+ else if (session->supports_svndiff2)
+ svndiff_version = 2;
+ else
+ svndiff_version = 0;
+ }
+ else if (session->using_compression == svn_tristate_true)
+ {
+ /* Otherwise, prefer svndiff1, as svndiff2 is not a reasonable
+ * substitute for svndiff1 with default compression level. (It gives
+ * better speed and compression ratio comparable to svndiff1 with
+ * compression level 1, but not 5).
+ *
+ * Note: For future compatibility, we also handle a theoretically
+ * possible case where the server has advertised only svndiff2 support.
+ */
+ if (session->supports_svndiff1)
+ svndiff_version = 1;
+ else if (session->supports_svndiff2)
+ svndiff_version = 2;
+ else
+ svndiff_version = 0;
+ }
+ else
+ {
+ /* Difference between svndiff formats 0 and 1/2 that format 1/2 allows
+ * compression. Uncompressed svndiff0 should also be slightly more
+ * effective if the compression is not required at all.
+ *
+ * If the server cannot handle svndiff1/2, or compression is disabled
+ * with the 'http-compression = no' client configuration option, fall
+ * back to uncompressed svndiff0 format. As a bonus, users can force
+ * the usage of the uncompressed format by setting the corresponding
+ * client configuration option, if they want to.
+ */
+ svndiff_version = 0;
+ }
+
+ if (svndiff_version == 0)
+ compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
+ else
+ compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
- *stream = svn_stream_from_aprfile2(file_ctx->svndiff, TRUE, result_pool);
- return SVN_NO_ERROR;
+ *svndiff_version_p = svndiff_version;
+ *svndiff_compression_level_p = compression_level;
}
static svn_error_t *
@@ -1907,21 +1942,31 @@ apply_textdelta(void *file_baton,
void **handler_baton)
{
file_context_t *ctx = file_baton;
+ int svndiff_version;
+ int compression_level;
- /* Store the stream in a temporary file; we'll give it to serf when we
+ /* Construct a holder for the request body; 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.
+ * Please note that if this callback is used, large request bodies will
+ * be spilled into temporary files (that requires disk space and prevents
+ * simultaneous processing by the server and the client). A better approach
+ * that streams the request body is implemented in apply_textdelta_stream().
+ * It will be used with most recent servers having the "send result checksum
+ * in response to a PUT" capability, and only if the editor driver uses the
+ * new callback.
*/
-
- ctx->stream = svn_stream_lazyopen_create(delayed_commit_stream_open,
- ctx, FALSE, ctx->pool);
-
- svn_txdelta_to_svndiff3(handler, handler_baton, ctx->stream, 0,
- SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+ ctx->svndiff =
+ svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
+ ctx->pool);
+ ctx->stream = svn_ra_serf__request_body_get_stream(ctx->svndiff);
+
+ negotiate_put_encoding(&svndiff_version, &compression_level,
+ ctx->commit_ctx->session);
+ /* Disown the stream; we'll close it explicitly in close_file(). */
+ svn_txdelta_to_svndiff3(handler, handler_baton,
+ svn_stream_disown(ctx->stream, pool),
+ svndiff_version, compression_level, pool);
if (base_checksum)
ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum);
@@ -1929,6 +1974,146 @@ apply_textdelta(void *file_baton,
return SVN_NO_ERROR;
}
+typedef struct open_txdelta_baton_t
+{
+ svn_ra_serf__session_t *session;
+ svn_txdelta_stream_open_func_t open_func;
+ void *open_baton;
+ svn_error_t *err;
+} open_txdelta_baton_t;
+
+static void
+txdelta_stream_errfunc(void *baton, svn_error_t *err)
+{
+ open_txdelta_baton_t *b = baton;
+
+ /* Remember extended error info from the stream bucket. Note that
+ * theoretically this errfunc could be called multiple times -- say,
+ * if the request gets restarted after an error. Compose the errors
+ * so we don't leak one of them if this happens. */
+ b->err = svn_error_compose_create(b->err, svn_error_dup(err));
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_body_from_txdelta_stream(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool /* request pool */,
+ apr_pool_t *scratch_pool)
+{
+ open_txdelta_baton_t *b = baton;
+ svn_txdelta_stream_t *txdelta_stream;
+ svn_stream_t *stream;
+ int svndiff_version;
+ int compression_level;
+
+ SVN_ERR(b->open_func(&txdelta_stream, b->open_baton, pool, scratch_pool));
+
+ negotiate_put_encoding(&svndiff_version, &compression_level, b->session);
+ stream = svn_txdelta_to_svndiff_stream(txdelta_stream, svndiff_version,
+ compression_level, pool);
+ *body_bkt = svn_ra_serf__create_stream_bucket(stream, alloc,
+ txdelta_stream_errfunc, b);
+
+ return SVN_NO_ERROR;
+}
+
+/* Handler baton for PUT request. */
+typedef struct put_response_ctx_t
+{
+ svn_ra_serf__handler_t *handler;
+ file_context_t *file_ctx;
+} put_response_ctx_t;
+
+/* Implements svn_ra_serf__response_handler_t */
+static svn_error_t *
+put_response_handler(serf_request_t *request,
+ serf_bucket_t *response,
+ void *baton,
+ apr_pool_t *scratch_pool)
+{
+ put_response_ctx_t *prc = baton;
+ serf_bucket_t *hdrs;
+ const char *val;
+
+ hdrs = serf_bucket_response_get_headers(response);
+ val = serf_bucket_headers_get(hdrs, SVN_DAV_RESULT_FULLTEXT_MD5_HEADER);
+ SVN_ERR(svn_checksum_parse_hex(&prc->file_ctx->remote_result_checksum,
+ svn_checksum_md5, val, prc->file_ctx->pool));
+
+ return svn_error_trace(
+ svn_ra_serf__expect_empty_body(request, response,
+ prc->handler, scratch_pool));
+}
+
+static svn_error_t *
+apply_textdelta_stream(const svn_delta_editor_t *editor,
+ void *file_baton,
+ const char *base_checksum,
+ svn_txdelta_stream_open_func_t open_func,
+ void *open_baton,
+ apr_pool_t *scratch_pool)
+{
+ file_context_t *ctx = file_baton;
+ open_txdelta_baton_t open_txdelta_baton = {0};
+ svn_ra_serf__handler_t *handler;
+ put_response_ctx_t *prc;
+ int expected_result;
+ svn_error_t *err;
+
+ /* Remember that we have sent the svndiff. A case when we need to
+ * perform a zero-byte file PUT (during add_file, close_file editor
+ * sequences) is handled in close_file().
+ */
+ ctx->svndiff_sent = TRUE;
+ ctx->base_checksum = base_checksum;
+
+ handler = svn_ra_serf__create_handler(ctx->commit_ctx->session,
+ scratch_pool);
+ handler->method = "PUT";
+ handler->path = ctx->url;
+
+ prc = apr_pcalloc(scratch_pool, sizeof(*prc));
+ prc->handler = handler;
+ prc->file_ctx = ctx;
+
+ handler->response_handler = put_response_handler;
+ handler->response_baton = prc;
+
+ open_txdelta_baton.session = ctx->commit_ctx->session;
+ open_txdelta_baton.open_func = open_func;
+ open_txdelta_baton.open_baton = open_baton;
+ open_txdelta_baton.err = SVN_NO_ERROR;
+
+ handler->body_delegate = create_body_from_txdelta_stream;
+ handler->body_delegate_baton = &open_txdelta_baton;
+ handler->body_type = SVN_SVNDIFF_MIME_TYPE;
+
+ handler->header_delegate = setup_put_headers;
+ handler->header_delegate_baton = ctx;
+
+ err = svn_ra_serf__context_run_one(handler, scratch_pool);
+ /* Do we have an error from the stream bucket? If yes, use it. */
+ if (open_txdelta_baton.err)
+ {
+ svn_error_clear(err);
+ return svn_error_trace(open_txdelta_baton.err);
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ if (ctx->added && !ctx->copy_path)
+ expected_result = 201; /* Created */
+ else
+ expected_result = 204; /* Updated */
+
+ if (handler->sline.code != expected_result)
+ return svn_error_trace(svn_ra_serf__unexpected_status(handler));
+
+ return SVN_NO_ERROR;
+}
+
static svn_error_t *
change_file_prop(void *file_baton,
const char *name,
@@ -1964,8 +2149,8 @@ close_file(void *file_baton,
if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path))
put_empty_file = TRUE;
- /* If we had a stream of changes, push them to the server... */
- if (ctx->svndiff || put_empty_file)
+ /* If we have a stream of changes, push them to the server... */
+ if ((ctx->svndiff || put_empty_file) && !ctx->svndiff_sent)
{
svn_ra_serf__handler_t *handler;
int expected_result;
@@ -1987,8 +2172,11 @@ close_file(void *file_baton,
}
else
{
- handler->body_delegate = create_put_body;
- handler->body_delegate_baton = ctx;
+ SVN_ERR(svn_stream_close(ctx->stream));
+
+ svn_ra_serf__request_body_get_delegate(&handler->body_delegate,
+ &handler->body_delegate_baton,
+ ctx->svndiff);
handler->body_type = SVN_SVNDIFF_MIME_TYPE;
}
@@ -2006,8 +2194,9 @@ close_file(void *file_baton,
return svn_error_trace(svn_ra_serf__unexpected_status(handler));
}
+ /* Don't keep open file handles longer than necessary. */
if (ctx->svndiff)
- SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool));
+ SVN_ERR(svn_ra_serf__request_body_cleanup(ctx->svndiff, scratch_pool));
/* If we had any prop changes, push them via PROPPATCH. */
if (apr_hash_count(ctx->prop_changes))
@@ -2026,6 +2215,24 @@ close_file(void *file_baton,
proppatch, scratch_pool));
}
+ if (ctx->result_checksum && ctx->remote_result_checksum)
+ {
+ svn_checksum_t *result_checksum;
+
+ SVN_ERR(svn_checksum_parse_hex(&result_checksum, svn_checksum_md5,
+ ctx->result_checksum, scratch_pool));
+
+ if (!svn_checksum_match(result_checksum, ctx->remote_result_checksum))
+ return svn_checksum_mismatch_err(result_checksum,
+ ctx->remote_result_checksum,
+ scratch_pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(ctx->relpath,
+ scratch_pool));
+ }
+
+ ctx->commit_ctx->open_batons--;
+
return SVN_NO_ERROR;
}
@@ -2039,6 +2246,11 @@ close_edit(void *edit_baton,
const svn_commit_info_t *commit_info;
svn_error_t *err = NULL;
+ if (ctx->open_batons > 0)
+ return svn_error_create(
+ SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION, NULL,
+ _("Closing editor with directories or files open"));
+
/* MERGE our activity */
SVN_ERR(svn_ra_serf__run_merge(&commit_info,
ctx->session,
@@ -2194,6 +2406,12 @@ svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session,
editor->close_file = close_file;
editor->close_edit = close_edit;
editor->abort_edit = abort_edit;
+ /* Only install the callback that allows streaming PUT request bodies
+ * if the server has the necessary capability. Otherwise, this will
+ * fallback to the default implementation using the temporary files.
+ * See default_editor.c:apply_textdelta_stream(). */
+ if (session->supports_put_result_checksum)
+ editor->apply_textdelta_stream = apply_textdelta_stream;
*ret_editor = editor;
*edit_baton = ctx;
diff --git a/subversion/libsvn_ra_serf/eagain_bucket.c b/subversion/libsvn_ra_serf/eagain_bucket.c
index 16387be91b9d4..da9b127d3a3d9 100644
--- a/subversion/libsvn_ra_serf/eagain_bucket.c
+++ b/subversion/libsvn_ra_serf/eagain_bucket.c
@@ -66,7 +66,7 @@ eagain_bucket_read(serf_bucket_t *bucket,
return APR_EAGAIN;
}
-
+#if !SERF_VERSION_AT_LEAST(1, 4, 0)
static apr_status_t
eagain_bucket_readline(serf_bucket_t *bucket,
int acceptable,
@@ -79,6 +79,7 @@ eagain_bucket_readline(serf_bucket_t *bucket,
"Not implemented."));
return APR_ENOTIMPL;
}
+#endif
static apr_status_t
@@ -98,7 +99,11 @@ eagain_bucket_peek(serf_bucket_t *bucket,
static const serf_bucket_type_t delay_bucket_vtable = {
"BUF-EAGAIN",
eagain_bucket_read,
+#if SERF_VERSION_AT_LEAST(1, 4, 0)
+ serf_default_readline,
+#else
eagain_bucket_readline,
+#endif
serf_default_read_iovec,
serf_default_read_for_sendfile,
serf_default_read_bucket,
diff --git a/subversion/libsvn_ra_serf/get_file.c b/subversion/libsvn_ra_serf/get_file.c
index cb63b7dd438b3..aee89453dce63 100644
--- a/subversion/libsvn_ra_serf/get_file.c
+++ b/subversion/libsvn_ra_serf/get_file.c
@@ -60,7 +60,7 @@ typedef struct stream_ctx_t {
/* Have we read our response headers yet? */
svn_boolean_t read_headers;
- svn_boolean_t using_compression;
+ svn_ra_serf__session_t *session;
/* This flag is set when our response is aborted before we reach the
* end and we decide to requeue this request.
@@ -88,7 +88,7 @@ headers_fetch(serf_bucket_t *headers,
{
stream_ctx_t *fetch_ctx = baton;
- if (fetch_ctx->using_compression)
+ if (fetch_ctx->session->using_compression != svn_tristate_false)
{
serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
}
@@ -321,17 +321,19 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
svn_stream_t *stream,
svn_revnum_t *fetched_rev,
apr_hash_t **props,
- apr_pool_t *pool)
+ apr_pool_t *result_pool)
{
svn_ra_serf__session_t *session = ra_session->priv;
const char *fetch_url;
const svn_ra_serf__dav_props_t *which_props;
svn_ra_serf__handler_t *propfind_handler;
+ apr_pool_t *scratch_pool = svn_pool_create(result_pool);
struct file_prop_baton_t fb;
/* Fetch properties. */
- fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
+ fetch_url = svn_path_url_add_component2(session->session_url.path, path,
+ scratch_pool);
/* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
*
@@ -343,7 +345,7 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
session,
fetch_url, revision,
- pool, pool));
+ scratch_pool, scratch_pool));
revision = SVN_INVALID_REVNUM;
}
/* REVISION is always SVN_INVALID_REVNUM */
@@ -356,8 +358,8 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
else
which_props = check_path_props;
- fb.result_pool = pool;
- fb.props = props ? apr_hash_make(pool) : NULL;
+ fb.result_pool = result_pool;
+ fb.props = props ? apr_hash_make(result_pool) : NULL;
fb.kind = svn_node_unknown;
fb.sha1_checksum = NULL;
@@ -365,9 +367,9 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
fetch_url, SVN_INVALID_REVNUM,
"0", which_props,
get_file_prop_cb, &fb,
- pool));
+ scratch_pool));
- SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, pool));
+ SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, scratch_pool));
/* Verify that resource type is not collection. */
if (fb.kind != svn_node_file)
@@ -382,7 +384,8 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
if (stream)
{
svn_boolean_t found;
- SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream, pool));
+ SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream,
+ scratch_pool));
/* No contents found in the WC, let's fetch from server. */
if (!found)
@@ -391,11 +394,11 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
svn_ra_serf__handler_t *handler;
/* Create the fetch context. */
- stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
+ stream_ctx = apr_pcalloc(scratch_pool, sizeof(*stream_ctx));
stream_ctx->result_stream = stream;
- stream_ctx->using_compression = session->using_compression;
+ stream_ctx->session = session;
- handler = svn_ra_serf__create_handler(session, pool);
+ handler = svn_ra_serf__create_handler(session, scratch_pool);
handler->method = "GET";
handler->path = fetch_url;
@@ -414,12 +417,14 @@ svn_ra_serf__get_file(svn_ra_session_t *ra_session,
stream_ctx->handler = handler;
- SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
if (handler->sline.code != 200)
return svn_error_trace(svn_ra_serf__unexpected_status(handler));
}
}
+ svn_pool_destroy(scratch_pool);
+
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_ra_serf/getlocations.c b/subversion/libsvn_ra_serf/getlocations.c
index 063272ec3c68b..ea25c598c3851 100644
--- a/subversion/libsvn_ra_serf/getlocations.c
+++ b/subversion/libsvn_ra_serf/getlocations.c
@@ -193,9 +193,8 @@ svn_ra_serf__get_locations(svn_ra_session_t *ra_session,
SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
- SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
- handler->path,
- handler->location));
+ if (handler->sline.code != 200)
+ SVN_ERR(svn_ra_serf__unexpected_status(handler));
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_ra_serf/getlocationsegments.c b/subversion/libsvn_ra_serf/getlocationsegments.c
index 884ab5daa5b40..cf1f23899291e 100644
--- a/subversion/libsvn_ra_serf/getlocationsegments.c
+++ b/subversion/libsvn_ra_serf/getlocationsegments.c
@@ -196,12 +196,8 @@ svn_ra_serf__get_location_segments(svn_ra_session_t *ra_session,
err = svn_ra_serf__context_run_one(handler, pool);
- if (!err)
- {
- err = svn_ra_serf__error_on_status(handler->sline,
- handler->path,
- handler->location);
- }
+ if (!err && handler->sline.code != 200)
+ err = svn_ra_serf__unexpected_status(handler);
if (err && (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))
return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, NULL);
diff --git a/subversion/libsvn_ra_serf/libsvn_ra_serf.pc.in b/subversion/libsvn_ra_serf/libsvn_ra_serf.pc.in
index e02ef12afb203..81285ed468d36 100644
--- a/subversion/libsvn_ra_serf/libsvn_ra_serf.pc.in
+++ b/subversion/libsvn_ra_serf/libsvn_ra_serf.pc.in
@@ -8,5 +8,5 @@ Description: Subversion HTTP/WebDAV Protocol Repository Access Library
Version: @PACKAGE_VERSION@
Requires: apr-util-@SVN_APR_MAJOR_VERSION@ apr-@SVN_APR_MAJOR_VERSION@
Requires.private: libsvn_delta libsvn_subr serf-1
-Libs: -L${libdir} -lsvn_ra_serf @SVN_XML_LIBS@ @SVN_ZLIB_LIBS@
+Libs: -L${libdir} -lsvn_ra_serf @SVN_ZLIB_LIBS@
Cflags: -I${includedir}
diff --git a/subversion/libsvn_ra_serf/list.c b/subversion/libsvn_ra_serf/list.c
new file mode 100644
index 0000000000000..722a946d36455
--- /dev/null
+++ b/subversion/libsvn_ra_serf/list.c
@@ -0,0 +1,301 @@
+/*
+ * list.c : entry point for the list RA function in ra_serf
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+
+#include <apr_uri.h>
+#include <serf.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_ra.h"
+#include "svn_dav.h"
+#include "svn_base64.h"
+#include "svn_xml.h"
+#include "svn_config.h"
+#include "svn_path.h"
+#include "svn_props.h"
+#include "svn_time.h"
+
+#include "private/svn_dav_protocol.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+#include "svn_private_config.h"
+
+#include "ra_serf.h"
+#include "../libsvn_ra/ra_loader.h"
+
+
+
+/*
+ * This enum represents the current state of our XML parsing for a REPORT.
+ */
+enum list_state_e {
+ INITIAL = XML_STATE_INITIAL,
+ REPORT,
+ ITEM,
+ AUTHOR
+};
+
+typedef struct list_context_t {
+ apr_pool_t *pool;
+
+ /* parameters set by our caller */
+ const char *path;
+ svn_revnum_t revision;
+ const apr_array_header_t *patterns;
+ svn_depth_t depth;
+ apr_uint32_t dirent_fields;
+ apr_array_header_t *props;
+
+ /* Buffer the author info for the current item.
+ * We use the AUTHOR pointer to differentiate between 0-length author
+ * strings and missing / NULL authors. */
+ const char *author;
+ svn_stringbuf_t *author_buf;
+
+ /* log receiver function and baton */
+ svn_ra_dirent_receiver_t receiver;
+ void *receiver_baton;
+} list_context_t;
+
+#define D_ "DAV:"
+#define S_ SVN_XML_NAMESPACE
+static const svn_ra_serf__xml_transition_t log_ttable[] = {
+ { INITIAL, S_, "list-report", REPORT,
+ FALSE, { NULL }, FALSE },
+
+ { REPORT, S_, "item", ITEM,
+ TRUE, { "node-kind", "?size", "?has-props", "?created-rev",
+ "?date", NULL }, TRUE },
+
+ { ITEM, D_, "creator-displayname", AUTHOR,
+ TRUE, { "?encoding", NULL }, TRUE },
+
+ { 0 }
+};
+
+/* Conforms to svn_ra_serf__xml_closed_t */
+static svn_error_t *
+item_closed(svn_ra_serf__xml_estate_t *xes,
+ void *baton,
+ int leaving_state,
+ const svn_string_t *cdata,
+ apr_hash_t *attrs,
+ apr_pool_t *scratch_pool)
+{
+ list_context_t *list_ctx = baton;
+
+ if (leaving_state == AUTHOR)
+ {
+ /* For compatibility with liveprops, current servers will not use
+ * base64-encoding for "binary" user names bu simply drop the
+ * offending control chars.
+ *
+ * We might want to switch to revprop-style encoding, though,
+ * and this is the code to do that. */
+ const char *encoding = svn_hash_gets(attrs, "encoding");
+ if (encoding)
+ {
+ /* Check for a known encoding type. This is easy -- there's
+ only one. */
+ if (strcmp(encoding, "base64") != 0)
+ {
+ return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
+ _("Unsupported encoding '%s'"),
+ encoding);
+ }
+
+ cdata = svn_base64_decode_string(cdata, scratch_pool);
+ }
+
+ /* Remember until the next ITEM closing tag. */
+ svn_stringbuf_set(list_ctx->author_buf, cdata->data);
+ list_ctx->author = list_ctx->author_buf->data;
+ }
+ else if (leaving_state == ITEM)
+ {
+ const char *dirent_path = cdata->data;
+ const char *kind_word, *date, *crev, *size;
+ svn_dirent_t dirent = { 0 };
+
+ kind_word = svn_hash_gets(attrs, "node-kind");
+ size = svn_hash_gets(attrs, "size");
+
+ dirent.has_props = svn_hash__get_bool(attrs, "has-props", FALSE);
+ crev = svn_hash_gets(attrs, "created-rev");
+ date = svn_hash_gets(attrs, "date");
+
+ /* Convert data. */
+ dirent.kind = svn_node_kind_from_word(kind_word);
+
+ if (size)
+ SVN_ERR(svn_cstring_atoi64(&dirent.size, size));
+ else
+ dirent.size = SVN_INVALID_FILESIZE;
+
+ if (crev)
+ SVN_ERR(svn_revnum_parse(&dirent.created_rev, crev, NULL));
+ else
+ dirent.created_rev = SVN_INVALID_REVNUM;
+
+ if (date)
+ SVN_ERR(svn_time_from_cstring(&dirent.time, date, scratch_pool));
+
+ if (list_ctx->author)
+ dirent.last_author = list_ctx->author;
+
+ /* Invoke RECEIVER */
+ SVN_ERR(list_ctx->receiver(dirent_path, &dirent,
+ list_ctx->receiver_baton, scratch_pool));
+
+ /* Reset buffered info. */
+ list_ctx->author = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t */
+static svn_error_t *
+create_list_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *pool /* request pool */,
+ apr_pool_t *scratch_pool)
+{
+ serf_bucket_t *buckets;
+ list_context_t *list_ctx = baton;
+ int i;
+
+ buckets = serf_bucket_aggregate_create(alloc);
+
+ svn_ra_serf__add_open_tag_buckets(buckets, alloc,
+ "S:list-report",
+ "xmlns:S", SVN_XML_NAMESPACE,
+ SVN_VA_NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:path", list_ctx->path,
+ alloc);
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:revision",
+ apr_ltoa(pool, list_ctx->revision),
+ alloc);
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:depth", svn_depth_to_word(list_ctx->depth),
+ alloc);
+
+ if (list_ctx->patterns)
+ {
+ for (i = 0; i < list_ctx->patterns->nelts; i++)
+ {
+ char *name = APR_ARRAY_IDX(list_ctx->patterns, i, char *);
+ svn_ra_serf__add_tag_buckets(buckets,
+ "S:pattern", name,
+ alloc);
+ }
+ if (list_ctx->patterns->nelts == 0)
+ {
+ svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
+ "S:no-patterns", SVN_VA_NULL);
+ }
+ }
+
+ for (i = 0; i < list_ctx->props->nelts; i++)
+ {
+ const svn_ra_serf__dav_props_t *prop
+ = &APR_ARRAY_IDX(list_ctx->props, i, const svn_ra_serf__dav_props_t);
+ const char *name
+ = apr_pstrcat(pool, prop->xmlns, prop->name, SVN_VA_NULL);
+
+ svn_ra_serf__add_tag_buckets(buckets, "S:prop", name, alloc);
+ }
+
+ svn_ra_serf__add_close_tag_buckets(buckets, alloc,
+ "S:list-report");
+
+ *body_bkt = buckets;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_ra_serf__list(svn_ra_session_t *ra_session,
+ const char *path,
+ svn_revnum_t revision,
+ const apr_array_header_t *patterns,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_ra_dirent_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *scratch_pool)
+{
+ list_context_t *list_ctx;
+ svn_ra_serf__session_t *session = ra_session->priv;
+ svn_ra_serf__handler_t *handler;
+ svn_ra_serf__xml_context_t *xmlctx;
+ const char *req_url;
+
+ list_ctx = apr_pcalloc(scratch_pool, sizeof(*list_ctx));
+ list_ctx->pool = scratch_pool;
+ list_ctx->receiver = receiver;
+ list_ctx->receiver_baton = receiver_baton;
+ list_ctx->path = path;
+ list_ctx->revision = revision;
+ list_ctx->patterns = patterns;
+ list_ctx->depth = depth;
+ list_ctx->dirent_fields = dirent_fields;
+ list_ctx->props = svn_ra_serf__get_dirent_props(dirent_fields, session,
+ scratch_pool);
+ list_ctx->author_buf = svn_stringbuf_create_empty(scratch_pool);
+
+ /* 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.
+ */
+ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */,
+ session,
+ NULL /* url */, revision,
+ scratch_pool, scratch_pool));
+
+ xmlctx = svn_ra_serf__xml_context_create(log_ttable,
+ NULL, item_closed, NULL,
+ list_ctx,
+ scratch_pool);
+ handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL,
+ scratch_pool);
+
+ handler->method = "REPORT";
+ handler->path = req_url;
+ handler->body_delegate = create_list_body;
+ handler->body_delegate_baton = list_ctx;
+ handler->body_type = "text/xml";
+
+ SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
+
+ if (handler->sline.code != 200)
+ SVN_ERR(svn_ra_serf__unexpected_status(handler));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/lock.c b/subversion/libsvn_ra_serf/lock.c
index dd045e3a17513..d303104b3684b 100644
--- a/subversion/libsvn_ra_serf/lock.c
+++ b/subversion/libsvn_ra_serf/lock.c
@@ -267,10 +267,9 @@ run_locks(svn_ra_serf__session_t *sess,
/* ### Authz can also lead to 403. */
err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH,
NULL,
- _("Unlock of '%s' failed (%d %s)"),
- ctx->path,
- ctx->handler->sline.code,
- ctx->handler->sline.reason);
+ _("Not authorized to perform lock "
+ "operation on '%s'"),
+ ctx->path);
break;
case 405:
err = svn_error_createf(SVN_ERR_FS_OUT_OF_DATE,
@@ -282,13 +281,20 @@ run_locks(svn_ra_serf__session_t *sess,
ctx->handler->sline.reason);
break;
case 423:
- err = svn_error_createf(SVN_ERR_FS_PATH_ALREADY_LOCKED,
- NULL,
- _("Path '%s' already locked "
- "(%d %s)"),
- ctx->path,
- ctx->handler->sline.code,
- ctx->handler->sline.reason);
+ if (server_err
+ && SVN_ERROR_IN_CATEGORY(server_err->apr_err,
+ SVN_ERR_FS_CATEGORY_START))
+ {
+ err = NULL;
+ }
+ else
+ err = svn_error_createf(SVN_ERR_FS_PATH_ALREADY_LOCKED,
+ NULL,
+ _("Path '%s' already locked "
+ "(%d %s)"),
+ ctx->path,
+ ctx->handler->sline.code,
+ ctx->handler->sline.reason);
break;
case 404:
diff --git a/subversion/libsvn_ra_serf/log.c b/subversion/libsvn_ra_serf/log.c
index 773ae5c9c1621..fb9494aee10dc 100644
--- a/subversion/libsvn_ra_serf/log.c
+++ b/subversion/libsvn_ra_serf/log.c
@@ -272,7 +272,7 @@ log_closed(svn_ra_serf__xml_estate_t *xes,
svn_log_entry_t *log_entry;
const char *rev_str;
- if (log_ctx->limit && (log_ctx->nest_level == 0)
+ if ((log_ctx->limit > 0) && (log_ctx->nest_level == 0)
&& (++log_ctx->count > log_ctx->limit))
{
return SVN_NO_ERROR;
@@ -598,8 +598,8 @@ svn_ra_serf__get_log(svn_ra_session_t *ra_session,
SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
- return svn_error_trace(
- svn_ra_serf__error_on_status(handler->sline,
- req_url,
- handler->location));
+ if (handler->sline.code != 200)
+ SVN_ERR(svn_ra_serf__unexpected_status(handler));
+
+ return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_ra_serf/merge.c b/subversion/libsvn_ra_serf/merge.c
index 0a2fd5465b590..227670b9a2297 100644
--- a/subversion/libsvn_ra_serf/merge.c
+++ b/subversion/libsvn_ra_serf/merge.c
@@ -79,6 +79,7 @@ typedef struct merge_context_t
apr_hash_t *lock_tokens;
svn_boolean_t keep_locks;
+ svn_boolean_t disable_merge_response;
const char *merge_resource_url; /* URL of resource to be merged. */
const char *merge_url; /* URL at which the MERGE request is aimed. */
@@ -275,12 +276,17 @@ setup_merge_headers(serf_bucket_t *headers,
apr_pool_t *scratch_pool)
{
merge_context_t *ctx = baton;
+ apr_array_header_t *vals = apr_array_make(scratch_pool, 2,
+ sizeof(const char *));
if (!ctx->keep_locks)
- {
- serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
- SVN_DAV_OPTION_RELEASE_LOCKS);
- }
+ APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_RELEASE_LOCKS;
+ if (ctx->disable_merge_response)
+ APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_NO_MERGE_RESPONSE;
+
+ if (vals->nelts > 0)
+ serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
+ svn_cstring_join2(vals, " ", FALSE, scratch_pool));
return SVN_NO_ERROR;
}
@@ -412,6 +418,13 @@ svn_ra_serf__run_merge(const svn_commit_info_t **commit_info,
merge_ctx->lock_tokens = lock_tokens;
merge_ctx->keep_locks = keep_locks;
+ /* We don't need the full merge response when working over HTTPv2.
+ * Over HTTPv1, this response is only required with a non-null
+ * svn_ra_push_wc_prop_func_t callback. */
+ merge_ctx->disable_merge_response =
+ SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session) ||
+ session->wc_callbacks->push_wc_prop == NULL;
+
merge_ctx->commit_info = svn_create_commit_info(result_pool);
merge_ctx->merge_url = session->session_url.path;
diff --git a/subversion/libsvn_ra_serf/mergeinfo.c b/subversion/libsvn_ra_serf/mergeinfo.c
index 589ff0768ca72..38f165bc30108 100644
--- a/subversion/libsvn_ra_serf/mergeinfo.c
+++ b/subversion/libsvn_ra_serf/mergeinfo.c
@@ -228,8 +228,8 @@ svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session,
SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
- SVN_ERR(svn_ra_serf__error_on_status(handler->sline, handler->path,
- handler->location));
+ if (handler->sline.code != 200)
+ SVN_ERR(svn_ra_serf__unexpected_status(handler));
if (apr_hash_count(mergeinfo_ctx->result_catalog))
*catalog = mergeinfo_ctx->result_catalog;
diff --git a/subversion/libsvn_ra_serf/multistatus.c b/subversion/libsvn_ra_serf/multistatus.c
index 9c269c39beddf..00642401e893a 100644
--- a/subversion/libsvn_ra_serf/multistatus.c
+++ b/subversion/libsvn_ra_serf/multistatus.c
@@ -707,7 +707,16 @@ svn_ra_serf__handle_server_error(svn_ra_serf__server_error_t *server_error,
clear the error and return - allowing serf to wait for more data.
*/
if (!err || SERF_BUCKET_READ_ERROR(err->apr_err))
- return svn_error_trace(err);
+ {
+ /* Perhaps we already parsed some server generated message. Let's pass
+ all information we can get.*/
+ if (err)
+ err = svn_error_compose_create(
+ svn_ra_serf__server_error_create(handler, scratch_pool),
+ err);
+
+ return svn_error_trace(err);
+ }
if (!APR_STATUS_IS_EOF(err->apr_err))
{
diff --git a/subversion/libsvn_ra_serf/options.c b/subversion/libsvn_ra_serf/options.c
index a52977bbf1f89..9cb507c8f4702 100644
--- a/subversion/libsvn_ra_serf/options.c
+++ b/subversion/libsvn_ra_serf/options.c
@@ -226,6 +226,26 @@ capabilities_headers_iterator_callback(void *baton,
{
session->supports_rev_rsrc_replay = TRUE;
}
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF1, vals))
+ {
+ /* Use compressed svndiff1 format for servers that properly
+ advertise this capability (Subversion 1.10 and greater). */
+ session->supports_svndiff1 = TRUE;
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LIST, vals))
+ {
+ svn_hash_sets(session->capabilities,
+ SVN_RA_CAPABILITY_LIST, capability_yes);
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF2, vals))
+ {
+ /* Same for svndiff2. */
+ session->supports_svndiff2 = TRUE;
+ }
+ if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PUT_RESULT_CHECKSUM, vals))
+ {
+ session->supports_put_result_checksum = TRUE;
+ }
}
/* SVN-specific headers -- if present, server supports HTTP protocol v2 */
@@ -350,6 +370,7 @@ options_response_handler(serf_request_t *request,
{
svn_ra_serf__session_t *session = opt_ctx->session;
serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
+ serf_connection_t *conn;
/* Start out assuming all capabilities are unsupported. */
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
@@ -368,6 +389,8 @@ options_response_handler(serf_request_t *request,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
capability_no);
+ svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LIST,
+ capability_no);
/* Then see which ones we can discover. */
serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
@@ -379,6 +402,10 @@ options_response_handler(serf_request_t *request,
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
capability_no);
+ /* Remember our latency. */
+ conn = serf_request_get_conn(request);
+ session->conn_latency = serf_connection_get_latency(conn);
+
opt_ctx->headers_processed = TRUE;
}
diff --git a/subversion/libsvn_ra_serf/property.c b/subversion/libsvn_ra_serf/property.c
index b7e03183c4c79..a3dcb627d25df 100644
--- a/subversion/libsvn_ra_serf/property.c
+++ b/subversion/libsvn_ra_serf/property.c
@@ -390,6 +390,26 @@ create_propfind_body(serf_bucket_t **bkt,
requested_allprop = TRUE;
}
+ prop++;
+ }
+
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
+ sizeof(PROPFIND_HEADER)-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+
+ /* If we're not doing an allprop, add <prop> tags. */
+ if (!requested_allprop)
+ {
+ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
+ sizeof("<prop>")-1,
+ alloc);
+ serf_bucket_aggregate_append(body_bkt, tmp);
+ }
+
+ prop = ctx->find_props;
+ while (prop && prop->xmlns)
+ {
/* <*propname* xmlns="*propns*" /> */
tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
serf_bucket_aggregate_append(body_bkt, tmp);
@@ -412,21 +432,6 @@ create_propfind_body(serf_bucket_t **bkt,
prop++;
}
- /* If we're not doing an allprop, add <prop> tags. */
- if (!requested_allprop)
- {
- tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
- sizeof("<prop>")-1,
- alloc);
- serf_bucket_aggregate_prepend(body_bkt, tmp);
- }
-
- tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
- sizeof(PROPFIND_HEADER)-1,
- alloc);
-
- serf_bucket_aggregate_prepend(body_bkt, tmp);
-
if (!requested_allprop)
{
tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
diff --git a/subversion/libsvn_ra_serf/ra_serf.h b/subversion/libsvn_ra_serf/ra_serf.h
index fcef73777a03d..9191d073bbc4d 100644
--- a/subversion/libsvn_ra_serf/ra_serf.h
+++ b/subversion/libsvn_ra_serf/ra_serf.h
@@ -26,7 +26,6 @@
#include <serf.h>
-#include <expat.h> /* for XML_Parser */
#include <apr_uri.h>
#include "svn_types.h"
@@ -114,8 +113,12 @@ struct svn_ra_serf__session_t {
/* Are we using ssl */
svn_boolean_t using_ssl;
- /* Should we ask for compressed responses? */
- svn_boolean_t using_compression;
+ /* Tristate flag that indicates if we should use compression for
+ network transmissions. If svn_tristate_true or svn_tristate_false,
+ the compression should be enabled and disabled, respectively.
+ If svn_tristate_unknown, determine this automatically based
+ on network parameters. */
+ svn_tristate_t using_compression;
/* The user agent string */
const char *useragent;
@@ -137,6 +140,10 @@ struct svn_ra_serf__session_t {
HTTP/1.0. Thus, we cannot send chunked requests. */
svn_boolean_t http10;
+ /* We are talking to the server via http/2. Responses of scheduled
+ requests may come in any order */
+ svn_boolean_t http20;
+
/* Should we use Transfer-Encoding: chunked for HTTP/1.1 servers. */
svn_boolean_t using_chunked_requests;
@@ -255,6 +262,18 @@ struct svn_ra_serf__session_t {
/* Indicates whether the server supports issuing replay REPORTs
against rev resources (children of `rev_stub', elsestruct). */
svn_boolean_t supports_rev_rsrc_replay;
+
+ /* Indicates whether the server can understand svndiff version 1. */
+ svn_boolean_t supports_svndiff1;
+
+ /* Indicates whether the server can understand svndiff version 2. */
+ svn_boolean_t supports_svndiff2;
+
+ /* Indicates whether the server sends the result checksum in the response
+ * to a successful PUT request. */
+ svn_boolean_t supports_put_result_checksum;
+
+ apr_interval_time_t conn_latency;
};
#define SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(sess) ((sess)->me_resource != NULL)
@@ -1412,6 +1431,18 @@ svn_ra_serf__get_locks(svn_ra_session_t *ra_session,
svn_depth_t depth,
apr_pool_t *pool);
+/* Implements svn_ra__vtable_t.list(). */
+svn_error_t *
+svn_ra_serf__list(svn_ra_session_t *ra_session,
+ const char *path,
+ svn_revnum_t revision,
+ const apr_array_header_t *patterns,
+ svn_depth_t depth,
+ apr_uint32_t dirent_fields,
+ svn_ra_dirent_receiver_t receiver,
+ void *receiver_baton,
+ apr_pool_t *scratch_pool);
+
/* Request a mergeinfo-report from the URL attached to SESSION,
and fill in the MERGEINFO hash with the results.
@@ -1515,6 +1546,11 @@ svn_ra_serf__error_on_status(serf_status_line sline,
svn_error_t *
svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler);
+/* Make sure handler is no longer scheduled on its connection. Resetting
+ the connection if necessary */
+void
+svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler);
+
/* ###? */
svn_error_t *
@@ -1557,6 +1593,70 @@ svn_ra_serf__uri_parse(apr_uri_t *uri,
const char *url_str,
apr_pool_t *result_pool);
+/* Setup the "Accept-Encoding" header value for requests that expect
+ svndiff-encoded deltas, depending on the SESSION state. */
+void
+svn_ra_serf__setup_svndiff_accept_encoding(serf_bucket_t *headers,
+ svn_ra_serf__session_t *session);
+
+svn_boolean_t
+svn_ra_serf__is_low_latency_connection(svn_ra_serf__session_t *session);
+
+/* Return an APR array of svn_ra_serf__dav_props_t containing the
+ * properties (names and namespaces) corresponding to the flegs set
+ * in DIRENT_FIELDS. If SESSION does not support deadprops, only
+ * the generic "DAV:allprop" will be returned. Allocate the result
+ * in RESULT_POOL. */
+apr_array_header_t *
+svn_ra_serf__get_dirent_props(apr_uint32_t dirent_fields,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *result_pool);
+
+/* Default limit for in-memory size of a request body. */
+#define SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE 256 * 1024
+
+/* An opaque structure used to prepare a request body. */
+typedef struct svn_ra_serf__request_body_t svn_ra_serf__request_body_t;
+
+/* Constructor for svn_ra_serf__request_body_t. Creates a new writable
+ buffer for the request body. Request bodies under IN_MEMORY_SIZE
+ bytes will be held in memory, otherwise, the body content will be
+ spilled to a temporary file. */
+svn_ra_serf__request_body_t *
+svn_ra_serf__request_body_create(apr_size_t in_memory_size,
+ apr_pool_t *result_pool);
+
+/* Get the writable stream associated with BODY. */
+svn_stream_t *
+svn_ra_serf__request_body_get_stream(svn_ra_serf__request_body_t *body);
+
+/* Get a svn_ra_serf__request_body_delegate_t and baton for BODY. */
+void
+svn_ra_serf__request_body_get_delegate(svn_ra_serf__request_body_delegate_t *del,
+ void **baton,
+ svn_ra_serf__request_body_t *body);
+
+/* Release intermediate resources associated with BODY. These resources
+ (such as open file handles) will be automatically released when the
+ pool used to construct BODY is cleared or destroyed, but this optional
+ function allows doing that explicitly. */
+svn_error_t *
+svn_ra_serf__request_body_cleanup(svn_ra_serf__request_body_t *body,
+ apr_pool_t *scratch_pool);
+
+/* Callback used in svn_ra_serf__create_stream_bucket(). ERR will be
+ will be cleared and becomes invalid after the callback returns,
+ use svn_error_dup() to preserve it. */
+typedef void
+(*svn_ra_serf__stream_bucket_errfunc_t)(void *baton, svn_error_t *err);
+
+/* Create a bucket that wraps a generic readable STREAM. This function
+ takes ownership of the passed-in stream, and will close it. */
+serf_bucket_t *
+svn_ra_serf__create_stream_bucket(svn_stream_t *stream,
+ serf_bucket_alloc_t *allocator,
+ svn_ra_serf__stream_bucket_errfunc_t errfunc,
+ void *errfunc_baton);
#if defined(SVN_DEBUG)
/* Wrapper macros to collect file and line information */
diff --git a/subversion/libsvn_ra_serf/replay.c b/subversion/libsvn_ra_serf/replay.c
index 8d2da69fa4ebe..bb90ad4a80419 100644
--- a/subversion/libsvn_ra_serf/replay.c
+++ b/subversion/libsvn_ra_serf/replay.c
@@ -166,6 +166,8 @@ typedef struct revision_report_t {
svn_ra_serf__handler_t *propfind_handler;
svn_ra_serf__handler_t *report_handler; /* For done handler */
+ svn_ra_serf__session_t *session;
+
} revision_report_t;
/* Conforms to svn_ra_serf__xml_opened_t */
@@ -427,10 +429,11 @@ replay_closed(svn_ra_serf__xml_estate_t *xes,
{
struct replay_node_t *node = ctx->current_node;
- if (! node || ! node->file || ! node->stream)
+ if (! node || ! node->file)
return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL);
- SVN_ERR(svn_stream_close(node->stream));
+ if (node->stream)
+ SVN_ERR(svn_stream_close(node->stream));
node->stream = NULL;
}
@@ -565,10 +568,10 @@ svn_ra_serf__replay(svn_ra_session_t *ra_session,
SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
- return svn_error_trace(
- svn_ra_serf__error_on_status(handler->sline,
- handler->path,
- handler->location));
+ if (handler->sline.code != 200)
+ SVN_ERR(svn_ra_serf__unexpected_status(handler));
+
+ return SVN_NO_ERROR;
}
/* The maximum number of outstanding requests at any time. When this
@@ -629,6 +632,20 @@ replay_done(serf_request_t *request,
return SVN_NO_ERROR;
}
+/* Implements svn_ra_serf__request_header_delegate_t */
+static svn_error_t *
+setup_headers(serf_bucket_t *headers,
+ void *baton,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct revision_report_t *ctx = baton;
+
+ svn_ra_serf__setup_svndiff_accept_encoding(headers, ctx->session);
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
svn_revnum_t start_revision,
@@ -646,9 +663,28 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
int active_reports = 0;
const char *include_path;
svn_boolean_t done;
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+
+ if (session->http20) {
+ /* ### Auch... this doesn't work yet...
+
+ This code relies on responses coming in in an exact order, while
+ http2 does everything to deliver responses as fast as possible.
+
+ With http/1.1 we were quite lucky that this worked, as serf doesn't
+ promise in order delivery.... (Please do not use authz with keys
+ that expire)
+
+ For now fall back to the legacy callback in libsvn_ra that is
+ used by all the other ra layers as workaround.
+
+ ### TODO: Optimize
+ */
+ return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
+ }
SVN_ERR(svn_ra_serf__report_resource(&report_target, session,
- scratch_pool));
+ subpool));
/* Prior to 1.8, mod_dav_svn expect to get replay REPORT requests
aimed at the session URL. But that's incorrect -- these reports
@@ -671,7 +707,7 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
{
SVN_ERR(svn_ra_serf__get_relative_path(&include_path,
session->session_url.path,
- session, scratch_pool));
+ session, subpool));
}
else
{
@@ -689,7 +725,7 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
{
struct revision_report_t *rev_ctx;
svn_ra_serf__handler_t *handler;
- apr_pool_t *rev_pool = svn_pool_create(scratch_pool);
+ apr_pool_t *rev_pool = svn_pool_create(subpool);
svn_ra_serf__xml_context_t *xmlctx;
const char *replay_target;
@@ -704,6 +740,7 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
rev_ctx->revision = rev;
rev_ctx->low_water_mark = low_water_mark;
rev_ctx->send_deltas = send_deltas;
+ rev_ctx->session = session;
/* Request all properties of a certain revision. */
rev_ctx->rev_props = apr_hash_make(rev_ctx->pool);
@@ -761,6 +798,10 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
handler->done_delegate = replay_done;
handler->done_delegate_baton = rev_ctx;
+ handler->custom_accept_encoding = TRUE;
+ handler->header_delegate = setup_headers;
+ handler->header_delegate_baton = rev_ctx;
+
rev_ctx->report_handler = handler;
svn_ra_serf__request_create(handler);
@@ -770,13 +811,23 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session,
/* Run the serf loop. */
done = FALSE;
- SVN_ERR(svn_ra_serf__context_run_wait(&done, session, scratch_pool));
+ {
+ svn_error_t *err = svn_ra_serf__context_run_wait(&done, session,
+ subpool);
+
+ if (err)
+ {
+ svn_pool_destroy(subpool); /* Unregister all requests! */
+ return svn_error_trace(err);
+ }
+ }
/* The done handler of reports decrements active_reports when a report
is done. This same handler reports (fatal) report errors, so we can
just loop here. */
}
+ svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
#undef MAX_OUTSTANDING_REQUESTS
diff --git a/subversion/libsvn_ra_serf/request_body.c b/subversion/libsvn_ra_serf/request_body.c
new file mode 100644
index 0000000000000..eb3b9fc1c10c1
--- /dev/null
+++ b/subversion/libsvn_ra_serf/request_body.c
@@ -0,0 +1,232 @@
+/*
+ * request_body.c : svn_ra_serf__request_body_t 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 <serf.h>
+
+#include "ra_serf.h"
+
+struct svn_ra_serf__request_body_t
+{
+ svn_stream_t *stream;
+ apr_size_t in_memory_size;
+ apr_size_t total_bytes;
+ serf_bucket_alloc_t *alloc;
+ serf_bucket_t *collect_bucket;
+ const void *all_data;
+ apr_file_t *file;
+ apr_pool_t *result_pool;
+ apr_pool_t *scratch_pool;
+};
+
+/* Fold all previously collected data in a single buffer allocated in
+ RESULT_POOL and clear all intermediate state. */
+static const char *
+allocate_all(svn_ra_serf__request_body_t *body,
+ apr_pool_t *result_pool)
+{
+ char *buffer = apr_pcalloc(result_pool, body->total_bytes);
+ const char *data;
+ apr_size_t sz;
+ apr_status_t s;
+ apr_size_t remaining = body->total_bytes;
+ char *next = buffer;
+
+ while (!(s = serf_bucket_read(body->collect_bucket, remaining, &data, &sz)))
+ {
+ memcpy(next, data, sz);
+ remaining -= sz;
+ next += sz;
+
+ if (! remaining)
+ break;
+ }
+
+ if (!SERF_BUCKET_READ_ERROR(s))
+ {
+ memcpy(next, data, sz);
+ }
+
+ serf_bucket_destroy(body->collect_bucket);
+ body->collect_bucket = NULL;
+
+ return (s != APR_EOF) ? NULL : buffer;
+}
+
+/* Noop function. Make serf take care of freeing in error situations. */
+static void serf_free_no_error(void *unfreed_baton, void *block) {}
+
+/* Stream write function for body creation. */
+static svn_error_t *
+request_body_stream_write(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ svn_ra_serf__request_body_t *b = baton;
+
+ if (!b->scratch_pool)
+ b->scratch_pool = svn_pool_create(b->result_pool);
+
+ if (b->file)
+ {
+ SVN_ERR(svn_io_file_write_full(b->file, data, *len, NULL,
+ b->scratch_pool));
+ svn_pool_clear(b->scratch_pool);
+
+ b->total_bytes += *len;
+ }
+ else if (*len + b->total_bytes > b->in_memory_size)
+ {
+ SVN_ERR(svn_io_open_unique_file3(&b->file, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ b->result_pool, b->scratch_pool));
+
+ if (b->total_bytes)
+ {
+ const char *all = allocate_all(b, b->scratch_pool);
+
+ SVN_ERR(svn_io_file_write_full(b->file, all, b->total_bytes,
+ NULL, b->scratch_pool));
+ }
+
+ SVN_ERR(svn_io_file_write_full(b->file, data, *len, NULL,
+ b->scratch_pool));
+ b->total_bytes += *len;
+ }
+ else
+ {
+ if (!b->alloc)
+ b->alloc = serf_bucket_allocator_create(b->scratch_pool,
+ serf_free_no_error, NULL);
+
+ if (!b->collect_bucket)
+ b->collect_bucket = serf_bucket_aggregate_create(b->alloc);
+
+ serf_bucket_aggregate_append(b->collect_bucket,
+ serf_bucket_simple_copy_create(data, *len,
+ b->alloc));
+
+ b->total_bytes += *len;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Stream close function for collecting body. */
+static svn_error_t *
+request_body_stream_close(void *baton)
+{
+ svn_ra_serf__request_body_t *b = baton;
+
+ if (b->file)
+ {
+ /* 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 handle.
+ */
+
+ SVN_ERR(svn_io_file_flush(b->file, b->scratch_pool));
+ apr_file_buffer_set(b->file, NULL, 0);
+ }
+ else if (b->collect_bucket)
+ b->all_data = allocate_all(b, b->result_pool);
+
+ if (b->scratch_pool)
+ svn_pool_destroy(b->scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements svn_ra_serf__request_body_delegate_t. */
+static svn_error_t *
+request_body_delegate(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *request_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_ra_serf__request_body_t *b = baton;
+
+ if (b->file)
+ {
+ apr_off_t offset;
+
+ offset = 0;
+ SVN_ERR(svn_io_file_seek(b->file, APR_SET, &offset, scratch_pool));
+
+ *body_bkt = serf_bucket_file_create(b->file, alloc);
+ }
+ else
+ {
+ *body_bkt = serf_bucket_simple_create(b->all_data,
+ b->total_bytes,
+ NULL, NULL, alloc);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_ra_serf__request_body_t *
+svn_ra_serf__request_body_create(apr_size_t in_memory_size,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__request_body_t *body = apr_pcalloc(result_pool, sizeof(*body));
+
+ body->in_memory_size = in_memory_size;
+ body->result_pool = result_pool;
+ body->stream = svn_stream_create(body, result_pool);
+
+ svn_stream_set_write(body->stream, request_body_stream_write);
+ svn_stream_set_close(body->stream, request_body_stream_close);
+
+ return body;
+}
+
+svn_stream_t *
+svn_ra_serf__request_body_get_stream(svn_ra_serf__request_body_t *body)
+{
+ return body->stream;
+}
+
+void
+svn_ra_serf__request_body_get_delegate(svn_ra_serf__request_body_delegate_t *del,
+ void **baton,
+ svn_ra_serf__request_body_t *body)
+{
+ *del = request_body_delegate;
+ *baton = body;
+}
+
+svn_error_t *
+svn_ra_serf__request_body_cleanup(svn_ra_serf__request_body_t *body,
+ apr_pool_t *scratch_pool)
+{
+ if (body->file)
+ SVN_ERR(svn_io_file_close(body->file, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_ra_serf/sb_bucket.c b/subversion/libsvn_ra_serf/sb_bucket.c
index df0541f75eb30..b88dba53d8a20 100644
--- a/subversion/libsvn_ra_serf/sb_bucket.c
+++ b/subversion/libsvn_ra_serf/sb_bucket.c
@@ -117,7 +117,7 @@ sb_bucket_read(serf_bucket_t *bucket, apr_size_t requested,
return *data == NULL ? APR_EOF : APR_SUCCESS;
}
-
+#if !SERF_VERSION_AT_LEAST(1, 4, 0)
static apr_status_t
sb_bucket_readline(serf_bucket_t *bucket, int acceptable,
int *found,
@@ -128,6 +128,7 @@ sb_bucket_readline(serf_bucket_t *bucket, int acceptable,
"Not implemented."));
return APR_ENOTIMPL;
}
+#endif
static apr_status_t
@@ -159,7 +160,11 @@ sb_bucket_peek(serf_bucket_t *bucket,
static const serf_bucket_type_t sb_bucket_vtable = {
"SPILLBUF",
sb_bucket_read,
+#if SERF_VERSION_AT_LEAST(1, 4, 0)
+ serf_default_readline,
+#else
sb_bucket_readline,
+#endif
serf_default_read_iovec,
serf_default_read_for_sendfile,
serf_default_read_bucket,
diff --git a/subversion/libsvn_ra_serf/serf.c b/subversion/libsvn_ra_serf/serf.c
index 3c47d5eae9009..830e5cad532e9 100644
--- a/subversion/libsvn_ra_serf/serf.c
+++ b/subversion/libsvn_ra_serf/serf.c
@@ -153,7 +153,8 @@ load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
static svn_error_t *
load_config(svn_ra_serf__session_t *session,
apr_hash_t *config_hash,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
svn_config_t *config, *config_client;
const char *server_group;
@@ -179,9 +180,10 @@ load_config(svn_ra_serf__session_t *session,
config_client = NULL;
}
- SVN_ERR(svn_config_get_bool(config, &session->using_compression,
- SVN_CONFIG_SECTION_GLOBAL,
- SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
+ SVN_ERR(svn_config_get_tristate(config, &session->using_compression,
+ SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_COMPRESSION,
+ "auto", svn_tristate_unknown));
svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
@@ -207,7 +209,7 @@ load_config(svn_ra_serf__session_t *session,
SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
if (! svn_cstring_match_glob_list(session->session_url.hostname,
svn_cstring_split(exceptions, ",",
- TRUE, pool)))
+ TRUE, scratch_pool)))
{
svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
@@ -265,10 +267,10 @@ load_config(svn_ra_serf__session_t *session,
if (server_group)
{
- SVN_ERR(svn_config_get_bool(config, &session->using_compression,
- server_group,
- SVN_CONFIG_OPTION_HTTP_COMPRESSION,
- session->using_compression));
+ SVN_ERR(svn_config_get_tristate(config, &session->using_compression,
+ server_group,
+ SVN_CONFIG_OPTION_HTTP_COMPRESSION,
+ "auto", session->using_compression));
svn_config_get(config, &timeout_str, server_group,
SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
@@ -340,7 +342,7 @@ load_config(svn_ra_serf__session_t *session,
(apr_uint32_t)log_components,
SERF_LOG_DEFAULT_LAYOUT,
stderr,
- pool);
+ result_pool);
if (!status)
serf_logging_add_output(session->context, output);
@@ -359,19 +361,16 @@ load_config(svn_ra_serf__session_t *session,
session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
if (timeout_str)
{
- char *endstr;
- const long int timeout = strtol(timeout_str, &endstr, 10);
-
- if (*endstr)
- return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
- _("Invalid config: illegal character in "
- "timeout value"));
- if (timeout < 0)
- return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
- _("Invalid config: negative timeout value"));
+ apr_int64_t timeout;
+ svn_error_t *err;
+
+ err = svn_cstring_strtoi64(&timeout, timeout_str, 0, APR_INT64_MAX, 10);
+ if (err)
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, err,
+ _("invalid config: bad value for '%s' option"),
+ SVN_CONFIG_OPTION_HTTP_TIMEOUT);
session->timeout = apr_time_from_sec(timeout);
}
- SVN_ERR_ASSERT(session->timeout >= 0);
/* Convert the proxy port value, if any. */
if (port_str)
@@ -438,7 +437,7 @@ load_config(svn_ra_serf__session_t *session,
}
/* Setup authentication. */
- SVN_ERR(load_http_auth_types(pool, config, server_group,
+ SVN_ERR(load_http_auth_types(result_pool, config, server_group,
&session->authn_types));
serf_config_authn_types(session->context, session->authn_types);
serf_config_credentials_callback(session->context,
@@ -449,12 +448,13 @@ load_config(svn_ra_serf__session_t *session,
#undef DEFAULT_HTTP_TIMEOUT
static void
-svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
+svn_ra_serf__progress(void *progress_baton, apr_off_t bytes_read,
+ apr_off_t bytes_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_func(bytes_read + bytes_written, -1,
serf_sess->progress_baton,
serf_sess->pool);
}
@@ -531,12 +531,13 @@ svn_ra_serf__open(svn_ra_session_t *session,
/* We have to assume that the server only supports HTTP/1.0. Once it's clear
HTTP/1.1 is supported, we can upgrade. */
serf_sess->http10 = TRUE;
+ serf_sess->http20 = FALSE;
/* If we switch to HTTP/1.1, then we will use chunked requests. We may disable
this, if we find an intervening proxy does not support chunked requests. */
serf_sess->using_chunked_requests = TRUE;
- SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
+ SVN_ERR(load_config(serf_sess, config, serf_sess->pool, scratch_pool));
serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
sizeof(*serf_sess->conns[0]));
@@ -593,8 +594,12 @@ svn_ra_serf__open(svn_ra_session_t *session,
&& apr_pool_is_ancestor(serf_sess->pool, scratch_pool));
#endif
+ /* The actual latency will be determined as a part of the initial
+ OPTIONS request. */
+ serf_sess->conn_latency = -1;
+
err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url,
- result_pool, scratch_pool);
+ result_pool, scratch_pool);
/* serf should produce a usable error code instead of APR_EGENERAL */
if (err && err->apr_err == APR_EGENERAL)
@@ -637,6 +642,7 @@ ra_serf_dup_session(svn_ra_session_t *new_session,
/* using_ssl */
/* using_compression */
/* http10 */
+ /* http20 */
/* using_chunked_requests */
/* detect_chunking */
@@ -685,7 +691,7 @@ ra_serf_dup_session(svn_ra_session_t *new_session,
if (new_sess->proxy_password)
{
- new_sess->proxy_username
+ new_sess->proxy_password
= apr_pstrdup(result_pool, new_sess->proxy_password);
}
@@ -733,11 +739,14 @@ ra_serf_dup_session(svn_ra_session_t *new_session,
new_sess->server_allows_bulk = apr_pstrdup(result_pool,
new_sess->server_allows_bulk);
- new_sess->repos_root_str = apr_pstrdup(result_pool,
- new_sess->repos_root_str);
- SVN_ERR(svn_ra_serf__uri_parse(&new_sess->repos_root,
- new_sess->repos_root_str,
- result_pool));
+ if (new_sess->repos_root_str)
+ {
+ new_sess->repos_root_str = apr_pstrdup(result_pool,
+ new_sess->repos_root_str);
+ SVN_ERR(svn_ra_serf__uri_parse(&new_sess->repos_root,
+ new_sess->repos_root_str,
+ result_pool));
+ }
new_sess->session_url_str = apr_pstrdup(result_pool, new_session_url);
@@ -747,10 +756,15 @@ ra_serf_dup_session(svn_ra_session_t *new_session,
/* svn_boolean_t supports_inline_props */
/* supports_rev_rsrc_replay */
+ /* supports_svndiff1 */
+ /* supports_svndiff2 */
+ /* supports_put_result_checksum */
+ /* conn_latency */
new_sess->context = serf_context_create(result_pool);
- SVN_ERR(load_config(new_sess, old_sess->config, result_pool));
+ SVN_ERR(load_config(new_sess, old_sess->config,
+ result_pool, scratch_pool));
new_sess->conns[0] = apr_pcalloc(result_pool,
sizeof(*new_sess->conns[0]));
@@ -1047,8 +1061,12 @@ static const svn_ra__vtable_t serf_vtable = {
svn_ra_serf__has_capability,
svn_ra_serf__replay_range,
svn_ra_serf__get_deleted_rev,
+ svn_ra_serf__get_inherited_props,
+ NULL /* set_svn_ra_open */,
+ svn_ra_serf__list,
svn_ra_serf__register_editor_shim_callbacks,
- svn_ra_serf__get_inherited_props
+ NULL /* commit_ev2 */,
+ NULL /* replay_range_ev2 */
};
svn_error_t *
diff --git a/subversion/libsvn_ra_serf/stat.c b/subversion/libsvn_ra_serf/stat.c
index b6d10c51f29d1..be5ae5a010f0b 100644
--- a/subversion/libsvn_ra_serf/stat.c
+++ b/subversion/libsvn_ra_serf/stat.c
@@ -193,6 +193,9 @@ fill_dirent_propfunc(void *baton,
{
if (*val->data)
{
+ /* Note: 1.8.x and earlier servers send the count proper; 1.9.0
+ * and newer send "1" if there are properties and "0" otherwise.
+ */
apr_int64_t deadprop_count;
SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
fdb->entry->has_props = deadprop_count > 0;
@@ -213,64 +216,8 @@ get_dirent_props(apr_uint32_t dirent_fields,
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->xmlns = "DAV:";
- prop->name = "resourcetype";
- }
-
- if (dirent_fields & SVN_DIRENT_SIZE)
- {
- prop = apr_array_push(props);
- prop->xmlns = "DAV:";
- prop->name = "getcontentlength";
- }
-
- if (dirent_fields & SVN_DIRENT_HAS_PROPS)
- {
- prop = apr_array_push(props);
- prop->xmlns = 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->xmlns = "DAV:";
- p->name = SVN_DAV__VERSION_NAME;
- }
-
- if (dirent_fields & SVN_DIRENT_TIME)
- {
- prop = apr_array_push(props);
- prop->xmlns = "DAV:";
- prop->name = SVN_DAV__CREATIONDATE;
- }
-
- if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
- {
- prop = apr_array_push(props);
- prop->xmlns = "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->xmlns = "DAV:";
- prop->name = "allprop";
- }
+ apr_array_header_t *props = svn_ra_serf__get_dirent_props(dirent_fields,
+ session, pool);
prop = apr_array_push(props);
prop->xmlns = NULL;
diff --git a/subversion/libsvn_ra_serf/stream_bucket.c b/subversion/libsvn_ra_serf/stream_bucket.c
new file mode 100644
index 0000000000000..79de7bfe84519
--- /dev/null
+++ b/subversion/libsvn_ra_serf/stream_bucket.c
@@ -0,0 +1,120 @@
+/*
+ * stream_bucket.c : a serf bucket that wraps a readable svn_stream_t
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <serf.h>
+#include <serf_bucket_util.h>
+
+#include "ra_serf.h"
+
+typedef struct stream_bucket_ctx_t
+{
+ svn_stream_t *stream;
+ svn_ra_serf__stream_bucket_errfunc_t errfunc;
+ void *errfunc_baton;
+ serf_databuf_t databuf;
+} stream_bucket_ctx_t;
+
+static apr_status_t
+stream_reader(void *baton, apr_size_t bufsize, char *buf, apr_size_t *len)
+{
+ stream_bucket_ctx_t *ctx = baton;
+ svn_error_t *err;
+
+ *len = bufsize;
+
+ err = svn_stream_read_full(ctx->stream, buf, len);
+ if (err)
+ {
+ if (ctx->errfunc)
+ ctx->errfunc(ctx->errfunc_baton, err);
+ svn_error_clear(err);
+
+ return SVN_ERR_RA_SERF_STREAM_BUCKET_READ_ERROR;
+ }
+
+ if (*len == bufsize)
+ {
+ return APR_SUCCESS;
+ }
+ else
+ {
+ svn_error_clear(svn_stream_close(ctx->stream));
+ return APR_EOF;
+ }
+}
+
+static apr_status_t
+stream_bucket_read(serf_bucket_t *bucket, apr_size_t requested,
+ const char **data, apr_size_t *len)
+{
+ stream_bucket_ctx_t *ctx = bucket->data;
+
+ return serf_databuf_read(&ctx->databuf, requested, data, len);
+}
+
+static apr_status_t
+stream_bucket_readline(serf_bucket_t *bucket, int acceptable,
+ int *found, const char **data, apr_size_t *len)
+{
+ stream_bucket_ctx_t *ctx = bucket->data;
+
+ return serf_databuf_readline(&ctx->databuf, acceptable, found, data, len);
+}
+
+static apr_status_t
+stream_bucket_peek(serf_bucket_t *bucket, const char **data, apr_size_t *len)
+{
+ stream_bucket_ctx_t *ctx = bucket->data;
+
+ return serf_databuf_peek(&ctx->databuf, data, len);
+}
+
+static const serf_bucket_type_t stream_bucket_vtable = {
+ "SVNSTREAM",
+ stream_bucket_read,
+ stream_bucket_readline,
+ serf_default_read_iovec,
+ serf_default_read_for_sendfile,
+ serf_default_read_bucket,
+ stream_bucket_peek,
+ serf_default_destroy_and_data
+};
+
+serf_bucket_t *
+svn_ra_serf__create_stream_bucket(svn_stream_t *stream,
+ serf_bucket_alloc_t *allocator,
+ svn_ra_serf__stream_bucket_errfunc_t errfunc,
+ void *errfunc_baton)
+{
+ stream_bucket_ctx_t *ctx;
+
+ ctx = serf_bucket_mem_calloc(allocator, sizeof(*ctx));
+ ctx->stream = stream;
+ ctx->errfunc = errfunc;
+ ctx->errfunc_baton = errfunc_baton;
+ serf_databuf_init(&ctx->databuf);
+ ctx->databuf.read = stream_reader;
+ ctx->databuf.read_baton = ctx;
+
+ return serf_bucket_create(&stream_bucket_vtable, allocator, ctx);
+}
diff --git a/subversion/libsvn_ra_serf/update.c b/subversion/libsvn_ra_serf/update.c
index 8313af019ecc3..63091f2ebd21d 100644
--- a/subversion/libsvn_ra_serf/update.c
+++ b/subversion/libsvn_ra_serf/update.c
@@ -365,7 +365,7 @@ typedef struct fetch_ctx_t {
/* The handler representing this particular fetch. */
svn_ra_serf__handler_t *handler;
- svn_boolean_t using_compression;
+ svn_ra_serf__session_t *session;
/* Stores the information for the file we want to fetch. */
file_baton_t *file;
@@ -433,13 +433,11 @@ struct report_context_t {
const svn_delta_editor_t *editor;
void *editor_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.
- */
+ /* Stream for collecting the request body. */
svn_stream_t *body_template;
- body_create_baton_t *body;
+
+ /* Buffer holding request body for the REPORT (can spill to disk). */
+ svn_ra_serf__request_body_t *body;
/* number of pending GET requests */
unsigned int num_active_fetches;
@@ -457,148 +455,6 @@ struct report_context_t {
svn_boolean_t closed_root;
};
-/* Baton for collecting REPORT body. Depending on the size this
- work is backed by a memory buffer (via serf buckets) or by
- a file */
-struct body_create_baton_t
-{
- apr_pool_t *result_pool;
- apr_size_t total_bytes;
-
- apr_pool_t *scratch_pool;
-
- serf_bucket_alloc_t *alloc;
- serf_bucket_t *collect_bucket;
-
- const void *all_data;
- apr_file_t *file;
-};
-
-
-#define MAX_BODY_IN_RAM (256*1024)
-
-/* Fold all previously collected data in a single buffer allocated in
- RESULT_POOL and clear all intermediate state */
-static const char *
-body_allocate_all(body_create_baton_t *body,
- apr_pool_t *result_pool)
-{
- char *buffer = apr_pcalloc(result_pool, body->total_bytes);
- const char *data;
- apr_size_t sz;
- apr_status_t s;
- apr_size_t remaining = body->total_bytes;
- char *next = buffer;
-
- while (!(s = serf_bucket_read(body->collect_bucket, remaining, &data, &sz)))
- {
- memcpy(next, data, sz);
- remaining -= sz;
- next += sz;
-
- if (! remaining)
- break;
- }
-
- if (!SERF_BUCKET_READ_ERROR(s))
- {
- memcpy(next, data, sz);
- }
-
- serf_bucket_destroy(body->collect_bucket);
- body->collect_bucket = NULL;
-
- return (s != APR_EOF) ? NULL : buffer;
-}
-
-/* Noop function. Make serf take care of freeing in error situations */
-static void serf_free_no_error(void *unfreed_baton, void *block) {}
-
-/* Stream write function for body creation */
-static svn_error_t *
-body_write_fn(void *baton,
- const char *data,
- apr_size_t *len)
-{
- body_create_baton_t *bcb = baton;
-
- if (!bcb->scratch_pool)
- bcb->scratch_pool = svn_pool_create(bcb->result_pool);
-
- if (bcb->file)
- {
- SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL,
- bcb->scratch_pool));
- svn_pool_clear(bcb->scratch_pool);
-
- bcb->total_bytes += *len;
- }
- else if (*len + bcb->total_bytes > MAX_BODY_IN_RAM)
- {
- SVN_ERR(svn_io_open_unique_file3(&bcb->file, NULL, NULL,
- svn_io_file_del_on_pool_cleanup,
- bcb->result_pool, bcb->scratch_pool));
-
- if (bcb->total_bytes)
- {
- const char *all = body_allocate_all(bcb, bcb->scratch_pool);
-
- SVN_ERR(svn_io_file_write_full(bcb->file, all, bcb->total_bytes,
- NULL, bcb->scratch_pool));
- }
-
- SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL,
- bcb->scratch_pool));
- bcb->total_bytes += *len;
- }
- else
- {
- if (!bcb->alloc)
- bcb->alloc = serf_bucket_allocator_create(bcb->scratch_pool,
- serf_free_no_error, NULL);
-
- if (!bcb->collect_bucket)
- bcb->collect_bucket = serf_bucket_aggregate_create(bcb->alloc);
-
- serf_bucket_aggregate_append(bcb->collect_bucket,
- serf_bucket_simple_copy_create(data, *len,
- bcb->alloc));
-
- bcb->total_bytes += *len;
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Stream close function for collecting body */
-static svn_error_t *
-body_done_fn(void *baton)
-{
- body_create_baton_t *bcb = baton;
- if (bcb->file)
- {
- /* 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 handle.
- */
-
- SVN_ERR(svn_io_file_flush(bcb->file, bcb->scratch_pool));
- apr_file_buffer_set(bcb->file, NULL, 0);
- }
- else if (bcb->collect_bucket)
- bcb->all_data = body_allocate_all(bcb, bcb->result_pool);
-
- if (bcb->scratch_pool)
- svn_pool_destroy(bcb->scratch_pool);
-
- return SVN_NO_ERROR;
-}
-
static svn_error_t *
create_dir_baton(dir_baton_t **new_dir,
report_context_t *ctx,
@@ -941,10 +797,9 @@ headers_fetch(serf_bucket_t *headers,
{
serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER,
fetch_ctx->delta_base);
- serf_bucket_headers_setn(headers, "Accept-Encoding",
- "svndiff1;q=0.9,svndiff;q=0.8");
+ svn_ra_serf__setup_svndiff_accept_encoding(headers, fetch_ctx->session);
}
- else if (fetch_ctx->using_compression)
+ else if (fetch_ctx->session->using_compression != svn_tristate_false)
{
serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
}
@@ -1422,7 +1277,7 @@ fetch_for_file(file_baton_t *file,
fetch_ctx = apr_pcalloc(file->pool, sizeof(*fetch_ctx));
fetch_ctx->file = file;
- fetch_ctx->using_compression = ctx->sess->using_compression;
+ fetch_ctx->session = ctx->sess;
/* Can we somehow get away with just obtaining a DIFF? */
if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess))
@@ -2304,37 +2159,6 @@ link_path(void *report_baton,
return APR_SUCCESS;
}
-/* Serf callback to create update request body bucket.
- Implements svn_ra_serf__request_body_delegate_t */
-static svn_error_t *
-create_update_report_body(serf_bucket_t **body_bkt,
- void *baton,
- serf_bucket_alloc_t *alloc,
- apr_pool_t *pool /* request pool */,
- apr_pool_t *scratch_pool)
-{
- report_context_t *report = baton;
- body_create_baton_t *body = report->body;
-
- if (body->file)
- {
- apr_off_t offset;
-
- offset = 0;
- SVN_ERR(svn_io_file_seek(body->file, APR_SET, &offset, pool));
-
- *body_bkt = serf_bucket_file_create(report->body->file, alloc);
- }
- else
- {
- *body_bkt = serf_bucket_simple_create(body->all_data,
- body->total_bytes,
- NULL, NULL, alloc);
- }
-
- return SVN_NO_ERROR;
-}
-
/* Serf callback to setup update request headers. */
static svn_error_t *
setup_update_report_headers(serf_bucket_t *headers,
@@ -2344,16 +2168,7 @@ setup_update_report_headers(serf_bucket_t *headers,
{
report_context_t *report = baton;
- if (report->sess->using_compression)
- {
- serf_bucket_headers_setn(headers, "Accept-Encoding",
- "gzip,svndiff1;q=0.9,svndiff;q=0.8");
- }
- else
- {
- serf_bucket_headers_setn(headers, "Accept-Encoding",
- "svndiff1;q=0.9,svndiff;q=0.8");
- }
+ svn_ra_serf__setup_svndiff_accept_encoding(headers, report->sess);
return SVN_NO_ERROR;
}
@@ -2674,10 +2489,11 @@ finish_report(void *report_baton,
handler = svn_ra_serf__create_expat_handler(sess, xmlctx, NULL,
scratch_pool);
+ svn_ra_serf__request_body_get_delegate(&handler->body_delegate,
+ &handler->body_delegate_baton,
+ report->body);
handler->method = "REPORT";
handler->path = report_target;
- handler->body_delegate = create_update_report_body;
- handler->body_delegate_baton = report;
handler->body_type = "text/xml";
handler->custom_accept_encoding = TRUE;
handler->header_delegate = setup_update_report_headers;
@@ -2792,11 +2608,10 @@ make_update_reporter(svn_ra_session_t *ra_session,
*reporter = &ra_serf_reporter;
*report_baton = report;
- report->body = apr_pcalloc(report->pool, sizeof(*report->body));
- report->body->result_pool = report->pool;
- report->body_template = svn_stream_create(report->body, report->pool);
- svn_stream_set_write(report->body_template, body_write_fn);
- svn_stream_set_close(report->body_template, body_done_fn);
+ report->body =
+ svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE,
+ report->pool);
+ report->body_template = svn_ra_serf__request_body_get_stream(report->body);
if (sess->bulk_updates == svn_tristate_true)
{
diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c
index 5490ddea8a592..52bf93b3a9b18 100644
--- a/subversion/libsvn_ra_serf/util.c
+++ b/subversion/libsvn_ra_serf/util.c
@@ -65,7 +65,9 @@ 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)
+ for (i = 0;
+ i < sizeof(serf_failure_map) / (sizeof(serf_failure_map[0]));
+ ++i)
{
if (failures & serf_failure_map[i][0])
{
@@ -478,6 +480,38 @@ load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
return SVN_NO_ERROR;
}
+#if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2)
+/* Implements serf_ssl_protocol_result_cb_t */
+static apr_status_t
+conn_negotiate_protocol(void *data,
+ const char *protocol)
+{
+ svn_ra_serf__connection_t *conn = data;
+
+ if (!strcmp(protocol, "h2"))
+ {
+ serf_connection_set_framing_type(
+ conn->conn,
+ SERF_CONNECTION_FRAMING_TYPE_HTTP2);
+
+ /* Disable generating content-length headers. */
+ conn->session->http10 = FALSE;
+ conn->session->http20 = TRUE;
+ conn->session->using_chunked_requests = TRUE;
+ conn->session->detect_chunking = FALSE;
+ }
+ else
+ {
+ /* protocol should be "" or "http/1.1" */
+ serf_connection_set_framing_type(
+ conn->conn,
+ SERF_CONNECTION_FRAMING_TYPE_HTTP1);
+ }
+
+ return APR_SUCCESS;
+}
+#endif
+
static svn_error_t *
conn_setup(apr_socket_t *sock,
serf_bucket_t **read_bkt,
@@ -523,6 +557,16 @@ conn_setup(apr_socket_t *sock,
SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
conn->session->pool));
}
+#if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2)
+ if (APR_SUCCESS ==
+ serf_ssl_negotiate_protocol(conn->ssl_context, "h2,http/1.1",
+ conn_negotiate_protocol, conn))
+ {
+ serf_connection_set_framing_type(
+ conn->conn,
+ SERF_CONNECTION_FRAMING_TYPE_NONE);
+ }
+#endif
}
if (write_bkt)
@@ -957,7 +1001,7 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done,
will cancel all outstanding requests and prepare the connection
for re-use.
*/
-static void
+void
svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler)
{
serf_connection_reset(handler->conn->conn);
@@ -1293,6 +1337,10 @@ handle_response(serf_request_t *request,
/* HTTP/1.1? (or later) */
if (sl.version != SERF_HTTP_10)
handler->session->http10 = FALSE;
+
+ if (sl.version >= SERF_HTTP_VERSION(2, 0)) {
+ handler->session->http20 = TRUE;
+ }
}
/* Keep reading from the network until we've read all the headers. */
@@ -1523,7 +1571,7 @@ setup_request(serf_request_t *request,
{
accept_encoding = NULL;
}
- else if (handler->session->using_compression)
+ else if (handler->session->using_compression != svn_tristate_false)
{
/* Accept gzip compression if enabled. */
accept_encoding = "gzip";
@@ -1975,3 +2023,114 @@ svn_ra_serf__uri_parse(apr_uri_t *uri,
return SVN_NO_ERROR;
}
+
+void
+svn_ra_serf__setup_svndiff_accept_encoding(serf_bucket_t *headers,
+ svn_ra_serf__session_t *session)
+{
+ if (session->using_compression == svn_tristate_false)
+ {
+ /* Don't advertise support for compressed svndiff formats if
+ compression is disabled. */
+ serf_bucket_headers_setn(
+ headers, "Accept-Encoding", "svndiff");
+ }
+ else if (session->using_compression == svn_tristate_unknown &&
+ svn_ra_serf__is_low_latency_connection(session))
+ {
+ /* With http-compression=auto, advertise that we prefer svndiff2
+ to svndiff1 with a low latency connection (assuming the underlying
+ network has high bandwidth), as it is faster and in this case, we
+ don't care about worse compression ratio. */
+ serf_bucket_headers_setn(
+ headers, "Accept-Encoding",
+ "gzip,svndiff2;q=0.9,svndiff1;q=0.8,svndiff;q=0.7");
+ }
+ else
+ {
+ /* Otherwise, advertise that we prefer svndiff1 over svndiff2.
+ svndiff2 is not a reasonable substitute for svndiff1 with default
+ compression level, because, while it is faster, it also gives worse
+ compression ratio. While we can use svndiff2 in some cases (see
+ above), we can't do this generally. */
+ serf_bucket_headers_setn(
+ headers, "Accept-Encoding",
+ "gzip,svndiff1;q=0.9,svndiff2;q=0.8,svndiff;q=0.7");
+ }
+}
+
+svn_boolean_t
+svn_ra_serf__is_low_latency_connection(svn_ra_serf__session_t *session)
+{
+ return session->conn_latency >= 0 &&
+ session->conn_latency < apr_time_from_msec(5);
+}
+
+apr_array_header_t *
+svn_ra_serf__get_dirent_props(apr_uint32_t dirent_fields,
+ svn_ra_serf__session_t *session,
+ apr_pool_t *result_pool)
+{
+ svn_ra_serf__dav_props_t *prop;
+ apr_array_header_t *props = apr_array_make
+ (result_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->xmlns = "DAV:";
+ prop->name = "resourcetype";
+ }
+
+ if (dirent_fields & SVN_DIRENT_SIZE)
+ {
+ prop = apr_array_push(props);
+ prop->xmlns = "DAV:";
+ prop->name = "getcontentlength";
+ }
+
+ if (dirent_fields & SVN_DIRENT_HAS_PROPS)
+ {
+ prop = apr_array_push(props);
+ prop->xmlns = 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->xmlns = "DAV:";
+ p->name = SVN_DAV__VERSION_NAME;
+ }
+
+ if (dirent_fields & SVN_DIRENT_TIME)
+ {
+ prop = apr_array_push(props);
+ prop->xmlns = "DAV:";
+ prop->name = SVN_DAV__CREATIONDATE;
+ }
+
+ if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
+ {
+ prop = apr_array_push(props);
+ prop->xmlns = "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->xmlns = "DAV:";
+ prop->name = "allprop";
+ }
+
+ return props;
+}
+
diff --git a/subversion/libsvn_ra_serf/xml.c b/subversion/libsvn_ra_serf/xml.c
index 1a988572b841a..cc6948cd607a2 100644
--- a/subversion/libsvn_ra_serf/xml.c
+++ b/subversion/libsvn_ra_serf/xml.c
@@ -24,7 +24,6 @@
#include <apr_uri.h>
-#include <expat.h>
#include <serf.h>
#include "svn_hash.h"
@@ -43,22 +42,6 @@
#include "ra_serf.h"
-/* Fix for older expat 1.95.x's that do not define
- * XML_STATUS_OK/XML_STATUS_ERROR
- */
-#ifndef XML_STATUS_OK
-#define XML_STATUS_OK 1
-#define XML_STATUS_ERROR 0
-#endif
-
-#ifndef XML_VERSION_AT_LEAST
-#define XML_VERSION_AT_LEAST(major,minor,patch) \
-(((major) < XML_MAJOR_VERSION) \
- || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \
- || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
- (patch) <= XML_MICRO_VERSION))
-#endif /* XML_VERSION_AT_LEAST */
-
/* Read/write chunks of this size into the spillbuf. */
#define PARSE_CHUNK_SIZE 8000
@@ -149,12 +132,10 @@ struct svn_ra_serf__xml_estate_t {
struct expat_ctx_t {
svn_ra_serf__xml_context_t *xmlctx;
- XML_Parser parser;
+ svn_xml_parser_t *parser;
svn_ra_serf__handler_t *handler;
const int *expected_status;
- svn_error_t *inner_error;
-
/* Do not use this pool for allocation. It is merely recorded for running
the cleanup handler. */
apr_pool_t *cleanup_pool;
@@ -886,132 +867,60 @@ xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx,
return SVN_NO_ERROR;
}
-/* svn_error_t * wrapper around XML_Parse */
+/* Wrapper around svn_xml_parse */
static APR_INLINE svn_error_t *
parse_xml(struct expat_ctx_t *ectx, const char *data, apr_size_t len, svn_boolean_t is_final)
{
- int xml_status = XML_Parse(ectx->parser, data, (int)len, is_final);
- const char *msg;
- int xml_code;
-
- if (xml_status == XML_STATUS_OK)
- return ectx->inner_error;
-
- xml_code = XML_GetErrorCode(ectx->parser);
-
-#if XML_VERSION_AT_LEAST(1, 95, 8)
- /* If we called XML_StopParser() expat will return an abort error. If we
- have a better error stored we should ignore it as it will not help
- the end-user to store it in the error chain. */
- if (xml_code == XML_ERROR_ABORTED && ectx->inner_error)
- return ectx->inner_error;
-#endif
-
- msg = XML_ErrorString(xml_code);
-
- return svn_error_compose_create(
- ectx->inner_error,
- svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA,
- svn_error_createf(SVN_ERR_XML_MALFORMED, NULL,
- _("Malformed XML: %s"),
- msg),
- _("The XML response contains invalid XML")));
-}
-
-/* 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;
+ svn_error_t *err = svn_xml_parse(ectx->parser, data, len, is_final);
- if (*xmlp)
- {
- (void) XML_ParserFree(*xmlp);
- *xmlp = NULL;
- }
+ if (err && err->apr_err == SVN_ERR_XML_MALFORMED)
+ err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
+ _("The XML response contains invalid XML"));
- return APR_SUCCESS;
+ return err;
}
-/* Conforms to Expat's XML_StartElementHandler */
+/* Implements svn_xml_start_elem callback */
static void
-expat_start(void *userData, const char *raw_name, const char **attrs)
+expat_start(void *baton, const char *raw_name, const char **attrs)
{
- struct expat_ctx_t *ectx = userData;
-
- if (ectx->inner_error != NULL)
- return;
+ struct expat_ctx_t *ectx = baton;
+ svn_error_t *err;
- ectx->inner_error = svn_error_trace(xml_cb_start(ectx->xmlctx,
- raw_name, attrs));
+ err = svn_error_trace(xml_cb_start(ectx->xmlctx, raw_name, attrs));
-#if XML_VERSION_AT_LEAST(1, 95, 8)
- if (ectx->inner_error)
- (void) XML_StopParser(ectx->parser, 0 /* resumable */);
-#endif
+ if (err)
+ svn_xml_signal_bailout(err, ectx->parser);
}
-/* Conforms to Expat's XML_EndElementHandler */
+/* Implements svn_xml_end_elem callback */
static void
-expat_end(void *userData, const char *raw_name)
+expat_end(void *baton, const char *raw_name)
{
- struct expat_ctx_t *ectx = userData;
-
- if (ectx->inner_error != NULL)
- return;
+ struct expat_ctx_t *ectx = baton;
+ svn_error_t *err;
- ectx->inner_error = svn_error_trace(xml_cb_end(ectx->xmlctx, raw_name));
+ err = svn_error_trace(xml_cb_end(ectx->xmlctx, raw_name));
-#if XML_VERSION_AT_LEAST(1, 95, 8)
- if (ectx->inner_error)
- (void) XML_StopParser(ectx->parser, 0 /* resumable */);
-#endif
+ if (err)
+ svn_xml_signal_bailout(err, ectx->parser);
}
-/* Conforms to Expat's XML_CharacterDataHandler */
+/* Implements svn_xml_char_data callback */
static void
-expat_cdata(void *userData, const char *data, int len)
+expat_cdata(void *baton, const char *data, apr_size_t len)
{
- struct expat_ctx_t *ectx = userData;
-
- if (ectx->inner_error != NULL)
- return;
+ struct expat_ctx_t *ectx = baton;
+ svn_error_t *err;
- ectx->inner_error = svn_error_trace(xml_cb_cdata(ectx->xmlctx, data, len));
+ err = svn_error_trace(xml_cb_cdata(ectx->xmlctx, data, len));
-#if XML_VERSION_AT_LEAST(1, 95, 8)
- if (ectx->inner_error)
- (void) XML_StopParser(ectx->parser, 0 /* resumable */);
-#endif
+ if (err)
+ svn_xml_signal_bailout(err, ectx->parser);
}
-#if XML_VERSION_AT_LEAST(1, 95, 8)
-static void
-expat_entity_declaration(void *userData,
- const XML_Char *entityName,
- int is_parameter_entity,
- const XML_Char *value,
- int value_length,
- const XML_Char *base,
- const XML_Char *systemId,
- const XML_Char *publicId,
- const XML_Char *notationName)
-{
- struct expat_ctx_t *ectx = userData;
-
- /* Stop the parser if an entity declaration is hit. */
- XML_StopParser(ectx->parser, 0 /* resumable */);
-}
-#else
-/* A noop default_handler. */
-static void
-expat_default_handler(void *userData, const XML_Char *s, int len)
-{
-}
-#endif
/* Implements svn_ra_serf__response_handler_t */
static svn_error_t *
@@ -1060,18 +969,8 @@ expat_response_handler(serf_request_t *request,
if (!ectx->parser)
{
- ectx->parser = XML_ParserCreate(NULL);
- apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
- xml_parser_cleanup, apr_pool_cleanup_null);
- XML_SetUserData(ectx->parser, ectx);
- XML_SetElementHandler(ectx->parser, expat_start, expat_end);
- XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
-
-#if XML_VERSION_AT_LEAST(1, 95, 8)
- XML_SetEntityDeclHandler(ectx->parser, expat_entity_declaration);
-#else
- XML_SetDefaultHandler(ectx->parser, expat_default_handler);
-#endif
+ ectx->parser = svn_xml_make_parser(ectx, expat_start, expat_end,
+ expat_cdata, ectx->cleanup_pool);
}
while (1)
@@ -1079,7 +978,6 @@ expat_response_handler(serf_request_t *request,
apr_status_t status;
const char *data;
apr_size_t len;
- svn_error_t *err;
svn_boolean_t at_eof = FALSE;
status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
@@ -1088,16 +986,7 @@ expat_response_handler(serf_request_t *request,
else if (APR_STATUS_IS_EOF(status))
at_eof = TRUE;
- err = parse_xml(ectx, data, len, at_eof /* isFinal */);
-
- if (at_eof || err)
- {
- /* Release xml parser state/tables. */
- apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
- xml_parser_cleanup);
- }
-
- SVN_ERR(err);
+ SVN_ERR(parse_xml(ectx, data, len, at_eof /* isFinal */));
/* The parsing went fine. What has the bucket told us? */
if (at_eof)