Index: subversion/include/mod_authz_svn.h =================================================================== --- subversion/include/mod_authz_svn.h (revision 1729555) +++ subversion/include/mod_authz_svn.h (working copy) @@ -41,7 +41,7 @@ extern "C" { /** Provider name for subrequest bypass */ #define AUTHZ_SVN__SUBREQ_BYPASS_PROV_NAME "mod_authz_svn_subreq_bypass" /** Provider version for subrequest bypass */ -#define AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER "00.00a" +#define AUTHZ_SVN__SUBREQ_BYPASS_PROV_VER "01.00a" /** Provider to allow mod_dav_svn to bypass the generation of an apache * request when checking GET access from "mod_dav_svn/auth.c". * @@ -50,9 +50,11 @@ extern "C" { * * If the access is allowed returns @c OK or @c HTTP_FORBIDDEN if it is not. */ -typedef int (*authz_svn__subreq_bypass_func_t)(request_rec *r, - const char *repos_path, - const char *repos_name); +typedef int +(*authz_svn__subreq_bypass_func_t)(request_rec *r, + const char *repos_path, + const char *repos_name, + int required_access); #ifdef __cplusplus } Index: subversion/libsvn_ra_serf/commit.c =================================================================== --- subversion/libsvn_ra_serf/commit.c (revision 1729555) +++ subversion/libsvn_ra_serf/commit.c (working copy) @@ -72,6 +72,11 @@ typedef struct commit_context_t { const char *vcc_url; /* vcc url */ int open_batons; /* Number of open batons */ + + svn_ra_serf__request_body_t *body; + svn_stream_t *stream; + apr_array_header_t *svndiff_sizes; + svn_stringbuf_t *header; } commit_context_t; #define USING_HTTPV2_COMMIT_SUPPORT(commit_ctx) ((commit_ctx)->txn_url != NULL) @@ -170,12 +175,6 @@ typedef struct file_context_t { const char *copy_path; svn_revnum_t copy_revision; - /* Stream for collecting the svndiff. */ - svn_stream_t *stream; - - /* Buffer holding the svndiff (can spill to disk). */ - svn_ra_serf__request_body_t *svndiff; - /* Our base checksum as reported by the WC. */ const char *base_checksum; @@ -188,6 +187,8 @@ typedef struct file_context_t { /* URL to PUT the file at. */ const char *url; + apr_size_t svndiff_size; + } file_context_t; @@ -1406,74 +1407,83 @@ create_delete_body(serf_bucket_t **body_bkt, } static svn_error_t * -delete_entry(const char *path, - svn_revnum_t revision, - void *parent_baton, - apr_pool_t *pool) +write_bytes(svn_stream_t *stream, + const void *buf, + apr_size_t nbytes) { - dir_context_t *dir = parent_baton; - delete_context_t *delete_ctx; - svn_ra_serf__handler_t *handler; - const char *delete_target; - svn_error_t *err; + if (nbytes > 0) + { + apr_size_t len = nbytes; - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) - { - delete_target = svn_path_url_add_component2( - dir->commit_ctx->txn_root_url, - path, dir->pool); + SVN_ERR(svn_stream_write(stream, buf, &len)); } - else - { - /* Ensure our directory has been checked out */ - SVN_ERR(checkout_dir(dir, pool /* scratch_pool */)); - delete_target = svn_path_url_add_component2(dir->working_url, - svn_relpath_basename(path, - NULL), - pool); - } - /* DELETE our entry */ - delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx)); - delete_ctx->relpath = apr_pstrdup(pool, path); - delete_ctx->revision = revision; - delete_ctx->commit_ctx = dir->commit_ctx; + return SVN_NO_ERROR; +} - handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool); +static svn_error_t * +write_bool(svn_stream_t *stream, svn_boolean_t value) +{ + SVN_ERR(write_bytes(stream, value ? "\1" : "\0", 1)); - handler->response_handler = svn_ra_serf__expect_empty_body; - handler->response_baton = handler; + return SVN_NO_ERROR; +} - handler->header_delegate = setup_delete_headers; - handler->header_delegate_baton = delete_ctx; +static svn_error_t * +write_int32(svn_stream_t *stream, apr_int32_t value) +{ + SVN_ERR(write_bytes(stream, &value, sizeof(value))); - handler->method = "DELETE"; - handler->path = delete_target; + return SVN_NO_ERROR; +} - err = svn_ra_serf__context_run_one(handler, pool); - if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED - && handler->sline.code == 400) +static svn_error_t * +write_uint32(svn_stream_t *stream, apr_uint32_t value) +{ + SVN_ERR(write_bytes(stream, &value, sizeof(value))); + + return SVN_NO_ERROR; +} + +static svn_error_t * +write_uint64(svn_stream_t *stream, apr_uint64_t value) +{ + SVN_ERR(write_bytes(stream, &value, sizeof(value))); + + return SVN_NO_ERROR; +} + +static svn_error_t * +write_string(svn_stream_t *stream, const char *value) +{ + if (value) { - svn_error_clear(err); + apr_size_t len = strlen(value); - /* Try again with non-standard body to overcome Apache Httpd - header limit */ - delete_ctx->non_recursive_if = TRUE; - handler->body_type = "text/xml"; - handler->body_delegate = create_delete_body; - handler->body_delegate_baton = delete_ctx; - - SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); + SVN_ERR_ASSERT(len < APR_UINT32_MAX); + SVN_ERR(write_uint32(stream, (apr_uint32_t) len)); + SVN_ERR(write_bytes(stream, value, len)); } else - SVN_ERR(err); + { + SVN_ERR(write_uint32(stream, APR_UINT32_MAX)); + } - /* 204 No Content: item successfully deleted */ - if (handler->sline.code != 204) - return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + return SVN_NO_ERROR; +} - svn_hash_sets(dir->commit_ctx->deleted_entries, - apr_pstrdup(dir->commit_ctx->pool, path), (void *)1); +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t revision, + void *parent_baton, + apr_pool_t *pool) +{ + dir_context_t *dir = parent_baton; + svn_stream_t *stream = dir->commit_ctx->stream; + + SVN_ERR(write_string(stream, "delete")); + SVN_ERR(write_string(stream, path)); + SVN_ERR(write_int32(stream, revision)); return SVN_NO_ERROR; } @@ -1488,9 +1498,7 @@ add_directory(const char *path, { dir_context_t *parent = parent_baton; dir_context_t *dir; - svn_ra_serf__handler_t *handler; - apr_status_t status; - const char *mkcol_target; + svn_stream_t *stream = parent->commit_ctx->stream; dir = apr_pcalloc(dir_pool, sizeof(*dir)); @@ -1507,67 +1515,24 @@ add_directory(const char *path, dir->commit_ctx->open_batons++; - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) + if (copyfrom_path) { - dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url, - path, dir->pool); - mkcol_target = dir->url; - } - else - { - /* Ensure our parent is checked out. */ - SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */)); - - dir->url = svn_path_url_add_component2(parent->commit_ctx->checked_in_url, - dir->name, dir->pool); - mkcol_target = svn_path_url_add_component2( - parent->working_url, - dir->name, dir->pool); - } - - handler = svn_ra_serf__create_handler(dir->commit_ctx->session, dir->pool); - - handler->response_handler = svn_ra_serf__expect_empty_body; - handler->response_baton = handler; - if (!dir->copy_path) - { - handler->method = "MKCOL"; - handler->path = mkcol_target; - - handler->header_delegate = setup_add_dir_common_headers; - handler->header_delegate_baton = dir; - } - else - { apr_uri_t uri; - const char *req_url; + apr_status_t status; - status = apr_uri_parse(dir->pool, dir->copy_path, &uri); + status = apr_uri_parse(dir_pool, copyfrom_path, &uri); if (status) - { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Unable to parse URL '%s'"), - dir->copy_path); - } + return svn_ra_serf__wrap_err(status, NULL); - SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, - dir->commit_ctx->session, - uri.path, dir->copy_revision, - dir_pool, dir_pool)); - - handler->method = "COPY"; - handler->path = req_url; - - handler->header_delegate = setup_copy_dir_headers; - handler->header_delegate_baton = dir; + copyfrom_path = + svn_urlpath__skip_ancestor(dir->commit_ctx->session->repos_root.path, + uri.path); } - /* We have the same problem as with DELETE here: if there are too many - locks, the request fails. But in this case there is no way to retry - with a non-standard request. #### How to fix? */ - SVN_ERR(svn_ra_serf__context_run_one(handler, dir->pool)); - if (handler->sline.code != 201) - return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + SVN_ERR(write_string(stream, "add-dir")); + SVN_ERR(write_string(stream, path)); + SVN_ERR(write_string(stream, copyfrom_path)); + SVN_ERR(write_int32(stream, copyfrom_revision)); *child_baton = dir; @@ -1583,6 +1548,7 @@ open_directory(const char *path, { dir_context_t *parent = parent_baton; dir_context_t *dir; + svn_stream_t *stream = parent->commit_ctx->stream; dir = apr_pcalloc(dir_pool, sizeof(*dir)); @@ -1599,19 +1565,10 @@ open_directory(const char *path, 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, - path, dir->pool); - } - else - { - SVN_ERR(get_version_url(&dir->url, - dir->commit_ctx->session, - dir->relpath, dir->base_revision, - dir->commit_ctx->checked_in_url, - dir->pool, dir->pool /* scratch_pool */)); - } + SVN_ERR(write_string(stream, "open-dir")); + SVN_ERR(write_string(stream, path)); + SVN_ERR(write_int32(stream, base_revision)); + *child_baton = dir; return SVN_NO_ERROR; @@ -1624,21 +1581,22 @@ change_dir_prop(void *dir_baton, apr_pool_t *scratch_pool) { dir_context_t *dir = dir_baton; - svn_prop_t *prop; + svn_stream_t *stream = dir->commit_ctx->stream; - if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) + SVN_ERR(write_string(stream, "change-dir-prop")); + SVN_ERR(write_string(stream, dir->relpath)); + SVN_ERR(write_string(stream, name)); + if (value) { - /* Ensure we have a checked out dir. */ - SVN_ERR(checkout_dir(dir, scratch_pool)); + SVN_ERR(write_bool(stream, TRUE)); + SVN_ERR(write_uint64(stream, value->len)); + SVN_ERR(write_bytes(stream, value->data, value->len)); } + else + { + SVN_ERR(write_bool(stream, FALSE)); + } - prop = apr_palloc(dir->pool, sizeof(*prop)); - - prop->name = apr_pstrdup(dir->pool, name); - prop->value = svn_string_dup(value, dir->pool); - - svn_hash_sets(dir->prop_changes, prop->name, prop); - return SVN_NO_ERROR; } @@ -1652,31 +1610,6 @@ close_directory(void *dir_baton, * Therefore, just wave politely at our caller. */ - /* PROPPATCH our prop change and pass it along. */ - if (apr_hash_count(dir->prop_changes)) - { - proppatch_context_t *proppatch_ctx; - - proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx)); - proppatch_ctx->pool = pool; - proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */; - proppatch_ctx->relpath = dir->relpath; - proppatch_ctx->prop_changes = dir->prop_changes; - proppatch_ctx->base_revision = dir->base_revision; - - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) - { - proppatch_ctx->path = dir->url; - } - else - { - proppatch_ctx->path = dir->working_url; - } - - SVN_ERR(proppatch_resource(dir->commit_ctx->session, - proppatch_ctx, dir->pool)); - } - dir->commit_ctx->open_batons--; return SVN_NO_ERROR; @@ -1692,8 +1625,7 @@ add_file(const char *path, { dir_context_t *dir = parent_baton; file_context_t *new_file; - const char *deleted_parent = path; - apr_pool_t *scratch_pool = svn_pool_create(file_pool); + svn_stream_t *stream = dir->commit_ctx->stream; new_file = apr_pcalloc(file_pool, sizeof(*new_file)); new_file->pool = file_pool; @@ -1710,98 +1642,25 @@ add_file(const char *path, 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. */ - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) - { - new_file->url = svn_path_url_add_component2(dir->commit_ctx->txn_root_url, - path, new_file->pool); - } - else - { - /* Ensure our parent directory has been checked out */ - SVN_ERR(checkout_dir(dir, scratch_pool)); - - new_file->url = - svn_path_url_add_component2(dir->working_url, - new_file->name, new_file->pool); - } - - while (deleted_parent && deleted_parent[0] != '\0') - { - if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent)) - { - break; - } - deleted_parent = svn_relpath_dirname(deleted_parent, file_pool); - } - if (copy_path) { - svn_ra_serf__handler_t *handler; apr_uri_t uri; - const char *req_url; apr_status_t status; - /* Create the copy directly as cheap 'does exist/out of date' - check. We update the copy (if needed) from close_file() */ - - status = apr_uri_parse(scratch_pool, copy_path, &uri); + status = apr_uri_parse(file_pool, copy_path, &uri); if (status) return svn_ra_serf__wrap_err(status, NULL); - SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, - dir->commit_ctx->session, - uri.path, copy_revision, - scratch_pool, scratch_pool)); - - handler = svn_ra_serf__create_handler(dir->commit_ctx->session, - scratch_pool); - handler->method = "COPY"; - handler->path = req_url; - - handler->response_handler = svn_ra_serf__expect_empty_body; - handler->response_baton = handler; - - handler->header_delegate = setup_copy_file_headers; - handler->header_delegate_baton = new_file; - - SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); - - if (handler->sline.code != 201) - return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + copy_path = + svn_urlpath__skip_ancestor(dir->commit_ctx->session->repos_root.path, + uri.path); } - else if (! ((dir->added && !dir->copy_path) || - (deleted_parent && deleted_parent[0] != '\0'))) - { - svn_ra_serf__handler_t *handler; - svn_error_t *err; - handler = svn_ra_serf__create_handler(dir->commit_ctx->session, - scratch_pool); - handler->method = "HEAD"; - handler->path = svn_path_url_add_component2( - dir->commit_ctx->session->session_url.path, - path, scratch_pool); - handler->response_handler = svn_ra_serf__expect_empty_body; - handler->response_baton = handler; - handler->no_dav_headers = TRUE; /* Read only operation outside txn */ + SVN_ERR(write_string(stream, "add-file")); + SVN_ERR(write_string(stream, path)); + SVN_ERR(write_string(stream, copy_path)); + SVN_ERR(write_int32(stream, copy_revision)); - err = svn_ra_serf__context_run_one(handler, scratch_pool); - - if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) - { - svn_error_clear(err); /* Great. We can create a new file! */ - } - else if (err) - return svn_error_trace(err); - else - return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, - _("File '%s' already exists"), path); - } - - svn_pool_destroy(scratch_pool); *file_baton = new_file; return SVN_NO_ERROR; @@ -1816,6 +1675,7 @@ open_file(const char *path, { dir_context_t *parent = parent_baton; file_context_t *new_file; + svn_stream_t *stream = parent->commit_ctx->stream; new_file = apr_pcalloc(file_pool, sizeof(*new_file)); new_file->pool = file_pool; @@ -1830,25 +1690,35 @@ open_file(const char *path, 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, - path, new_file->pool); - } - else - { - /* CHECKOUT the file into our activity. */ - SVN_ERR(checkout_file(new_file, new_file->pool /* scratch_pool */)); + SVN_ERR(write_string(stream, "open-file")); + SVN_ERR(write_string(stream, path)); + SVN_ERR(write_int32(stream, base_revision)); - new_file->url = new_file->working_url; - } - *file_baton = new_file; return SVN_NO_ERROR; } +typedef struct counting_stream_baton_t +{ + svn_stream_t *stream; + apr_size_t *count; +} counting_stream_baton_t; + static svn_error_t * +write_fn(void *baton, + const char *data, + apr_size_t *len) +{ + counting_stream_baton_t *b = baton; + + SVN_ERR(svn_stream_write(b->stream, data, len)); + *b->count += *len; + + return SVN_NO_ERROR; +} + +static svn_error_t * apply_textdelta(void *file_baton, const char *base_checksum, apr_pool_t *pool, @@ -1858,30 +1728,9 @@ apply_textdelta(void *file_baton, file_context_t *ctx = file_baton; int svndiff_version; int compression_level; + counting_stream_baton_t *baton; + svn_stream_t *stream; - /* 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 - * possibly 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. - * - * BH: If you wait to a specific event... why not use that event to - * trigger the operation? - * Having a request (body) bucket return EAGAIN until done stalls - * the entire HTTP pipeline after writing the first part of the - * request. It is not like we can interrupt some part of a request - * and continue later. Or somebody else must use tempfiles and - * always assume that clients work this bad... as it only knows - * for sure after the request is completely available. - */ - - 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); - if (ctx->commit_ctx->session->supports_svndiff1 && ctx->commit_ctx->session->using_compression) { @@ -1905,14 +1754,22 @@ apply_textdelta(void *file_baton, compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE; } - /* Disown the stream; we'll close it explicitly in close_file(). */ - svn_txdelta_to_svndiff3(handler, handler_baton, - svn_stream_disown(ctx->stream, pool), + baton = apr_pcalloc(pool, sizeof(*baton)); + baton->stream = svn_stream_disown(ctx->commit_ctx->stream, pool); + baton->count = &ctx->svndiff_size; + stream = svn_stream_create(baton, pool); + svn_stream_set_write(stream, write_fn); + + svn_txdelta_to_svndiff3(handler, handler_baton, stream, svndiff_version, compression_level, pool); if (base_checksum) ctx->base_checksum = apr_pstrdup(ctx->pool, base_checksum); + SVN_ERR(write_string(ctx->commit_ctx->stream, "apply-textdelta")); + SVN_ERR(write_string(ctx->commit_ctx->stream, ctx->relpath)); + SVN_ERR(write_string(ctx->commit_ctx->stream, base_checksum)); + return SVN_NO_ERROR; } @@ -1923,15 +1780,22 @@ change_file_prop(void *file_baton, apr_pool_t *pool) { file_context_t *file = file_baton; - svn_prop_t *prop; + svn_stream_t *stream = file->commit_ctx->stream; - prop = apr_palloc(file->pool, sizeof(*prop)); + SVN_ERR(write_string(stream, "change-file-prop")); + SVN_ERR(write_string(stream, file->relpath)); + SVN_ERR(write_string(stream, name)); + if (value) + { + SVN_ERR(write_bool(stream, TRUE)); + SVN_ERR(write_uint64(stream, value->len)); + SVN_ERR(write_bytes(stream, value->data, value->len)); + } + else + { + SVN_ERR(write_bool(stream, FALSE)); + } - prop->name = apr_pstrdup(file->pool, name); - prop->value = svn_string_dup(value, file->pool); - - svn_hash_sets(file->prop_changes, prop->name, prop); - return SVN_NO_ERROR; } @@ -1941,84 +1805,47 @@ close_file(void *file_baton, apr_pool_t *scratch_pool) { file_context_t *ctx = file_baton; - svn_boolean_t put_empty_file = FALSE; + svn_stream_t *stream = ctx->commit_ctx->stream; ctx->result_checksum = text_checksum; - /* If we got no stream of changes, but this is an added-without-history - * file, make a note that we'll be PUTting a zero-byte file to the server. - */ - if ((!ctx->svndiff) && ctx->added && (!ctx->copy_path)) - put_empty_file = TRUE; + ctx->commit_ctx->open_batons--; - /* If we had a stream of changes, push them to the server... */ - if (ctx->svndiff || put_empty_file) - { - svn_ra_serf__handler_t *handler; - int expected_result; + SVN_ERR(write_string(stream, "close-file")); + SVN_ERR(write_string(stream, ctx->relpath)); + SVN_ERR(write_string(stream, text_checksum)); - handler = svn_ra_serf__create_handler(ctx->commit_ctx->session, - scratch_pool); + if (ctx->svndiff_size) + APR_ARRAY_PUSH(ctx->commit_ctx->svndiff_sizes, + apr_size_t) = ctx->svndiff_size; - handler->method = "PUT"; - handler->path = ctx->url; + return SVN_NO_ERROR; +} - handler->response_handler = svn_ra_serf__expect_empty_body; - handler->response_baton = handler; +/* Implements svn_ra_serf__request_body_delegate_t */ +static svn_error_t * +create_replay_txn_body(serf_bucket_t **body_bkt, + void *baton, + serf_bucket_alloc_t *alloc, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) +{ + commit_context_t *ctx = baton; + serf_bucket_t *body = serf_bucket_aggregate_create(alloc); + serf_bucket_t *tmp; + svn_ra_serf__request_body_delegate_t delegate; + void *delegate_baton; - if (put_empty_file) - { - handler->body_delegate = create_empty_put_body; - handler->body_delegate_baton = ctx; - handler->body_type = "text/plain"; - } - else - { - SVN_ERR(svn_stream_close(ctx->stream)); + tmp = SERF_BUCKET_SIMPLE_STRING_LEN(ctx->header->data, + ctx->header->len, alloc); + serf_bucket_aggregate_append(body, tmp); - svn_ra_serf__request_body_get_delegate(&handler->body_delegate, - &handler->body_delegate_baton, - ctx->svndiff); - handler->body_type = SVN_SVNDIFF_MIME_TYPE; - } + svn_ra_serf__request_body_get_delegate(&delegate, &delegate_baton, + ctx->body); + SVN_ERR(delegate(&tmp, delegate_baton, alloc, pool, scratch_pool)); + serf_bucket_aggregate_append(body, tmp); - handler->header_delegate = setup_put_headers; - handler->header_delegate_baton = ctx; - - SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); - - 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)); - } - - /* Don't keep open file handles longer than necessary. */ - if (ctx->svndiff) - 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)) - { - proppatch_context_t *proppatch; - - proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch)); - proppatch->pool = scratch_pool; - proppatch->relpath = ctx->relpath; - proppatch->path = ctx->url; - proppatch->commit_ctx = ctx->commit_ctx; - proppatch->prop_changes = ctx->prop_changes; - proppatch->base_revision = ctx->base_revision; - - SVN_ERR(proppatch_resource(ctx->commit_ctx->session, - proppatch, scratch_pool)); - } - - ctx->commit_ctx->open_batons--; - + *body_bkt = body; return SVN_NO_ERROR; } @@ -2031,6 +1858,9 @@ close_edit(void *edit_baton, ctx->activity_url ? ctx->activity_url : ctx->txn_url; const svn_commit_info_t *commit_info; svn_error_t *err = NULL; + svn_ra_serf__handler_t *handler; + svn_stream_t *header_stream; + const char *relpath; if (ctx->open_batons > 0) return svn_error_create( @@ -2037,6 +1867,67 @@ close_edit(void *edit_baton, SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION, NULL, _("Closing editor with directories or files open")); + SVN_ERR(write_string(ctx->stream, "close-edit")); + SVN_ERR(svn_stream_close(ctx->stream)); + + SVN_ERR(svn_ra_serf__get_relative_path(&relpath, + ctx->session->session_url.path, + ctx->session, pool)); + + header_stream = svn_stream_from_stringbuf(ctx->header, pool); + SVN_ERR(write_string(header_stream, "header")); + SVN_ERR(write_string(header_stream, relpath)); + SVN_ERR(write_int32(header_stream, ctx->svndiff_sizes->nelts)); + + if (ctx->svndiff_sizes->nelts > 0) + { + int i; + + for (i = 0; i < ctx->svndiff_sizes->nelts; i++) + { + apr_size_t size = APR_ARRAY_IDX(ctx->svndiff_sizes, i, apr_size_t); + + SVN_ERR(write_uint64(header_stream, size)); + } + } + + if (ctx->lock_tokens) + { + apr_hash_index_t *hi; + + SVN_ERR(write_uint32(header_stream, apr_hash_count(ctx->lock_tokens))); + + for (hi = apr_hash_first(pool, ctx->lock_tokens); hi; + hi = apr_hash_next(hi)) + { + const char *relpath = apr_hash_this_key(hi); + const char *token = apr_hash_this_val(hi); + + SVN_ERR(write_string(header_stream, relpath)); + SVN_ERR(write_string(header_stream, token)); + } + } + else + { + SVN_ERR(write_uint32(header_stream, 0)); + } + + SVN_ERR(svn_stream_close(header_stream)); + + /* POST everything -- the skel and every svndiff. */ + handler = svn_ra_serf__create_handler(ctx->session, pool); + handler->method = "POST"; + handler->path = ctx->txn_url; + handler->response_handler = svn_ra_serf__expect_empty_body; + handler->response_baton = handler; + handler->body_delegate = create_replay_txn_body; + handler->body_delegate_baton = ctx; + + SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); + + if (handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + /* MERGE our activity */ SVN_ERR(svn_ra_serf__run_merge(&commit_info, ctx->session, @@ -2154,6 +2045,16 @@ svn_ra_serf__get_commit_editor(svn_ra_session_t *r ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool); + ctx->body = + svn_ra_serf__request_body_create(SVN_RA_SERF__REQUEST_BODY_IN_MEM_SIZE, + pool); + + ctx->stream = svn_ra_serf__request_body_get_stream(ctx->body); + + ctx->svndiff_sizes = apr_array_make(pool, 0, sizeof(apr_size_t)); + + ctx->header = svn_stringbuf_create_ensure(64, pool); + /* If the server supports ephemeral properties, add some carrying interesting version information. */ SVN_ERR(svn_ra_serf__has_capability(ra_session, &supports_ephemeral_props, Index: subversion/mod_authz_svn/mod_authz_svn.c =================================================================== --- subversion/mod_authz_svn/mod_authz_svn.c (revision 1729555) +++ subversion/mod_authz_svn/mod_authz_svn.c (working copy) @@ -783,6 +783,7 @@ static int subreq_bypass2(request_rec *r, const char *repos_path, const char *repos_name, + svn_repos_authz_access_t required_access, apr_pool_t *scratch_pool) { svn_error_t *svn_err = NULL; @@ -816,7 +817,7 @@ subreq_bypass2(request_rec *r, svn_err = svn_repos_authz_check_access(access_conf, repos_name, repos_path, username_to_authorize, - svn_authz_none|svn_authz_read, + required_access, &authz_access_granted, scratch_pool); if (svn_err) @@ -846,13 +847,15 @@ subreq_bypass2(request_rec *r, static int subreq_bypass(request_rec *r, const char *repos_path, - const char *repos_name) + const char *repos_name, + svn_repos_authz_access_t required_access) { int status; apr_pool_t *scratch_pool; scratch_pool = svn_pool_create(r->pool); - status = subreq_bypass2(r, repos_path, repos_name, scratch_pool); + status = subreq_bypass2(r, repos_path, repos_name, required_access, + scratch_pool); svn_pool_destroy(scratch_pool); return status; Index: subversion/mod_dav_svn/authz.c =================================================================== --- subversion/mod_dav_svn/authz.c (revision 1729555) +++ subversion/mod_dav_svn/authz.c (working copy) @@ -67,7 +67,8 @@ dav_svn__allow_read(request_rec *r, allow_read_bypass = dav_svn__get_pathauthz_bypass(r); if (allow_read_bypass != NULL) { - if (allow_read_bypass(r, path, repos->repo_basename) == OK) + if (allow_read_bypass(r, path, repos->repo_basename, + svn_authz_none|svn_authz_read) == OK) return TRUE; else return FALSE; @@ -243,3 +244,44 @@ dav_svn__allow_read_resource(const dav_resource *r return dav_svn__allow_read(resource->info->r, resource->info->repos, resource->info->repos_path, rev, pool); } + +static svn_error_t * +authz_callback(svn_repos_authz_access_t required, + svn_boolean_t *allowed, + svn_fs_root_t *root, + const char *path, + void *baton, + apr_pool_t *pool) +{ + dav_svn__authz_callback_baton *acb = baton; + authz_svn__subreq_bypass_func_t bypass_func = NULL; + + if (path && path[0] != '/') + path = apr_pstrcat(pool, "/", path, SVN_VA_NULL); + + bypass_func = dav_svn__get_pathauthz_bypass(acb->r); + if (bypass_func != NULL) + { + if (bypass_func(acb->r, path, acb->repos->repo_basename, + required) == OK) + *allowed = TRUE; + else + *allowed = FALSE; + } + else + { + /* ### Not implemented. */ + *allowed = TRUE; + } + + return SVN_NO_ERROR; +} + +svn_repos_authz_callback_t +dav_svn__authz_callback(dav_svn__authz_callback_baton *baton) +{ + if (! dav_svn__get_pathauthz_flag(baton->r)) + return NULL; + + return authz_callback; +} Index: subversion/mod_dav_svn/dav_svn.h =================================================================== --- subversion/mod_dav_svn/dav_svn.h (revision 1729555) +++ subversion/mod_dav_svn/dav_svn.h (working copy) @@ -749,6 +749,15 @@ typedef struct dav_svn__authz_read_baton } dav_svn__authz_read_baton; +typedef struct dav_svn__authz_callback_baton +{ + request_rec *r; + + const dav_svn_repos *repos; + +} dav_svn__authz_callback_baton; + + /* Return TRUE iff the current user (as determined by Apache's authentication system) has permission to read PATH in REPOS at REV (where an invalid REV means "HEAD"). This will invoke any authz @@ -795,7 +804,10 @@ dav_svn__allow_list_repos(request_rec *r, svn_repos_authz_func_t dav_svn__authz_read_func(dav_svn__authz_read_baton *baton); +svn_repos_authz_callback_t +dav_svn__authz_callback(dav_svn__authz_callback_baton *baton); + /*** util.c ***/ /* A wrapper around mod_dav's dav_new_error_tag, mod_dav_svn uses this Index: subversion/mod_dav_svn/repos.c =================================================================== --- subversion/mod_dav_svn/repos.c (revision 1729555) +++ subversion/mod_dav_svn/repos.c (working copy) @@ -4705,7 +4705,416 @@ handle_err(request_rec *r, dav_error *err) return err->status; } +static svn_error_t * +read_bytes(ap_filter_t *filter, + apr_bucket_brigade *bb, + void *buf, + apr_size_t nbytes) +{ + apr_size_t total_len = 0; + apr_status_t status; + apr_bucket *bucket; + svn_boolean_t seen_eos = FALSE; + while (!seen_eos && total_len < nbytes) + { + status = ap_get_brigade(filter, bb, AP_MODE_READBYTES, + APR_BLOCK_READ, nbytes - total_len); + if (status) + return svn_error_wrap_apr(status, NULL); + + for (bucket = APR_BRIGADE_FIRST(bb); + bucket != APR_BRIGADE_SENTINEL(bb); + bucket = APR_BUCKET_NEXT(bucket)) + { + const char *data; + apr_size_t len; + + if (APR_BUCKET_IS_EOS(bucket)) + { + seen_eos = TRUE; + break; + } + + if (APR_BUCKET_IS_METADATA(bucket)) + continue; + + status = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); + if (status) + return svn_error_wrap_apr(status, NULL); + + memcpy((char *) buf + total_len, data, len); + total_len += len; + } + + status = apr_brigade_cleanup(bb); + if (status) + return svn_error_wrap_apr(status, NULL); + } + + if (total_len < nbytes) + return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, NULL); + + return SVN_NO_ERROR; +} + +static svn_error_t * +read_bool(svn_boolean_t *value_p, + ap_filter_t *filter, + apr_bucket_brigade *bb) +{ + char c; + + SVN_ERR(read_bytes(filter, bb, &c, 1)); + + if (c == 0) + *value_p = FALSE; + else if (c == 1) + *value_p = TRUE; + else + return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL, NULL); + + return SVN_NO_ERROR; +} + +static svn_error_t * +read_int32(apr_int32_t *value_p, + ap_filter_t *filter, + apr_bucket_brigade *bb) +{ + SVN_ERR(read_bytes(filter, bb, value_p, sizeof(apr_int32_t))); + + return SVN_NO_ERROR; +} + +static svn_error_t * +read_uint32(apr_uint32_t *value_p, + ap_filter_t *filter, + apr_bucket_brigade *bb) +{ + SVN_ERR(read_bytes(filter, bb, value_p, sizeof(apr_uint32_t))); + + return SVN_NO_ERROR; +} + +static svn_error_t * +read_uint64(apr_uint64_t *value_p, + ap_filter_t *filter, + apr_bucket_brigade *bb) +{ + SVN_ERR(read_bytes(filter, bb, value_p, sizeof(apr_uint64_t))); + + return SVN_NO_ERROR; +} + +svn_error_t * +read_string(const char **value_p, + ap_filter_t *filter, + apr_bucket_brigade *bb, + apr_pool_t *pool) +{ + char *value; + apr_uint32_t len; + + SVN_ERR(read_uint32(&len, filter, bb)); + + if (len == APR_UINT32_MAX) + value = NULL; + else + { + /* ### Need a reasonable limit here. */ + value = apr_palloc(pool, len + 1); + SVN_ERR(read_bytes(filter, bb, value, len)); + value[len] = '\0'; + } + + *value_p = value; + return SVN_NO_ERROR; +} + +static svn_error_t * +replay_txn(const dav_resource *resource, + request_rec *r, + apr_bucket_brigade *bb, + apr_pool_t *pool) +{ + const svn_delta_editor_t *editor; + void *edit_baton; + apr_hash_t *batons; + void *root_baton; + apr_array_header_t *svndiff_sizes; + int svndiff_index; + dav_svn__authz_callback_baton acb = {0}; + apr_pool_t *iterpool; + + editor = NULL; + edit_baton = NULL; + batons = apr_hash_make(pool); + root_baton = NULL; + svndiff_sizes = NULL; + svndiff_index = 0; + + acb.r = resource->info->r; + acb.repos = resource->info->repos; + + iterpool = svn_pool_create(pool); + while (1) + { + const char *s; + + svn_pool_clear(iterpool); + SVN_ERR(read_string(&s, r->input_filters, bb, iterpool)); + + if (strcmp(s, "header") == 0) + { + const char *base_path; + apr_int32_t base_rev; + apr_int32_t len; + apr_uint32_t ulen; + + SVN_ERR(read_string(&base_path, r->input_filters, bb, iterpool)); + SVN_ERR(read_int32(&len, r->input_filters, bb)); + svndiff_sizes = apr_array_make(pool, len, sizeof(size_t)); + if (len > 0) + { + int i; + + for (i = 0; i < len; i++) + { + apr_uint64_t size; + + SVN_ERR(read_uint64(&size, r->input_filters, bb)); + APR_ARRAY_IDX(svndiff_sizes, i, size_t) = (size_t) size; + } + } + + SVN_ERR(read_uint32(&ulen, r->input_filters, bb)); + if (ulen > 0) + { + svn_fs_access_t *access_ctx; + apr_uint32_t i; + + SVN_ERR(svn_fs_get_access(&access_ctx, + resource->info->repos->fs)); + + if (!access_ctx) + return svn_error_create(SVN_ERR_FS_LOCK_OWNER_MISMATCH, + NULL, NULL); + + for (i = 0; i < ulen; i++) + { + const char *path; + const char *token; + + SVN_ERR(read_string(&path, r->input_filters, bb, pool)); + SVN_ERR(read_string(&token, r->input_filters, bb, pool)); + SVN_ERR(svn_fs_access_add_lock_token2(access_ctx, + path, token)); + } + } + + SVN_ERR(svn_repos_get_commit_editor5( + &editor, &edit_baton, + resource->info->repos->repos, + resource->info->root.txn, "", + base_path, apr_hash_make(pool), + NULL, NULL, + dav_svn__authz_callback(&acb), &acb, + pool)); + + base_rev = svn_fs_txn_base_revision(resource->info->root.txn); + SVN_ERR(editor->open_root(edit_baton, base_rev, pool, &root_baton)); + svn_hash_sets(batons, "", root_baton); + } + else if (strcmp(s, "add-file") == 0) + { + const char *path; + const char *copyfrom_path; + apr_int32_t copyfrom_rev; + void *file_baton; + + SVN_ERR(read_string(&path, r->input_filters, bb, pool)); + SVN_ERR(read_string(©from_path, r->input_filters, bb, iterpool)); + SVN_ERR(read_int32(©from_rev, r->input_filters, bb)); + + SVN_ERR(editor->add_file(path, root_baton, copyfrom_path, + copyfrom_rev, pool, &file_baton)); + svn_hash_sets(batons, path, file_baton); + } + else if (strcmp(s, "add-dir") == 0) + { + const char *path; + const char *copyfrom_path; + apr_int32_t copyfrom_rev; + void *dir_baton; + + SVN_ERR(read_string(&path, r->input_filters, bb, pool)); + SVN_ERR(read_string(©from_path, r->input_filters, bb, + iterpool)); + SVN_ERR(read_int32(©from_rev, r->input_filters, bb)); + + SVN_ERR(editor->add_directory(path, root_baton, copyfrom_path, + copyfrom_rev, pool, &dir_baton)); + svn_hash_sets(batons, path, dir_baton); + } + else if (strcmp(s, "open-file") == 0) + { + const char *path; + apr_int32_t base_rev; + const char *parent_path; + void *file_baton; + + SVN_ERR(read_string(&path, r->input_filters, bb, pool)); + SVN_ERR(read_int32(&base_rev, r->input_filters, bb)); + + parent_path = svn_relpath_dirname(path, iterpool); + SVN_ERR(editor->open_file(path, + svn_hash_gets(batons, parent_path), + base_rev, pool, + &file_baton)); + svn_hash_sets(batons, path, file_baton); + } + else if (strcmp(s, "open-dir") == 0) + { + const char *path; + apr_int32_t base_rev; + const char *parent_path; + void *dir_baton; + + SVN_ERR(read_string(&path, r->input_filters, bb, pool)); + SVN_ERR(read_int32(&base_rev, r->input_filters, bb)); + + parent_path = svn_relpath_dirname(path, iterpool); + SVN_ERR(editor->open_directory(path, + svn_hash_gets(batons, parent_path), + base_rev, pool, + &dir_baton)); + svn_hash_sets(batons, path, dir_baton); + } + else if (strcmp(s, "delete") == 0) + { + const char *path; + apr_int32_t rev; + const char *parent_path; + + SVN_ERR(read_string(&path, r->input_filters, bb, iterpool)); + SVN_ERR(read_int32(&rev, r->input_filters, bb)); + + parent_path = svn_relpath_dirname(path, iterpool); + SVN_ERR(editor->delete_entry(path, rev, + svn_hash_gets(batons, parent_path), + iterpool)); + } + else if (strcmp(s, "apply-textdelta") == 0) + { + const char *path; + const char *base_checksum; + svn_txdelta_window_handler_t wh; + void *wb; + svn_stream_t *svndiff_parser; + apr_size_t svndiff_len; + apr_size_t remaining; + char buf[APR_BUCKET_BUFF_SIZE]; + + SVN_ERR(read_string(&path, r->input_filters, bb, iterpool)); + SVN_ERR(read_string(&base_checksum, r->input_filters, bb, + iterpool)); + + SVN_ERR(editor->apply_textdelta(svn_hash_gets(batons, path), + base_checksum, iterpool, &wh, &wb)); + svndiff_parser = svn_txdelta_parse_svndiff(wh, wb, TRUE, iterpool); + svndiff_len = APR_ARRAY_IDX(svndiff_sizes, svndiff_index, size_t); + remaining = svndiff_len; + + while (remaining > 0) + { + apr_size_t n = MIN(remaining, sizeof(buf)); + + SVN_ERR(read_bytes(r->input_filters, bb, buf, n)); + SVN_ERR(svn_stream_write(svndiff_parser, buf, &n)); + remaining -= n; + } + + SVN_ERR(svn_stream_close(svndiff_parser)); + svndiff_index++; + } + else if (strcmp(s, "change-file-prop") == 0) + { + const char *path; + const char *propname; + svn_boolean_t has_propval; + svn_string_t *propval; + + SVN_ERR(read_string(&path, r->input_filters, bb, iterpool)); + SVN_ERR(read_string(&propname, r->input_filters, bb, iterpool)); + SVN_ERR(read_bool(&has_propval, r->input_filters, bb)); + if (has_propval) + { + apr_uint64_t len; + char *val; + + SVN_ERR(read_uint64(&len, r->input_filters, bb)); + val = apr_palloc(iterpool, len); + SVN_ERR(read_bytes(r->input_filters, bb, val, len)); + propval = svn_string_ncreate(val, len, iterpool); + } + else + { + propval = NULL; + } + + SVN_ERR(editor->change_file_prop(svn_hash_gets(batons, path), + propname, propval, iterpool)); + } + else if (strcmp(s, "change-dir-prop") == 0) + { + const char *path; + const char *propname; + svn_boolean_t has_propval; + svn_string_t *propval; + + SVN_ERR(read_string(&path, r->input_filters, bb, iterpool)); + SVN_ERR(read_string(&propname, r->input_filters, bb, iterpool)); + SVN_ERR(read_bool(&has_propval, r->input_filters, bb)); + if (has_propval) + { + apr_uint64_t len; + char *val; + + SVN_ERR(read_uint64(&len, r->input_filters, bb)); + val = apr_palloc(iterpool, len); + SVN_ERR(read_bytes(r->input_filters, bb, val, len)); + propval = svn_string_ncreate(val, len, iterpool); + } + else + { + propval = NULL; + } + + SVN_ERR(editor->change_dir_prop(svn_hash_gets(batons, path), + propname, propval, iterpool)); + } + else if (strcmp(s, "close-file") == 0) + { + const char *path; + const char *result_checksum; + + SVN_ERR(read_string(&path, r->input_filters, bb, iterpool)); + SVN_ERR(read_string(&result_checksum, r->input_filters, bb, + iterpool)); + SVN_ERR(editor->close_file(svn_hash_gets(batons, path), + result_checksum, iterpool)); + } + else if (strcmp(s, "close-edit") == 0) + break; + else + return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL, NULL); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + int dav_svn__method_post(request_rec *r) { dav_resource *resource; @@ -4712,25 +5121,56 @@ int dav_svn__method_post(request_rec *r) dav_error *derr; const char *content_type; - /* We only allow POSTs against the "me resource" right now. */ derr = get_resource(r, dav_svn__get_root_dir(r), "ignored", 0, &resource); if (derr != NULL) return derr->status; - if (resource->info->restype != DAV_SVN_RESTYPE_ME) - return HTTP_BAD_REQUEST; - /* Pass skel-type POST request handling off to a dispatcher; any - other type of request is considered bogus. */ - content_type = apr_table_get(r->headers_in, "content-type"); - if (content_type && (strcmp(content_type, SVN_SKEL_MIME_TYPE) == 0)) + if (resource->info->restype == DAV_SVN_RESTYPE_ME) { - derr = handle_post_request(r, resource, r->output_filters); + /* Pass skel-type POST request handling off to a dispatcher; any + other type of request is considered bogus. */ + content_type = apr_table_get(r->headers_in, "content-type"); + if (content_type && (strcmp(content_type, SVN_SKEL_MIME_TYPE) == 0)) + { + derr = handle_post_request(r, resource, r->output_filters); + } + else + { + derr = dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0, 0, + "Unsupported POST request type."); + } } + else if (resource->info->restype == DAV_SVN_RESTYPE_TXN_COLLECTION) + { + apr_bucket_brigade *bb; + svn_error_t *err; + + bb = apr_brigade_create(resource->pool, r->connection->bucket_alloc); + err = replay_txn(resource, r, bb, resource->pool); + apr_brigade_destroy(bb); + + if (err && err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE) + { + derr = dav_svn__convert_err(err, HTTP_CONFLICT, + "Attempting to modify out-of-date " + "resource.", resource->pool); + } + else if (err && (err->apr_err == SVN_ERR_STREAM_MALFORMED_DATA || + err->apr_err == SVN_ERR_STREAM_UNEXPECTED_EOF)) + { + derr = dav_svn__convert_err(err, HTTP_BAD_REQUEST, NULL, + resource->pool); + } + else if (err) + { + derr = dav_svn__convert_err(err, HTTP_INTERNAL_SERVER_ERROR, NULL, + resource->pool); + } + } else { - derr = dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, 0, 0, - "Unsupported POST request type."); + return HTTP_BAD_REQUEST; } /* If something went wrong above, we'll generate a response back to