diff options
Diffstat (limited to 'subversion/libsvn_ra_serf')
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) |