diff options
Diffstat (limited to 'subversion/libsvn_ra_serf')
31 files changed, 7715 insertions, 7431 deletions
diff --git a/subversion/libsvn_ra_serf/README b/subversion/libsvn_ra_serf/README index d3baf33..98a48a6 100644 --- a/subversion/libsvn_ra_serf/README +++ b/subversion/libsvn_ra_serf/README @@ -22,7 +22,7 @@ support for ra_serf: For more about how ra_serf/ra_neon talk WebDAV, consult notes/webdav-protocol. -Working copies are interchangable between ra_serf and ra_neon. (They both use +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 diff --git a/subversion/libsvn_ra_serf/blame.c b/subversion/libsvn_ra_serf/blame.c index b6f136a..d915a19 100644 --- a/subversion/libsvn_ra_serf/blame.c +++ b/subversion/libsvn_ra_serf/blame.c @@ -47,7 +47,7 @@ * This enum represents the current state of our XML parsing for a REPORT. */ typedef enum blame_state_e { - INITIAL = 0, + INITIAL = XML_STATE_INITIAL, FILE_REVS_REPORT, FILE_REV, REV_PROP, @@ -111,7 +111,6 @@ static const svn_ra_serf__xml_transition_t blame_ttable[] = { { 0 } }; - /* Conforms to svn_ra_serf__xml_opened_t */ static svn_error_t * blame_opened(svn_ra_serf__xml_estate_t *xes, @@ -140,17 +139,20 @@ blame_opened(svn_ra_serf__xml_estate_t *xes, apr_pool_t *state_pool = svn_ra_serf__xml_state_pool(xes); apr_hash_t *gathered = svn_ra_serf__xml_gather_since(xes, FILE_REV); const char *path; - const char *rev; + const char *rev_str; const char *merged_revision; svn_txdelta_window_handler_t txdelta; void *txdelta_baton; + apr_int64_t rev; path = svn_hash_gets(gathered, "path"); - rev = svn_hash_gets(gathered, "rev"); + rev_str = svn_hash_gets(gathered, "rev"); + + SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); merged_revision = svn_hash_gets(gathered, "merged-revision"); SVN_ERR(blame_ctx->file_rev(blame_ctx->file_rev_baton, - path, SVN_STR_TO_REV(rev), + path, (svn_revnum_t)rev, blame_ctx->rev_props, merged_revision != NULL, &txdelta, &txdelta_baton, @@ -278,7 +280,8 @@ static svn_error_t * create_file_revs_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { serf_bucket_t *buckets; blame_context_t *blame_ctx = baton; @@ -288,7 +291,7 @@ create_file_revs_body(serf_bucket_t **body_bkt, svn_ra_serf__add_open_tag_buckets(buckets, alloc, "S:file-revs-report", "xmlns:S", SVN_XML_NAMESPACE, - NULL); + SVN_VA_NULL); svn_ra_serf__add_tag_buckets(buckets, "S:start-revision", apr_ltoa(pool, blame_ctx->start), @@ -300,9 +303,8 @@ create_file_revs_body(serf_bucket_t **body_bkt, if (blame_ctx->include_merged_revisions) { - svn_ra_serf__add_tag_buckets(buckets, - "S:include-merged-revisions", NULL, - alloc); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, + "S:include-merged-revisions", SVN_VA_NULL); } svn_ra_serf__add_tag_buckets(buckets, @@ -331,7 +333,7 @@ svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session, svn_ra_serf__handler_t *handler; svn_ra_serf__xml_context_t *xmlctx; const char *req_url; - svn_error_t *err; + svn_revnum_t peg_rev; blame_ctx = apr_pcalloc(pool, sizeof(*blame_ctx)); blame_ctx->pool = pool; @@ -342,9 +344,16 @@ svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session, blame_ctx->end = end; blame_ctx->include_merged_revisions = include_merged_revisions; + /* Since Subversion 1.8 we allow retrieving blames backwards. So we can't + just unconditionally use end_rev as the peg revision as before */ + if (end > start) + peg_rev = end; + else + peg_rev = start; + SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, - session, NULL /* conn */, - NULL /* url */, end, + session, + NULL /* url */, peg_rev, pool, pool)); xmlctx = svn_ra_serf__xml_context_create(blame_ttable, @@ -353,23 +362,18 @@ svn_ra_serf__get_file_revs(svn_ra_session_t *ra_session, blame_cdata, blame_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); handler->method = "REPORT"; handler->path = req_url; handler->body_type = "text/xml"; handler->body_delegate = create_file_revs_body; handler->body_delegate_baton = blame_ctx; - handler->conn = session->conns[0]; - handler->session = session; - err = svn_ra_serf__context_run_one(handler, pool); + SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); - err = svn_error_compose_create( - svn_ra_serf__error_on_status(handler->sline, - handler->path, - handler->location), - err); + if (handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); - return svn_error_trace(err); + return SVN_NO_ERROR; } diff --git a/subversion/libsvn_ra_serf/blncache.c b/subversion/libsvn_ra_serf/blncache.c index d6abcdf..6fefa93 100644 --- a/subversion/libsvn_ra_serf/blncache.c +++ b/subversion/libsvn_ra_serf/blncache.c @@ -112,7 +112,7 @@ svn_ra_serf__blncache_set(svn_ra_serf__blncache_t *blncache, const char *baseline_url, svn_revnum_t revision, const char *bc_url, - apr_pool_t *pool) + apr_pool_t *scratch_pool) { if (bc_url && SVN_IS_VALID_REVNUM(revision)) { @@ -147,11 +147,11 @@ svn_error_t * svn_ra_serf__blncache_get_bc_url(const char **bc_url_p, svn_ra_serf__blncache_t *blncache, svn_revnum_t revnum, - apr_pool_t *pool) + apr_pool_t *result_pool) { const char *value = apr_hash_get(blncache->revnum_to_bc, &revnum, sizeof(revnum)); - *bc_url_p = value ? apr_pstrdup(pool, value) : NULL; + *bc_url_p = value ? apr_pstrdup(result_pool, value) : NULL; return SVN_NO_ERROR; } diff --git a/subversion/libsvn_ra_serf/blncache.h b/subversion/libsvn_ra_serf/blncache.h index 5ad4eba..fe14799 100644 --- a/subversion/libsvn_ra_serf/blncache.h +++ b/subversion/libsvn_ra_serf/blncache.h @@ -59,7 +59,7 @@ svn_ra_serf__blncache_set(svn_ra_serf__blncache_t *blncache, const char *baseline_url, svn_revnum_t revnum, const char *bc_url, - apr_pool_t *pool); + apr_pool_t *scratch_pool); /* Sets *BC_URL_P with a pointer to baseline collection URL for the given * REVNUM. *BC_URL_P will be NULL if cache doesn't have information about @@ -69,7 +69,7 @@ svn_error_t * svn_ra_serf__blncache_get_bc_url(const char **bc_url_p, svn_ra_serf__blncache_t *blncache, svn_revnum_t revnum, - apr_pool_t *pool); + apr_pool_t *result_pool); /* Sets *BC_URL_P with pointer to baseline collection URL and *REVISION_P * with revision number of baseline BASELINE_URL. *BC_URL_P will be NULL, diff --git a/subversion/libsvn_ra_serf/commit.c b/subversion/libsvn_ra_serf/commit.c index 9d48d41..b1e81c7 100644 --- a/subversion/libsvn_ra_serf/commit.c +++ b/subversion/libsvn_ra_serf/commit.c @@ -51,7 +51,6 @@ typedef struct commit_context_t { apr_pool_t *pool; svn_ra_serf__session_t *session; - svn_ra_serf__connection_t *conn; apr_hash_t *revprop_table; @@ -83,15 +82,13 @@ typedef struct proppatch_context_t { const char *relpath; const char *path; - commit_context_t *commit; + commit_context_t *commit_ctx; - /* Changed and removed properties. */ - apr_hash_t *changed_props; - apr_hash_t *removed_props; + /* Changed properties. const char * -> svn_prop_t * */ + apr_hash_t *prop_changes; - /* Same, for the old value (*old_value_p). */ - apr_hash_t *previous_changed_props; - apr_hash_t *previous_removed_props; + /* Same, for the old value, or NULL. */ + apr_hash_t *old_props; /* In HTTP v2, this is the file/directory version we think we're changing. */ svn_revnum_t base_revision; @@ -103,7 +100,9 @@ typedef struct delete_context_t { svn_revnum_t revision; - commit_context_t *commit; + commit_context_t *commit_ctx; + + svn_boolean_t non_recursive_if; /* Only create a non-recursive If header */ } delete_context_t; /* Represents a directory. */ @@ -112,7 +111,7 @@ typedef struct dir_context_t { apr_pool_t *pool; /* The root commit we're in progress for. */ - commit_context_t *commit; + commit_context_t *commit_ctx; /* URL to operate against (used for CHECKOUT and PROPPATCH before HTTP v2, for PROPPATCH in HTTP v2). */ @@ -139,9 +138,8 @@ typedef struct dir_context_t { const char *copy_path; svn_revnum_t copy_revision; - /* Changed and removed properties */ - apr_hash_t *changed_props; - apr_hash_t *removed_props; + /* Changed properties (const char * -> svn_prop_t *) */ + apr_hash_t *prop_changes; /* The checked-out working resource for this directory. May be NULL; if so call checkout_dir() first. */ @@ -154,7 +152,7 @@ typedef struct file_context_t { apr_pool_t *pool; /* The root commit we're in progress for. */ - commit_context_t *commit; + commit_context_t *commit_ctx; /* Is this file being added? (Otherwise, just opened.) */ svn_boolean_t added; @@ -186,9 +184,8 @@ typedef struct file_context_t { /* Our resulting checksum as reported by the WC. */ const char *result_checksum; - /* Changed and removed properties. */ - apr_hash_t *changed_props; - apr_hash_t *removed_props; + /* Changed properties (const char * -> svn_prop_t *) */ + apr_hash_t *prop_changes; /* URL to PUT the file at. */ const char *url; @@ -198,39 +195,13 @@ typedef struct file_context_t { /* Setup routines and handlers for various requests we'll invoke. */ -static svn_error_t * -return_response_err(svn_ra_serf__handler_t *handler) -{ - svn_error_t *err; - - /* We should have captured SLINE and LOCATION in the HANDLER. */ - SVN_ERR_ASSERT(handler->handler_pool != NULL); - - /* Ye Olde Fallback Error */ - err = svn_error_compose_create( - handler->server_error != NULL - ? handler->server_error->error - : SVN_NO_ERROR, - svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, - _("%s of '%s': %d %s"), - handler->method, handler->path, - handler->sline.code, handler->sline.reason)); - - /* Try to return one of the standard errors for 301, 404, etc., - then look for an error embedded in the response. */ - return svn_error_compose_create(svn_ra_serf__error_on_status( - handler->sline, - handler->path, - handler->location), - err); -} - /* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_checkout_body(serf_bucket_t **bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { const char *activity_url = baton; serf_bucket_t *body_bkt; @@ -240,9 +211,11 @@ create_checkout_body(serf_bucket_t **bkt, svn_ra_serf__add_xml_header_buckets(body_bkt, alloc); svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:checkout", "xmlns:D", "DAV:", - NULL); - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", NULL); - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL); + SVN_VA_NULL); + svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:activity-set", + SVN_VA_NULL); + svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", + SVN_VA_NULL); SVN_ERR_ASSERT(activity_url != NULL); svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc, @@ -251,7 +224,8 @@ create_checkout_body(serf_bucket_t **bkt, svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href"); svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:activity-set"); - svn_ra_serf__add_tag_buckets(body_bkt, "D:apply-to-version", NULL, alloc); + svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, + "D:apply-to-version", SVN_VA_NULL); svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:checkout"); *bkt = body_bkt; @@ -285,32 +259,30 @@ checkout_node(const char **working_url, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_ra_serf__handler_t handler = { 0 }; + svn_ra_serf__handler_t *handler; apr_status_t status; apr_uri_t uri; /* HANDLER_POOL is the scratch pool since we don't need to remember anything from the handler. We just want the working resource. */ - handler.handler_pool = scratch_pool; - handler.session = commit_ctx->session; - handler.conn = commit_ctx->conn; + handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool); - handler.body_delegate = create_checkout_body; - handler.body_delegate_baton = (/* const */ void *)commit_ctx->activity_url; - handler.body_type = "text/xml"; + handler->body_delegate = create_checkout_body; + handler->body_delegate_baton = (/* const */ void *)commit_ctx->activity_url; + handler->body_type = "text/xml"; - handler.response_handler = svn_ra_serf__expect_empty_body; - handler.response_baton = &handler; + handler->response_handler = svn_ra_serf__expect_empty_body; + handler->response_baton = handler; - handler.method = "CHECKOUT"; - handler.path = node_url; + handler->method = "CHECKOUT"; + handler->path = node_url; - SVN_ERR(svn_ra_serf__context_run_one(&handler, scratch_pool)); + SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); - if (handler.sline.code != 201) - return svn_error_trace(return_response_err(&handler)); + if (handler->sline.code != 201) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); - if (handler.location == NULL) + if (handler->location == NULL) return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, _("No Location header received")); @@ -319,7 +291,7 @@ checkout_node(const char **working_url, 'https:' transaction ... we'll work around that by stripping the scheme, host, and port here and re-adding the correct ones later. */ - status = apr_uri_parse(scratch_pool, handler.location, &uri); + status = apr_uri_parse(scratch_pool, handler->location, &uri); if (status) return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, _("Error parsing Location header value")); @@ -365,11 +337,11 @@ retry_checkout_node(const char **working_url, error and retry a few times, asking for the latest baseline again. */ if (err && (err->apr_err != SVN_ERR_APMOD_BAD_BASELINE)) - return err; + return svn_error_trace(err); } while (err && retry_count--); - return err; + return svn_error_trace(err); } @@ -377,8 +349,7 @@ static svn_error_t * checkout_dir(dir_context_t *dir, apr_pool_t *scratch_pool) { - svn_error_t *err; - dir_context_t *p_dir = dir; + dir_context_t *c_dir = dir; const char *checkout_url; const char **working; @@ -389,35 +360,35 @@ checkout_dir(dir_context_t *dir, /* Is this directory or one of our parent dirs newly added? * If so, we're already implicitly checked out. */ - while (p_dir) + while (c_dir) { - if (p_dir->added) + if (c_dir->added) { - /* Calculate the working_url by skipping the shared ancestor bewteen - * the parent->relpath and dir->relpath. This is safe since an + /* Calculate the working_url by skipping the shared ancestor between + * the c_dir_parent->relpath and dir->relpath. This is safe since an * add is guaranteed to have a parent that is checked out. */ - dir_context_t *parent = p_dir->parent_dir; - const char *relpath = svn_relpath_skip_ancestor(parent->relpath, + dir_context_t *c_dir_parent = c_dir->parent_dir; + const char *relpath = svn_relpath_skip_ancestor(c_dir_parent->relpath, dir->relpath); /* Implicitly checkout this dir now. */ - SVN_ERR_ASSERT(parent->working_url); + SVN_ERR_ASSERT(c_dir_parent->working_url); dir->working_url = svn_path_url_add_component2( - parent->working_url, + c_dir_parent->working_url, relpath, dir->pool); return SVN_NO_ERROR; } - p_dir = p_dir->parent_dir; + c_dir = c_dir->parent_dir; } /* We could be called twice for the root: once to checkout the baseline; * once to checkout the directory itself if we need to do so. * Note: CHECKOUT_URL should live longer than HANDLER. */ - if (!dir->parent_dir && !dir->commit->baseline_url) + if (!dir->parent_dir && !dir->commit_ctx->baseline_url) { - checkout_url = dir->commit->vcc_url; - working = &dir->commit->baseline_url; + checkout_url = dir->commit_ctx->vcc_url; + working = &dir->commit_ctx->baseline_url; } else { @@ -426,18 +397,9 @@ checkout_dir(dir_context_t *dir, } /* Checkout our directory into the activity URL now. */ - err = retry_checkout_node(working, dir->commit, checkout_url, - dir->pool, scratch_pool); - if (err) - { - if (err->apr_err == SVN_ERR_FS_CONFLICT) - SVN_ERR_W(err, apr_psprintf(scratch_pool, - _("Directory '%s' is out of date; try updating"), - svn_dirent_local_style(dir->relpath, scratch_pool))); - return err; - } - - return SVN_NO_ERROR; + return svn_error_trace(retry_checkout_node(working, dir->commit_ctx, + checkout_url, + dir->pool, scratch_pool)); } @@ -493,7 +455,6 @@ get_version_url(const char **checked_in_url, else { const char *propfind_url; - svn_ra_serf__connection_t *conn = session->conns[0]; if (SVN_IS_VALID_REVNUM(base_revision)) { @@ -502,10 +463,9 @@ get_version_url(const char **checked_in_url, this lookup, so we'll do things the hard(er) way, by looking up the version URL from a resource in the baseline collection. */ - /* ### conn==NULL for session->conns[0]. same as CONN. */ SVN_ERR(svn_ra_serf__get_stable_url(&propfind_url, NULL /* latest_revnum */, - session, NULL /* conn */, + session, NULL /* url */, base_revision, scratch_pool, scratch_pool)); } @@ -514,8 +474,8 @@ get_version_url(const char **checked_in_url, propfind_url = session->session_url.path; } - SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout, - conn, propfind_url, base_revision, + SVN_ERR(svn_ra_serf__fetch_dav_prop(&root_checkout, session, + propfind_url, base_revision, "checked-in", scratch_pool, scratch_pool)); if (!root_checkout) @@ -536,7 +496,6 @@ static svn_error_t * checkout_file(file_context_t *file, apr_pool_t *scratch_pool) { - svn_error_t *err; dir_context_t *parent_dir = file->parent_dir; const char *checkout_url; @@ -548,6 +507,7 @@ checkout_file(file_context_t *file, if (parent_dir->added) { /* Implicitly checkout this file now. */ + SVN_ERR_ASSERT(parent_dir->working_url); file->working_url = svn_path_url_add_component2( parent_dir->working_url, svn_relpath_skip_ancestor( @@ -559,23 +519,14 @@ checkout_file(file_context_t *file, } SVN_ERR(get_version_url(&checkout_url, - file->commit->session, + file->commit_ctx->session, file->relpath, file->base_revision, NULL, scratch_pool, scratch_pool)); /* Checkout our file into the activity URL now. */ - err = retry_checkout_node(&file->working_url, file->commit, checkout_url, - file->pool, scratch_pool); - if (err) - { - if (err->apr_err == SVN_ERR_FS_CONFLICT) - SVN_ERR_W(err, apr_psprintf(scratch_pool, - _("File '%s' is out of date; try updating"), - svn_dirent_local_style(file->relpath, scratch_pool))); - return err; - } - - return SVN_NO_ERROR; + return svn_error_trace(retry_checkout_node(&file->working_url, + file->commit_ctx, checkout_url, + file->pool, scratch_pool)); } /* Helper function for proppatch_walker() below. */ @@ -612,101 +563,23 @@ get_encoding_and_cdata(const char **encoding_p, return SVN_NO_ERROR; } -typedef struct walker_baton_t { - serf_bucket_t *body_bkt; - apr_pool_t *body_pool; - - apr_hash_t *previous_changed_props; - apr_hash_t *previous_removed_props; - - const char *path; - - /* Hack, since change_rev_prop(old_value_p != NULL, value = NULL) uses D:set - rather than D:remove... (see notes/http-and-webdav/webdav-protocol) */ - enum { - filter_all_props, - filter_props_with_old_value, - filter_props_without_old_value - } filter; - - /* Is the property being deleted? */ - svn_boolean_t deleting; -} walker_baton_t; - -/* If we have (recorded in WB) the old value of the property named NS:NAME, - * then set *HAVE_OLD_VAL to TRUE and set *OLD_VAL_P to that old value - * (which may be NULL); else set *HAVE_OLD_VAL to FALSE. */ -static svn_error_t * -derive_old_val(svn_boolean_t *have_old_val, - const svn_string_t **old_val_p, - walker_baton_t *wb, - const char *ns, - const char *name) -{ - *have_old_val = FALSE; - - if (wb->previous_changed_props) - { - const svn_string_t *val; - val = svn_ra_serf__get_prop_string(wb->previous_changed_props, - wb->path, ns, name); - if (val) - { - *have_old_val = TRUE; - *old_val_p = val; - } - } - - if (wb->previous_removed_props) - { - const svn_string_t *val; - val = svn_ra_serf__get_prop_string(wb->previous_removed_props, - wb->path, ns, name); - if (val) - { - *have_old_val = TRUE; - *old_val_p = NULL; - } - } - - return SVN_NO_ERROR; -} - +/* Helper for create_proppatch_body. Writes per property xml to body */ static svn_error_t * -proppatch_walker(void *baton, - const char *ns, - const char *name, - const svn_string_t *val, - apr_pool_t *scratch_pool) +write_prop_xml(const proppatch_context_t *proppatch, + serf_bucket_t *body_bkt, + serf_bucket_alloc_t *alloc, + const svn_prop_t *prop, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - walker_baton_t *wb = baton; - serf_bucket_t *body_bkt = wb->body_bkt; serf_bucket_t *cdata_bkt; - serf_bucket_alloc_t *alloc; const char *encoding; - svn_boolean_t have_old_val; - const svn_string_t *old_val; const svn_string_t *encoded_value; const char *prop_name; + const svn_prop_t *old_prop; - SVN_ERR(derive_old_val(&have_old_val, &old_val, wb, ns, name)); - - /* Jump through hoops to work with D:remove and its val = (""-for-NULL) - * representation. */ - if (wb->filter != filter_all_props) - { - if (wb->filter == filter_props_with_old_value && ! have_old_val) - return SVN_NO_ERROR; - if (wb->filter == filter_props_without_old_value && have_old_val) - return SVN_NO_ERROR; - } - if (wb->deleting) - val = NULL; - - alloc = body_bkt->allocator; - - SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, val, - wb->body_pool, scratch_pool)); + SVN_ERR(get_encoding_and_cdata(&encoding, &encoded_value, alloc, prop->value, + result_pool, scratch_pool)); if (encoded_value) { cdata_bkt = SERF_BUCKET_SIMPLE_STRING_LEN(encoded_value->data, @@ -720,29 +593,40 @@ proppatch_walker(void *baton, /* Use the namespace prefix instead of adding the xmlns attribute to support property names containing ':' */ - if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) - prop_name = apr_pstrcat(wb->body_pool, "S:", name, (char *)NULL); - else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) - prop_name = apr_pstrcat(wb->body_pool, "C:", name, (char *)NULL); + if (strncmp(prop->name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0) + { + prop_name = apr_pstrcat(result_pool, + "S:", prop->name + sizeof(SVN_PROP_PREFIX) - 1, + SVN_VA_NULL); + } + else + { + prop_name = apr_pstrcat(result_pool, + "C:", prop->name, + SVN_VA_NULL); + } if (cdata_bkt) svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name, "V:encoding", encoding, - NULL); + SVN_VA_NULL); else svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, prop_name, "V:" SVN_DAV__OLD_VALUE__ABSENT, "1", - NULL); + SVN_VA_NULL); - if (have_old_val) + old_prop = proppatch->old_props + ? svn_hash_gets(proppatch->old_props, prop->name) + : NULL; + if (old_prop) { const char *encoding2; const svn_string_t *encoded_value2; serf_bucket_t *cdata_bkt2; SVN_ERR(get_encoding_and_cdata(&encoding2, &encoded_value2, - alloc, old_val, - wb->body_pool, scratch_pool)); + alloc, old_prop->value, + result_pool, scratch_pool)); if (encoded_value2) { @@ -759,12 +643,12 @@ proppatch_walker(void *baton, svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "V:" SVN_DAV__OLD_VALUE, "V:encoding", encoding2, - NULL); + SVN_VA_NULL); else svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "V:" SVN_DAV__OLD_VALUE, "V:" SVN_DAV__OLD_VALUE__ABSENT, "1", - NULL); + SVN_VA_NULL); if (cdata_bkt2) serf_bucket_aggregate_append(body_bkt, cdata_bkt2); @@ -799,7 +683,7 @@ maybe_set_lock_token_header(serf_bucket_t *headers, { const char *token; - if (! (relpath && commit_ctx->lock_tokens)) + if (! (*relpath && commit_ctx->lock_tokens)) return SVN_NO_ERROR; if (! svn_hash_gets(commit_ctx->deleted_entries, relpath)) @@ -819,7 +703,7 @@ maybe_set_lock_token_header(serf_bucket_t *headers, token_uri = apr_uri_unparse(pool, &uri, 0); token_header = apr_pstrcat(pool, "<", token_uri, "> (<", token, ">)", - (char *)NULL); + SVN_VA_NULL); serf_bucket_headers_set(headers, "If", token_header); } } @@ -830,7 +714,8 @@ maybe_set_lock_token_header(serf_bucket_t *headers, static svn_error_t * setup_proppatch_headers(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { proppatch_context_t *proppatch = baton; @@ -841,31 +726,26 @@ setup_proppatch_headers(serf_bucket_t *headers, proppatch->base_revision)); } - SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit, - proppatch->relpath, pool)); + if (proppatch->relpath && proppatch->commit_ctx) + SVN_ERR(maybe_set_lock_token_header(headers, proppatch->commit_ctx, + proppatch->relpath, pool)); return SVN_NO_ERROR; } -struct proppatch_body_baton_t { - proppatch_context_t *proppatch; - - /* Content in the body should be allocated here, to live long enough. */ - apr_pool_t *body_pool; -}; - /* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_proppatch_body(serf_bucket_t **bkt, void *baton, serf_bucket_alloc_t *alloc, + apr_pool_t *pool /* request pool */, apr_pool_t *scratch_pool) { - struct proppatch_body_baton_t *pbb = baton; - proppatch_context_t *ctx = pbb->proppatch; + proppatch_context_t *ctx = baton; serf_bucket_t *body_bkt; - walker_baton_t wb = { 0 }; + svn_boolean_t opened = FALSE; + apr_hash_index_t *hi; body_bkt = serf_bucket_aggregate_create(alloc); @@ -875,58 +755,66 @@ create_proppatch_body(serf_bucket_t **bkt, "xmlns:V", SVN_DAV_PROP_NS_DAV, "xmlns:C", SVN_DAV_PROP_NS_CUSTOM, "xmlns:S", SVN_DAV_PROP_NS_SVN, - NULL); + SVN_VA_NULL); - wb.body_bkt = body_bkt; - wb.body_pool = pbb->body_pool; - wb.previous_changed_props = ctx->previous_changed_props; - wb.previous_removed_props = ctx->previous_removed_props; - wb.path = ctx->path; - - if (apr_hash_count(ctx->changed_props) > 0) + /* First we write property SETs */ + for (hi = apr_hash_first(scratch_pool, ctx->prop_changes); + hi; + hi = apr_hash_next(hi)) { - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL); - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL); + svn_prop_t *prop = apr_hash_this_val(hi); - wb.filter = filter_all_props; - wb.deleting = FALSE; - SVN_ERR(svn_ra_serf__walk_all_props(ctx->changed_props, ctx->path, - SVN_INVALID_REVNUM, - proppatch_walker, &wb, - scratch_pool)); + if (prop->value + || (ctx->old_props && svn_hash_gets(ctx->old_props, prop->name))) + { + if (!opened) + { + opened = TRUE; + svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", + SVN_VA_NULL); + svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", + SVN_VA_NULL); + } - svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop"); - svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set"); + SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop, + pool, scratch_pool)); + } } - if (apr_hash_count(ctx->removed_props) > 0) + if (opened) { - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:set", NULL); - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL); - - wb.filter = filter_props_with_old_value; - wb.deleting = TRUE; - SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path, - SVN_INVALID_REVNUM, - proppatch_walker, &wb, - scratch_pool)); - svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop"); svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:set"); } - if (apr_hash_count(ctx->removed_props) > 0) + /* And then property REMOVEs */ + opened = FALSE; + + for (hi = apr_hash_first(scratch_pool, ctx->prop_changes); + hi; + hi = apr_hash_next(hi)) { - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", NULL); - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL); + svn_prop_t *prop = apr_hash_this_val(hi); - wb.filter = filter_props_without_old_value; - wb.deleting = TRUE; - SVN_ERR(svn_ra_serf__walk_all_props(ctx->removed_props, ctx->path, - SVN_INVALID_REVNUM, - proppatch_walker, &wb, - scratch_pool)); + if (!prop->value + && !(ctx->old_props && svn_hash_gets(ctx->old_props, prop->name))) + { + if (!opened) + { + opened = TRUE; + svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:remove", + SVN_VA_NULL); + svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", + SVN_VA_NULL); + } + SVN_ERR(write_prop_xml(ctx, body_bkt, alloc, prop, + pool, scratch_pool)); + } + } + + if (opened) + { svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop"); svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:remove"); } @@ -938,45 +826,47 @@ create_proppatch_body(serf_bucket_t **bkt, } static svn_error_t* -proppatch_resource(proppatch_context_t *proppatch, - commit_context_t *commit, +proppatch_resource(svn_ra_serf__session_t *session, + proppatch_context_t *proppatch, apr_pool_t *pool) { svn_ra_serf__handler_t *handler; - struct proppatch_body_baton_t pbb; + svn_error_t *err; + + handler = svn_ra_serf__create_handler(session, pool); - handler = apr_pcalloc(pool, sizeof(*handler)); - handler->handler_pool = pool; handler->method = "PROPPATCH"; handler->path = proppatch->path; - handler->conn = commit->conn; - handler->session = commit->session; handler->header_delegate = setup_proppatch_headers; handler->header_delegate_baton = proppatch; - pbb.proppatch = proppatch; - pbb.body_pool = pool; handler->body_delegate = create_proppatch_body; - handler->body_delegate_baton = &pbb; + handler->body_delegate_baton = proppatch; + handler->body_type = "text/xml"; handler->response_handler = svn_ra_serf__handle_multistatus_only; handler->response_baton = handler; - SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); + err = svn_ra_serf__context_run_one(handler, pool); - if (handler->sline.code != 207 - || (handler->server_error != NULL - && handler->server_error->error != NULL)) + if (!err && handler->sline.code != 207) + err = svn_error_trace(svn_ra_serf__unexpected_status(handler)); + + /* Use specific error code for property handling errors. + Use loop to provide the right result with tracing */ + if (err && err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED) { - return svn_error_create( - SVN_ERR_RA_DAV_PROPPATCH_FAILED, - return_response_err(handler), - _("At least one property change failed; repository" - " is unchanged")); + svn_error_t *e = err; + + while (e && e->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED) + { + e->apr_err = SVN_ERR_RA_DAV_PROPPATCH_FAILED; + e = e->child; + } } - return SVN_NO_ERROR; + return svn_error_trace(err); } /* Implements svn_ra_serf__request_body_delegate_t */ @@ -984,7 +874,8 @@ static svn_error_t * create_put_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { file_context_t *ctx = baton; apr_off_t offset; @@ -998,12 +889,10 @@ create_put_body(serf_bucket_t **body_bkt, * check the buffer status; but serf will fall through and create a file * bucket for us on the buffered svndiff handle. */ - apr_file_flush(ctx->svndiff); -#if APR_VERSION_AT_LEAST(1, 3, 0) + SVN_ERR(svn_io_file_flush(ctx->svndiff, pool)); apr_file_buffer_set(ctx->svndiff, NULL, 0); -#endif offset = 0; - apr_file_seek(ctx->svndiff, APR_SET, &offset); + 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; @@ -1014,7 +903,8 @@ static svn_error_t * create_empty_put_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { *body_bkt = SERF_BUCKET_SIMPLE_STRING("", alloc); return SVN_NO_ERROR; @@ -1023,7 +913,8 @@ create_empty_put_body(serf_bucket_t **body_bkt, static svn_error_t * setup_put_headers(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { file_context_t *ctx = baton; @@ -1045,7 +936,7 @@ setup_put_headers(serf_bucket_t *headers, ctx->result_checksum); } - SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit, + SVN_ERR(maybe_set_lock_token_header(headers, ctx->commit_ctx, ctx->relpath, pool)); return APR_SUCCESS; @@ -1054,21 +945,21 @@ setup_put_headers(serf_bucket_t *headers, static svn_error_t * setup_copy_file_headers(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { file_context_t *file = baton; apr_uri_t uri; const char *absolute_uri; /* The Dest URI must be absolute. Bummer. */ - uri = file->commit->session->session_url; + uri = file->commit_ctx->session->session_url; uri.path = (char*)file->url; absolute_uri = apr_uri_unparse(pool, &uri, 0); serf_bucket_headers_set(headers, "Destination", absolute_uri); - serf_bucket_headers_setn(headers, "Depth", "0"); - serf_bucket_headers_setn(headers, "Overwrite", "T"); + serf_bucket_headers_setn(headers, "Overwrite", "F"); return SVN_NO_ERROR; } @@ -1102,7 +993,7 @@ setup_if_header_recursive(svn_boolean_t *added, hi; hi = apr_hash_next(hi)) { - const char *relpath = svn__apr_hash_index_key(hi); + const char *relpath = apr_hash_this_key(hi); apr_uri_t uri; if (!svn_relpath_skip_ancestor(rq_relpath, relpath)) @@ -1132,7 +1023,7 @@ setup_if_header_recursive(svn_boolean_t *added, svn_stringbuf_appendbyte(sb, '<'); svn_stringbuf_appendcstr(sb, apr_uri_unparse(iterpool, &uri, 0)); svn_stringbuf_appendcstr(sb, "> (<"); - svn_stringbuf_appendcstr(sb, svn__apr_hash_index_val(hi)); + svn_stringbuf_appendcstr(sb, apr_hash_this_val(hi)); svn_stringbuf_appendcstr(sb, ">)"); } @@ -1153,29 +1044,31 @@ setup_if_header_recursive(svn_boolean_t *added, static svn_error_t * setup_add_dir_common_headers(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { dir_context_t *dir = baton; svn_boolean_t added; return svn_error_trace( - setup_if_header_recursive(&added, headers, dir->commit, dir->relpath, + setup_if_header_recursive(&added, headers, dir->commit_ctx, dir->relpath, pool)); } static svn_error_t * setup_copy_dir_headers(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { dir_context_t *dir = baton; apr_uri_t uri; const char *absolute_uri; /* The Dest URI must be absolute. Bummer. */ - uri = dir->commit->session->session_url; + uri = dir->commit_ctx->session->session_url; - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit)) + if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) { uri.path = (char *)dir->url; } @@ -1190,18 +1083,20 @@ setup_copy_dir_headers(serf_bucket_t *headers, serf_bucket_headers_set(headers, "Destination", absolute_uri); serf_bucket_headers_setn(headers, "Depth", "infinity"); - serf_bucket_headers_setn(headers, "Overwrite", "T"); + serf_bucket_headers_setn(headers, "Overwrite", "F"); /* Implicitly checkout this dir now. */ dir->working_url = apr_pstrdup(dir->pool, uri.path); - return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool)); + return svn_error_trace(setup_add_dir_common_headers(headers, baton, pool, + scratch_pool)); } static svn_error_t * setup_delete_headers(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { delete_context_t *del = baton; svn_boolean_t added; @@ -1209,34 +1104,23 @@ setup_delete_headers(serf_bucket_t *headers, serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER, apr_ltoa(pool, del->revision)); - SVN_ERR(setup_if_header_recursive(&added, headers, del->commit, - del->relpath, pool)); + if (! del->non_recursive_if) + SVN_ERR(setup_if_header_recursive(&added, headers, del->commit_ctx, + del->relpath, pool)); + else + { + SVN_ERR(maybe_set_lock_token_header(headers, del->commit_ctx, + del->relpath, pool)); + added = TRUE; + } - if (added && del->commit->keep_locks) + if (added && del->commit_ctx->keep_locks) serf_bucket_headers_setn(headers, SVN_DAV_OPTIONS_HEADER, SVN_DAV_OPTION_KEEP_LOCKS); return SVN_NO_ERROR; } -/* Helper function to write the svndiff stream to temporary file. */ -static svn_error_t * -svndiff_stream_write(void *file_baton, - const char *data, - apr_size_t *len) -{ - file_context_t *ctx = file_baton; - apr_status_t status; - - status = apr_file_write_full(ctx->svndiff, data, *len, NULL); - if (status) - return svn_error_wrap_apr(status, _("Failed writing updated file")); - - return SVN_NO_ERROR; -} - - - /* POST against 'me' resource handlers. */ /* Implements svn_ra_serf__request_body_delegate_t */ @@ -1244,7 +1128,8 @@ static svn_error_t * create_txn_post_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { apr_hash_t *revprops = baton; svn_skel_t *request_skel; @@ -1273,7 +1158,8 @@ create_txn_post_body(serf_bucket_t **body_bkt, static svn_error_t * setup_post_headers(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { #ifdef SVN_DAV_SEND_VTXN_NAME /* Enable this to exercise the VTXN-NAME code based on a client @@ -1365,66 +1251,47 @@ open_root(void *edit_baton, apr_pool_t *dir_pool, void **root_baton) { - commit_context_t *ctx = edit_baton; + commit_context_t *commit_ctx = edit_baton; svn_ra_serf__handler_t *handler; proppatch_context_t *proppatch_ctx; dir_context_t *dir; apr_hash_index_t *hi; const char *proppatch_target = NULL; + apr_pool_t *scratch_pool = svn_pool_create(dir_pool); - if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->session)) + if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(commit_ctx->session)) { post_response_ctx_t *prc; const char *rel_path; svn_boolean_t post_with_revprops - = (NULL != svn_hash_gets(ctx->session->supported_posts, + = (NULL != svn_hash_gets(commit_ctx->session->supported_posts, "create-txn-with-props")); /* Create our activity URL now on the server. */ - handler = apr_pcalloc(ctx->pool, sizeof(*handler)); - handler->handler_pool = ctx->pool; + handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool); + handler->method = "POST"; handler->body_type = SVN_SKEL_MIME_TYPE; handler->body_delegate = create_txn_post_body; handler->body_delegate_baton = - post_with_revprops ? ctx->revprop_table : NULL; + post_with_revprops ? commit_ctx->revprop_table : NULL; handler->header_delegate = setup_post_headers; handler->header_delegate_baton = NULL; - handler->path = ctx->session->me_resource; - handler->conn = ctx->session->conns[0]; - handler->session = ctx->session; + handler->path = commit_ctx->session->me_resource; - prc = apr_pcalloc(ctx->pool, sizeof(*prc)); + prc = apr_pcalloc(scratch_pool, sizeof(*prc)); prc->handler = handler; - prc->commit_ctx = ctx; + prc->commit_ctx = commit_ctx; handler->response_handler = post_response_handler; handler->response_baton = prc; - SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool)); + SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); if (handler->sline.code != 201) - { - apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED; - - switch (handler->sline.code) - { - case 403: - status = SVN_ERR_RA_DAV_FORBIDDEN; - break; - case 404: - status = SVN_ERR_FS_NOT_FOUND; - break; - } + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); - return svn_error_createf(status, NULL, - _("%s of '%s': %d %s (%s://%s)"), - handler->method, handler->path, - handler->sline.code, handler->sline.reason, - ctx->session->session_url.scheme, - ctx->session->session_url.hostinfo); - } - if (! (ctx->txn_root_url && ctx->txn_url)) + if (! (commit_ctx->txn_root_url && commit_ctx->txn_url)) { return svn_error_createf( SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, @@ -1432,114 +1299,82 @@ open_root(void *edit_baton, } /* Fixup the txn_root_url to point to the anchor of the commit. */ - SVN_ERR(svn_ra_serf__get_relative_path(&rel_path, - ctx->session->session_url.path, - ctx->session, NULL, dir_pool)); - ctx->txn_root_url = svn_path_url_add_component2(ctx->txn_root_url, - rel_path, ctx->pool); + SVN_ERR(svn_ra_serf__get_relative_path( + &rel_path, + commit_ctx->session->session_url.path, + commit_ctx->session, + scratch_pool)); + commit_ctx->txn_root_url = svn_path_url_add_component2( + commit_ctx->txn_root_url, + rel_path, commit_ctx->pool); /* Build our directory baton. */ dir = apr_pcalloc(dir_pool, sizeof(*dir)); dir->pool = dir_pool; - dir->commit = ctx; + dir->commit_ctx = commit_ctx; dir->base_revision = base_revision; dir->relpath = ""; dir->name = ""; - dir->changed_props = apr_hash_make(dir->pool); - dir->removed_props = apr_hash_make(dir->pool); - dir->url = apr_pstrdup(dir->pool, ctx->txn_root_url); + dir->prop_changes = apr_hash_make(dir->pool); + dir->url = apr_pstrdup(dir->pool, commit_ctx->txn_root_url); /* If we included our revprops in the POST, we need not PROPPATCH them. */ - proppatch_target = post_with_revprops ? NULL : ctx->txn_url; + proppatch_target = post_with_revprops ? NULL : commit_ctx->txn_url; } else { - const char *activity_str = ctx->session->activity_collection_url; + const char *activity_str = commit_ctx->session->activity_collection_url; if (!activity_str) - SVN_ERR(svn_ra_serf__v1_get_activity_collection(&activity_str, - ctx->session->conns[0], - ctx->pool, - ctx->pool)); - - /* Cache the result. */ - if (activity_str) - { - ctx->session->activity_collection_url = - apr_pstrdup(ctx->session->pool, activity_str); - } - else - { - return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, - _("The OPTIONS response did not include the " - "requested activity-collection-set value")); - } + SVN_ERR(svn_ra_serf__v1_get_activity_collection( + &activity_str, + commit_ctx->session, + scratch_pool, scratch_pool)); - ctx->activity_url = - svn_path_url_add_component2(activity_str, svn_uuid_generate(ctx->pool), - ctx->pool); + commit_ctx->activity_url = svn_path_url_add_component2( + activity_str, + svn_uuid_generate(scratch_pool), + commit_ctx->pool); /* Create our activity URL now on the server. */ - handler = apr_pcalloc(ctx->pool, sizeof(*handler)); - handler->handler_pool = ctx->pool; + handler = svn_ra_serf__create_handler(commit_ctx->session, scratch_pool); + handler->method = "MKACTIVITY"; - handler->path = ctx->activity_url; - handler->conn = ctx->session->conns[0]; - handler->session = ctx->session; + handler->path = commit_ctx->activity_url; handler->response_handler = svn_ra_serf__expect_empty_body; handler->response_baton = handler; - SVN_ERR(svn_ra_serf__context_run_one(handler, ctx->pool)); + SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); if (handler->sline.code != 201) - { - apr_status_t status = SVN_ERR_RA_DAV_REQUEST_FAILED; - - switch (handler->sline.code) - { - case 403: - status = SVN_ERR_RA_DAV_FORBIDDEN; - break; - case 404: - status = SVN_ERR_FS_NOT_FOUND; - break; - } - - return svn_error_createf(status, NULL, - _("%s of '%s': %d %s (%s://%s)"), - handler->method, handler->path, - handler->sline.code, handler->sline.reason, - ctx->session->session_url.scheme, - ctx->session->session_url.hostinfo); - } + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); /* Now go fetch our VCC and baseline so we can do a CHECKOUT. */ - SVN_ERR(svn_ra_serf__discover_vcc(&(ctx->vcc_url), ctx->session, - ctx->conn, ctx->pool)); + SVN_ERR(svn_ra_serf__discover_vcc(&(commit_ctx->vcc_url), + commit_ctx->session, scratch_pool)); /* Build our directory baton. */ dir = apr_pcalloc(dir_pool, sizeof(*dir)); dir->pool = dir_pool; - dir->commit = ctx; + dir->commit_ctx = commit_ctx; dir->base_revision = base_revision; dir->relpath = ""; dir->name = ""; - dir->changed_props = apr_hash_make(dir->pool); - dir->removed_props = apr_hash_make(dir->pool); + dir->prop_changes = apr_hash_make(dir->pool); - SVN_ERR(get_version_url(&dir->url, dir->commit->session, + SVN_ERR(get_version_url(&dir->url, dir->commit_ctx->session, dir->relpath, - dir->base_revision, ctx->checked_in_url, - dir->pool, dir->pool /* scratch_pool */)); - ctx->checked_in_url = dir->url; + dir->base_revision, commit_ctx->checked_in_url, + dir->pool, scratch_pool)); + commit_ctx->checked_in_url = apr_pstrdup(commit_ctx->pool, dir->url); /* Checkout our root dir */ - SVN_ERR(checkout_dir(dir, dir->pool /* scratch_pool */)); + SVN_ERR(checkout_dir(dir, scratch_pool)); - proppatch_target = ctx->baseline_url; + proppatch_target = commit_ctx->baseline_url; } /* Unless this is NULL -- which means we don't need to PROPPATCH the @@ -1547,44 +1382,58 @@ open_root(void *edit_baton, transaction with our revprops. */ if (proppatch_target) { - proppatch_ctx = apr_pcalloc(ctx->pool, sizeof(*proppatch_ctx)); - proppatch_ctx->pool = dir_pool; - proppatch_ctx->commit = ctx; + proppatch_ctx = apr_pcalloc(scratch_pool, sizeof(*proppatch_ctx)); + proppatch_ctx->pool = scratch_pool; + proppatch_ctx->commit_ctx = NULL; /* No lock info */ proppatch_ctx->path = proppatch_target; - proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool); - proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool); + proppatch_ctx->prop_changes = apr_hash_make(proppatch_ctx->pool); proppatch_ctx->base_revision = SVN_INVALID_REVNUM; - for (hi = apr_hash_first(ctx->pool, ctx->revprop_table); hi; + for (hi = apr_hash_first(scratch_pool, commit_ctx->revprop_table); + hi; hi = apr_hash_next(hi)) { - const char *name = svn__apr_hash_index_key(hi); - svn_string_t *value = svn__apr_hash_index_val(hi); - const char *ns; + svn_prop_t *prop = apr_palloc(scratch_pool, sizeof(*prop)); - if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0) - { - ns = SVN_DAV_PROP_NS_SVN; - name += sizeof(SVN_PROP_PREFIX) - 1; - } - else - { - ns = SVN_DAV_PROP_NS_CUSTOM; - } + prop->name = apr_hash_this_key(hi); + prop->value = apr_hash_this_val(hi); - svn_ra_serf__set_prop(proppatch_ctx->changed_props, - proppatch_ctx->path, - ns, name, value, proppatch_ctx->pool); + svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop); } - SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, ctx->pool)); + SVN_ERR(proppatch_resource(commit_ctx->session, + proppatch_ctx, scratch_pool)); } + svn_pool_destroy(scratch_pool); + *root_baton = dir; return SVN_NO_ERROR; } +/* Implements svn_ra_serf__request_body_delegate_t */ +static svn_error_t * +create_delete_body(serf_bucket_t **body_bkt, + void *baton, + serf_bucket_alloc_t *alloc, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) +{ + delete_context_t *ctx = baton; + serf_bucket_t *body; + + body = serf_bucket_aggregate_create(alloc); + + svn_ra_serf__add_xml_header_buckets(body, alloc); + + svn_ra_serf__merge_lock_token_list(ctx->commit_ctx->lock_tokens, + ctx->relpath, body, alloc, pool); + + *body_bkt = body; + return SVN_NO_ERROR; +} + static svn_error_t * delete_entry(const char *path, svn_revnum_t revision, @@ -1596,10 +1445,11 @@ delete_entry(const char *path, svn_ra_serf__handler_t *handler; const char *delete_target; - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit)) + if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) { - delete_target = svn_path_url_add_component2(dir->commit->txn_root_url, - path, dir->pool); + delete_target = svn_path_url_add_component2( + dir->commit_ctx->txn_root_url, + path, dir->pool); } else { @@ -1615,12 +1465,9 @@ delete_entry(const char *path, delete_ctx = apr_pcalloc(pool, sizeof(*delete_ctx)); delete_ctx->relpath = apr_pstrdup(pool, path); delete_ctx->revision = revision; - delete_ctx->commit = dir->commit; + delete_ctx->commit_ctx = dir->commit_ctx; - handler = apr_pcalloc(pool, sizeof(*handler)); - handler->handler_pool = pool; - handler->session = dir->commit->session; - handler->conn = dir->commit->conn; + handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool); handler->response_handler = svn_ra_serf__expect_empty_body; handler->response_baton = handler; @@ -1630,17 +1477,43 @@ delete_entry(const char *path, handler->method = "DELETE"; handler->path = delete_target; + handler->no_fail_on_http_failure_status = TRUE; SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); - /* 204 No Content: item successfully deleted */ - if (handler->sline.code != 204) + if (handler->sline.code == 400) { - return svn_error_trace(return_response_err(handler)); + /* Try again with non-standard body to overcome Apache Httpd + header limit */ + delete_ctx->non_recursive_if = TRUE; + + handler = svn_ra_serf__create_handler(dir->commit_ctx->session, pool); + + handler->response_handler = svn_ra_serf__expect_empty_body; + handler->response_baton = handler; + + handler->header_delegate = setup_delete_headers; + handler->header_delegate_baton = delete_ctx; + + handler->method = "DELETE"; + handler->path = delete_target; + + 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_hash_sets(dir->commit->deleted_entries, - apr_pstrdup(dir->commit->pool, path), (void *)1); + if (handler->server_error) + return svn_ra_serf__server_error_create(handler, pool); + + /* 204 No Content: item successfully deleted */ + if (handler->sline.code != 204) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + + svn_hash_sets(dir->commit_ctx->deleted_entries, + apr_pstrdup(dir->commit_ctx->pool, path), (void *)1); return SVN_NO_ERROR; } @@ -1663,19 +1536,18 @@ add_directory(const char *path, dir->pool = dir_pool; dir->parent_dir = parent; - dir->commit = parent->commit; + dir->commit_ctx = parent->commit_ctx; dir->added = TRUE; dir->base_revision = SVN_INVALID_REVNUM; dir->copy_revision = copyfrom_revision; dir->copy_path = apr_pstrdup(dir->pool, copyfrom_path); dir->relpath = apr_pstrdup(dir->pool, path); dir->name = svn_relpath_basename(dir->relpath, NULL); - dir->changed_props = apr_hash_make(dir->pool); - dir->removed_props = apr_hash_make(dir->pool); + dir->prop_changes = apr_hash_make(dir->pool); - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit)) + if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) { - dir->url = svn_path_url_add_component2(parent->commit->txn_root_url, + dir->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url, path, dir->pool); mkcol_target = dir->url; } @@ -1684,17 +1556,14 @@ add_directory(const char *path, /* Ensure our parent is checked out. */ SVN_ERR(checkout_dir(parent, dir->pool /* scratch_pool */)); - dir->url = svn_path_url_add_component2(parent->commit->checked_in_url, + dir->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 = apr_pcalloc(dir->pool, sizeof(*handler)); - handler->handler_pool = dir->pool; - handler->conn = dir->commit->conn; - handler->session = dir->commit->session; + 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; @@ -1719,10 +1588,8 @@ add_directory(const char *path, dir->copy_path); } - /* ### conn==NULL for session->conns[0]. same as commit->conn. */ SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, - dir->commit->session, - NULL /* conn */, + dir->commit_ctx->session, uri.path, dir->copy_revision, dir_pool, dir_pool)); @@ -1732,26 +1599,13 @@ add_directory(const char *path, handler->header_delegate = setup_copy_dir_headers; handler->header_delegate_baton = dir; } - + /* 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)); - switch (handler->sline.code) - { - case 201: /* Created: item was successfully copied */ - case 204: /* No Content: item successfully replaced an existing target */ - break; - - case 403: - return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL, - _("Access to '%s' forbidden"), - handler->path); - default: - return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, - _("Adding directory failed: %s on %s " - "(%d %s)"), - handler->method, handler->path, - handler->sline.code, handler->sline.reason); - } + if (handler->sline.code != 201) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); *child_baton = dir; @@ -1773,26 +1627,25 @@ open_directory(const char *path, dir->pool = dir_pool; dir->parent_dir = parent; - dir->commit = parent->commit; + dir->commit_ctx = parent->commit_ctx; dir->added = FALSE; dir->base_revision = base_revision; dir->relpath = apr_pstrdup(dir->pool, path); dir->name = svn_relpath_basename(dir->relpath, NULL); - dir->changed_props = apr_hash_make(dir->pool); - dir->removed_props = apr_hash_make(dir->pool); + dir->prop_changes = apr_hash_make(dir->pool); - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit)) + if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) { - dir->url = svn_path_url_add_component2(parent->commit->txn_root_url, + 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->session, + dir->commit_ctx->session, dir->relpath, dir->base_revision, - dir->commit->checked_in_url, + dir->commit_ctx->checked_in_url, dir->pool, dir->pool /* scratch_pool */)); } *child_baton = dir; @@ -1804,48 +1657,23 @@ static svn_error_t * change_dir_prop(void *dir_baton, const char *name, const svn_string_t *value, - apr_pool_t *pool) + apr_pool_t *scratch_pool) { dir_context_t *dir = dir_baton; - const char *ns; - const char *proppatch_target; + svn_prop_t *prop; - - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit)) - { - proppatch_target = dir->url; - } - else + if (! USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) { /* Ensure we have a checked out dir. */ - SVN_ERR(checkout_dir(dir, pool /* scratch_pool */)); - - proppatch_target = dir->working_url; + SVN_ERR(checkout_dir(dir, scratch_pool)); } - name = apr_pstrdup(dir->pool, name); - if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0) - { - ns = SVN_DAV_PROP_NS_SVN; - name += sizeof(SVN_PROP_PREFIX) - 1; - } - else - { - ns = SVN_DAV_PROP_NS_CUSTOM; - } + prop = apr_palloc(dir->pool, sizeof(*prop)); - if (value) - { - value = svn_string_dup(value, dir->pool); - svn_ra_serf__set_prop(dir->changed_props, proppatch_target, - ns, name, value, dir->pool); - } - else - { - value = svn_string_create_empty(dir->pool); - svn_ra_serf__set_prop(dir->removed_props, proppatch_target, - ns, name, value, dir->pool); - } + 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; } @@ -1861,20 +1689,18 @@ close_directory(void *dir_baton, */ /* PROPPATCH our prop change and pass it along. */ - if (apr_hash_count(dir->changed_props) || - apr_hash_count(dir->removed_props)) + 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 = dir->commit; + proppatch_ctx->commit_ctx = NULL /* No lock tokens necessary */; proppatch_ctx->relpath = dir->relpath; - proppatch_ctx->changed_props = dir->changed_props; - proppatch_ctx->removed_props = dir->removed_props; + proppatch_ctx->prop_changes = dir->prop_changes; proppatch_ctx->base_revision = dir->base_revision; - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit)) + if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) { proppatch_ctx->path = dir->url; } @@ -1883,7 +1709,8 @@ close_directory(void *dir_baton, proppatch_ctx->path = dir->working_url; } - SVN_ERR(proppatch_resource(proppatch_ctx, dir->commit, dir->pool)); + SVN_ERR(proppatch_resource(dir->commit_ctx->session, + proppatch_ctx, dir->pool)); } return SVN_NO_ERROR; @@ -1900,6 +1727,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); new_file = apr_pcalloc(file_pool, sizeof(*new_file)); new_file->pool = file_pool; @@ -1907,28 +1735,27 @@ add_file(const char *path, dir->ref_count++; new_file->parent_dir = dir; - new_file->commit = dir->commit; + new_file->commit_ctx = dir->commit_ctx; new_file->relpath = apr_pstrdup(new_file->pool, path); new_file->name = svn_relpath_basename(new_file->relpath, NULL); new_file->added = TRUE; new_file->base_revision = SVN_INVALID_REVNUM; new_file->copy_path = apr_pstrdup(new_file->pool, copy_path); new_file->copy_revision = copy_revision; - new_file->changed_props = apr_hash_make(new_file->pool); - new_file->removed_props = apr_hash_make(new_file->pool); + new_file->prop_changes = apr_hash_make(new_file->pool); /* Ensure that the file doesn't exist by doing a HEAD on the resource. If we're using HTTP v2, we'll just look into the transaction root tree for this thing. */ - if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit)) + if (USING_HTTPV2_COMMIT_SUPPORT(dir->commit_ctx)) { - new_file->url = svn_path_url_add_component2(dir->commit->txn_root_url, + 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, new_file->pool /* scratch_pool */)); + SVN_ERR(checkout_dir(dir, scratch_pool)); new_file->url = svn_path_url_add_component2(dir->working_url, @@ -1937,49 +1764,78 @@ add_file(const char *path, while (deleted_parent && deleted_parent[0] != '\0') { - if (svn_hash_gets(dir->commit->deleted_entries, deleted_parent)) + if (svn_hash_gets(dir->commit_ctx->deleted_entries, deleted_parent)) { break; } deleted_parent = svn_relpath_dirname(deleted_parent, file_pool); } - if (! ((dir->added && !dir->copy_path) || - (deleted_parent && deleted_parent[0] != '\0'))) + 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); + 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 = apr_pcalloc(new_file->pool, sizeof(*handler)); - handler->handler_pool = new_file->pool; - handler->session = new_file->commit->session; - handler->conn = new_file->commit->conn; - handler->method = "HEAD"; - handler->path = svn_path_url_add_component2( - dir->commit->session->session_url.path, - path, new_file->pool); handler->response_handler = svn_ra_serf__expect_empty_body; handler->response_baton = handler; - SVN_ERR(svn_ra_serf__context_run_one(handler, new_file->pool)); + handler->header_delegate = setup_copy_file_headers; + handler->header_delegate_baton = new_file; - if (handler->sline.code != 404) - { - if (handler->sline.code != 200) - { - svn_error_t *err; + SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); - err = svn_ra_serf__error_on_status(handler->sline, - handler->path, - handler->location); + if (handler->sline.code != 201) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + } + else if (! ((dir->added && !dir->copy_path) || + (deleted_parent && deleted_parent[0] != '\0'))) + { + svn_ra_serf__handler_t *handler; + svn_error_t *err; - SVN_ERR(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 */ + + err = svn_ra_serf__context_run_one(handler, scratch_pool); - return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, - _("File '%s' already exists"), path); + 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; @@ -2001,17 +1857,16 @@ open_file(const char *path, parent->ref_count++; new_file->parent_dir = parent; - new_file->commit = parent->commit; + new_file->commit_ctx = parent->commit_ctx; new_file->relpath = apr_pstrdup(new_file->pool, path); new_file->name = svn_relpath_basename(new_file->relpath, NULL); new_file->added = FALSE; new_file->base_revision = base_revision; - new_file->changed_props = apr_hash_make(new_file->pool); - new_file->removed_props = apr_hash_make(new_file->pool); + new_file->prop_changes = apr_hash_make(new_file->pool); - if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit)) + if (USING_HTTPV2_COMMIT_SUPPORT(parent->commit_ctx)) { - new_file->url = svn_path_url_add_component2(parent->commit->txn_root_url, + new_file->url = svn_path_url_add_component2(parent->commit_ctx->txn_root_url, path, new_file->pool); } else @@ -2027,6 +1882,23 @@ 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) +{ + 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)); + + *stream = svn_stream_from_aprfile2(file_ctx->svndiff, TRUE, result_pool); + return SVN_NO_ERROR; +} + static svn_error_t * apply_textdelta(void *file_baton, const char *base_checksum, @@ -2043,18 +1915,10 @@ apply_textdelta(void *file_baton, * writing to a temporary file (ugh). A special svn stream serf bucket * that returns EAGAIN until we receive the done call? But, when * would we run through the serf context? Grr. - * - * ctx->pool is the same for all files in the commit that send a - * textdelta so this file is explicitly closed in close_file to - * avoid too many simultaneously open files. */ - SVN_ERR(svn_io_open_unique_file3(&ctx->svndiff, NULL, NULL, - svn_io_file_del_on_pool_cleanup, - ctx->pool, pool)); - - ctx->stream = svn_stream_create(ctx, pool); - svn_stream_set_write(ctx->stream, svndiff_stream_write); + 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); @@ -2072,33 +1936,14 @@ change_file_prop(void *file_baton, apr_pool_t *pool) { file_context_t *file = file_baton; - const char *ns; + svn_prop_t *prop; - name = apr_pstrdup(file->pool, name); + prop = apr_palloc(file->pool, sizeof(*prop)); - if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0) - { - ns = SVN_DAV_PROP_NS_SVN; - name += sizeof(SVN_PROP_PREFIX) - 1; - } - else - { - ns = SVN_DAV_PROP_NS_CUSTOM; - } - - if (value) - { - value = svn_string_dup(value, file->pool); - svn_ra_serf__set_prop(file->changed_props, file->url, - ns, name, value, file->pool); - } - else - { - value = svn_string_create_empty(file->pool); + prop->name = apr_pstrdup(file->pool, name); + prop->value = svn_string_dup(value, file->pool); - svn_ra_serf__set_prop(file->removed_props, file->url, - ns, name, value, file->pool); - } + svn_hash_sets(file->prop_changes, prop->name, prop); return SVN_NO_ERROR; } @@ -2110,69 +1955,26 @@ close_file(void *file_baton, { file_context_t *ctx = file_baton; svn_boolean_t put_empty_file = FALSE; - apr_status_t status; ctx->result_checksum = text_checksum; - if (ctx->copy_path) - { - svn_ra_serf__handler_t *handler; - apr_uri_t uri; - const char *req_url; - - status = apr_uri_parse(scratch_pool, ctx->copy_path, &uri); - if (status) - { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Unable to parse URL '%s'"), - ctx->copy_path); - } - - /* ### conn==NULL for session->conns[0]. same as commit->conn. */ - SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, - ctx->commit->session, - NULL /* conn */, - uri.path, ctx->copy_revision, - scratch_pool, scratch_pool)); - - handler = apr_pcalloc(scratch_pool, sizeof(*handler)); - handler->handler_pool = scratch_pool; - handler->method = "COPY"; - handler->path = req_url; - handler->conn = ctx->commit->conn; - handler->session = ctx->commit->session; - - handler->response_handler = svn_ra_serf__expect_empty_body; - handler->response_baton = handler; - - handler->header_delegate = setup_copy_file_headers; - handler->header_delegate_baton = ctx; - - SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); - - if (handler->sline.code != 201 && handler->sline.code != 204) - { - return svn_error_trace(return_response_err(handler)); - } - } - /* If we got no stream of changes, but this is an added-without-history * file, make a note that we'll be PUTting a zero-byte file to the server. */ - if ((!ctx->stream) && ctx->added && (!ctx->copy_path)) + 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->stream || put_empty_file) + if (ctx->svndiff || put_empty_file) { svn_ra_serf__handler_t *handler; + int expected_result; + + handler = svn_ra_serf__create_handler(ctx->commit_ctx->session, + scratch_pool); - handler = apr_pcalloc(scratch_pool, sizeof(*handler)); - handler->handler_pool = scratch_pool; handler->method = "PUT"; handler->path = ctx->url; - handler->conn = ctx->commit->conn; - handler->session = ctx->commit->session; handler->response_handler = svn_ra_serf__expect_empty_body; handler->response_baton = handler; @@ -2195,31 +1997,33 @@ close_file(void *file_baton, SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); - if (handler->sline.code != 204 && handler->sline.code != 201) - { - return svn_error_trace(return_response_err(handler)); - } + if (ctx->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)); } if (ctx->svndiff) SVN_ERR(svn_io_file_close(ctx->svndiff, scratch_pool)); /* If we had any prop changes, push them via PROPPATCH. */ - if (apr_hash_count(ctx->changed_props) || - apr_hash_count(ctx->removed_props)) + if (apr_hash_count(ctx->prop_changes)) { proppatch_context_t *proppatch; - proppatch = apr_pcalloc(ctx->pool, sizeof(*proppatch)); - proppatch->pool = ctx->pool; + proppatch = apr_pcalloc(scratch_pool, sizeof(*proppatch)); + proppatch->pool = scratch_pool; proppatch->relpath = ctx->relpath; proppatch->path = ctx->url; - proppatch->commit = ctx->commit; - proppatch->changed_props = ctx->changed_props; - proppatch->removed_props = ctx->removed_props; + proppatch->commit_ctx = ctx->commit_ctx; + proppatch->prop_changes = ctx->prop_changes; proppatch->base_revision = ctx->base_revision; - SVN_ERR(proppatch_resource(proppatch, ctx->commit, ctx->pool)); + SVN_ERR(proppatch_resource(ctx->commit_ctx->session, + proppatch, scratch_pool)); } return SVN_NO_ERROR; @@ -2233,26 +2037,16 @@ close_edit(void *edit_baton, const char *merge_target = ctx->activity_url ? ctx->activity_url : ctx->txn_url; const svn_commit_info_t *commit_info; - int response_code; svn_error_t *err = NULL; /* MERGE our activity */ - SVN_ERR(svn_ra_serf__run_merge(&commit_info, &response_code, + SVN_ERR(svn_ra_serf__run_merge(&commit_info, ctx->session, - ctx->session->conns[0], merge_target, ctx->lock_tokens, ctx->keep_locks, pool, pool)); - if (response_code != 200) - { - return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, - _("MERGE request failed: returned %d " - "(during commit)"), - response_code); - } - ctx->txn_url = NULL; /* If HTTPv2, the txn is now done */ /* Inform the WC that we did a commit. */ @@ -2264,12 +2058,10 @@ close_edit(void *edit_baton, { svn_ra_serf__handler_t *handler; - handler = apr_pcalloc(pool, sizeof(*handler)); - handler->handler_pool = pool; + handler = svn_ra_serf__create_handler(ctx->session, pool); + handler->method = "DELETE"; handler->path = ctx->activity_url; - handler->conn = ctx->conn; - handler->session = ctx->session; handler->response_handler = svn_ra_serf__expect_empty_body; handler->response_baton = handler; @@ -2280,7 +2072,8 @@ close_edit(void *edit_baton, err, svn_ra_serf__context_run_one(handler, pool))); - SVN_ERR_ASSERT(handler->sline.code == 204); + if (handler->sline.code != 204) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); } SVN_ERR(err); @@ -2305,14 +2098,13 @@ abort_edit(void *edit_baton, serf_connection_reset(ctx->session->conns[0]->conn); /* DELETE our aborted activity */ - handler = apr_pcalloc(pool, sizeof(*handler)); - handler->handler_pool = pool; + handler = svn_ra_serf__create_handler(ctx->session, pool); + handler->method = "DELETE"; - handler->conn = ctx->session->conns[0]; - handler->session = ctx->session; handler->response_handler = svn_ra_serf__expect_empty_body; handler->response_baton = handler; + handler->no_fail_on_http_failure_status = TRUE; if (USING_HTTPV2_COMMIT_SUPPORT(ctx)) /* HTTP v2 */ handler->path = ctx->txn_url; @@ -2326,14 +2118,15 @@ abort_edit(void *edit_baton, 404 if the activity wasn't found. */ if (handler->sline.code != 204 && handler->sline.code != 403 - && handler->sline.code != 404 - ) + && handler->sline.code != 404) { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("DELETE returned unexpected status: %d"), - handler->sline.code); + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); } + /* Don't delete again if somebody aborts twice */ + ctx->activity_url = NULL; + ctx->txn_url = NULL; + return SVN_NO_ERROR; } @@ -2360,7 +2153,6 @@ svn_ra_serf__get_commit_editor(svn_ra_session_t *ra_session, ctx->pool = pool; ctx->session = session; - ctx->conn = session->conns[0]; ctx->revprop_table = svn_prop_hash_dup(revprop_table, pool); @@ -2427,28 +2219,43 @@ svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session, { svn_ra_serf__session_t *session = ra_session->priv; proppatch_context_t *proppatch_ctx; - commit_context_t *commit; const char *proppatch_target; - const char *ns; + const svn_string_t *tmp_old_value; + svn_boolean_t atomic_capable = FALSE; + svn_prop_t *prop; svn_error_t *err; + if (old_value_p || !value) + SVN_ERR(svn_ra_serf__has_capability(ra_session, &atomic_capable, + SVN_RA_CAPABILITY_ATOMIC_REVPROPS, + pool)); + if (old_value_p) { - svn_boolean_t capable; - SVN_ERR(svn_ra_serf__has_capability(ra_session, &capable, - SVN_RA_CAPABILITY_ATOMIC_REVPROPS, - pool)); - /* How did you get past the same check in svn_ra_change_rev_prop2()? */ - SVN_ERR_ASSERT(capable); + SVN_ERR_ASSERT(atomic_capable); } + else if (! value && atomic_capable) + { + svn_string_t *old_value; + /* mod_dav_svn doesn't report a failure when a property delete fails. The + atomic revprop change behavior is a nice workaround, to allow getting + access to the error anyway. - commit = apr_pcalloc(pool, sizeof(*commit)); + Somehow the mod_dav maintainers think that returning an error from + mod_dav's property delete is an RFC violation. + See https://issues.apache.org/bugzilla/show_bug.cgi?id=53525 */ - commit->pool = pool; + SVN_ERR(svn_ra_serf__rev_prop(ra_session, rev, name, &old_value, + pool)); - commit->session = session; - commit->conn = session->conns[0]; + if (!old_value) + return SVN_NO_ERROR; /* Nothing to delete */ + + /* The api expects a double const pointer. Let's make one */ + tmp_old_value = old_value; + old_value_p = &tmp_old_value; + } if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) { @@ -2458,74 +2265,52 @@ svn_ra_serf__change_rev_prop(svn_ra_session_t *ra_session, { const char *vcc_url; - SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, commit->session, - commit->conn, pool)); + SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool)); SVN_ERR(svn_ra_serf__fetch_dav_prop(&proppatch_target, - commit->conn, vcc_url, rev, - "href", + session, vcc_url, rev, "href", pool, pool)); } - if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX) - 1) == 0) - { - ns = SVN_DAV_PROP_NS_SVN; - name += sizeof(SVN_PROP_PREFIX) - 1; - } - else - { - ns = SVN_DAV_PROP_NS_CUSTOM; - } - /* PROPPATCH our log message and pass it along. */ proppatch_ctx = apr_pcalloc(pool, sizeof(*proppatch_ctx)); proppatch_ctx->pool = pool; - proppatch_ctx->commit = commit; + proppatch_ctx->commit_ctx = NULL; /* No lock headers */ proppatch_ctx->path = proppatch_target; - proppatch_ctx->changed_props = apr_hash_make(proppatch_ctx->pool); - proppatch_ctx->removed_props = apr_hash_make(proppatch_ctx->pool); - if (old_value_p) - { - proppatch_ctx->previous_changed_props = apr_hash_make(proppatch_ctx->pool); - proppatch_ctx->previous_removed_props = apr_hash_make(proppatch_ctx->pool); - } + proppatch_ctx->prop_changes = apr_hash_make(pool); proppatch_ctx->base_revision = SVN_INVALID_REVNUM; - if (old_value_p && *old_value_p) - { - svn_ra_serf__set_prop(proppatch_ctx->previous_changed_props, - proppatch_ctx->path, - ns, name, *old_value_p, proppatch_ctx->pool); - } - else if (old_value_p) + if (old_value_p) { - svn_string_t *dummy_value = svn_string_create_empty(proppatch_ctx->pool); + prop = apr_palloc(pool, sizeof (*prop)); - svn_ra_serf__set_prop(proppatch_ctx->previous_removed_props, - proppatch_ctx->path, - ns, name, dummy_value, proppatch_ctx->pool); - } + prop->name = name; + prop->value = *old_value_p; - if (value) - { - svn_ra_serf__set_prop(proppatch_ctx->changed_props, proppatch_ctx->path, - ns, name, value, proppatch_ctx->pool); + proppatch_ctx->old_props = apr_hash_make(pool); + svn_hash_sets(proppatch_ctx->old_props, prop->name, prop); } - else + + prop = apr_palloc(pool, sizeof (*prop)); + + prop->name = name; + prop->value = value; + svn_hash_sets(proppatch_ctx->prop_changes, prop->name, prop); + + err = proppatch_resource(session, proppatch_ctx, pool); + + /* Use specific error code for old property value mismatch. + Use loop to provide the right result with tracing */ + if (err && err->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED) { - value = svn_string_create_empty(proppatch_ctx->pool); + svn_error_t *e = err; - svn_ra_serf__set_prop(proppatch_ctx->removed_props, proppatch_ctx->path, - ns, name, value, proppatch_ctx->pool); + while (e && e->apr_err == SVN_ERR_RA_DAV_PRECONDITION_FAILED) + { + e->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH; + e = e->child; + } } - err = proppatch_resource(proppatch_ctx, commit, proppatch_ctx->pool); - if (err) - return - svn_error_create - (SVN_ERR_RA_DAV_REQUEST_FAILED, err, - _("DAV request failed; it's possible that the repository's " - "pre-revprop-change hook either failed or is non-existent")); - - return SVN_NO_ERROR; + return svn_error_trace(err); } diff --git a/subversion/libsvn_ra_serf/eagain_bucket.c b/subversion/libsvn_ra_serf/eagain_bucket.c new file mode 100644 index 0000000..16387be --- /dev/null +++ b/subversion/libsvn_ra_serf/eagain_bucket.c @@ -0,0 +1,122 @@ +/* + * eagain_bucket.c : a serf bucket that handles slowing down data + * for specific readers that would have unwanted + * behavior if they read everything too fast + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <serf.h> +#include <serf_bucket_util.h> + +#include "svn_private_config.h" + +#include "ra_serf.h" + +typedef struct eagain_baton_t +{ + const char *data; + apr_size_t remaining; +} eagain_baton_t; + +static apr_status_t +eagain_bucket_read(serf_bucket_t *bucket, + apr_size_t requested, + const char **data, + apr_size_t *len) +{ + eagain_baton_t *eab = bucket->data; + + if (eab->remaining > 0) + { + *data = eab->data; + if (requested > eab->remaining || requested == SERF_READ_ALL_AVAIL) + { + *len = eab->remaining; + eab->data = NULL; + eab->remaining = 0; + } + else + { + *len = requested; + eab->data += requested; + eab->remaining -= requested; + } + + if (eab->remaining) + return APR_SUCCESS; + } + + return APR_EAGAIN; +} + + +static apr_status_t +eagain_bucket_readline(serf_bucket_t *bucket, + int acceptable, + int *found, + const char **data, + apr_size_t *len) +{ + /* ### for now, we know callers won't use this function. */ + svn_error_clear(svn_error__malfunction(TRUE, __FILE__, __LINE__, + "Not implemented.")); + return APR_ENOTIMPL; +} + + +static apr_status_t +eagain_bucket_peek(serf_bucket_t *bucket, + const char **data, + apr_size_t *len) +{ + const eagain_baton_t *eab = bucket->data; + + *data = eab->data ? eab->data : ""; + *len = eab->remaining; + + return APR_SUCCESS; +} + + +static const serf_bucket_type_t delay_bucket_vtable = { + "BUF-EAGAIN", + eagain_bucket_read, + eagain_bucket_readline, + serf_default_read_iovec, + serf_default_read_for_sendfile, + serf_default_read_bucket, + eagain_bucket_peek, + serf_default_destroy_and_data, +}; + + +serf_bucket_t * +svn_ra_serf__create_bucket_with_eagain(const char *data, + apr_size_t len, + serf_bucket_alloc_t *allocator) +{ + eagain_baton_t *eab; + + eab = serf_bucket_mem_alloc(allocator, sizeof(*eab)); + eab->data = data; + eab->remaining = len; + + return serf_bucket_create(&delay_bucket_vtable, allocator, eab); +} diff --git a/subversion/libsvn_ra_serf/get_deleted_rev.c b/subversion/libsvn_ra_serf/get_deleted_rev.c index 40f6b1d..624854d 100644 --- a/subversion/libsvn_ra_serf/get_deleted_rev.c +++ b/subversion/libsvn_ra_serf/get_deleted_rev.c @@ -36,7 +36,7 @@ * This enum represents the current state of our XML parsing for a REPORT. */ enum drev_state_e { - INITIAL = 0, + INITIAL = XML_STATE_INITIAL, REPORT, VERSION_NAME }; @@ -75,11 +75,13 @@ getdrev_closed(svn_ra_serf__xml_estate_t *xes, apr_pool_t *scratch_pool) { drev_context_t *drev_ctx = baton; + apr_int64_t rev; SVN_ERR_ASSERT(leaving_state == VERSION_NAME); SVN_ERR_ASSERT(cdata != NULL); - *drev_ctx->revision_deleted = SVN_STR_TO_REV(cdata->data); + SVN_ERR(svn_cstring_atoi64(&rev, cdata->data)); + *drev_ctx->revision_deleted = (svn_revnum_t)rev; return SVN_NO_ERROR; } @@ -90,7 +92,8 @@ static svn_error_t * create_getdrev_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { serf_bucket_t *buckets; drev_context_t *drev_ctx = baton; @@ -101,7 +104,7 @@ create_getdrev_body(serf_bucket_t **body_bkt, "S:get-deleted-rev-report", "xmlns:S", SVN_XML_NAMESPACE, "xmlns:D", "DAV:", - NULL, NULL); + SVN_VA_NULL); svn_ra_serf__add_tag_buckets(buckets, "S:path", drev_ctx->path, @@ -146,23 +149,20 @@ svn_ra_serf__get_deleted_rev(svn_ra_session_t *session, drev_ctx->revision_deleted = revision_deleted; SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, - ras, NULL /* conn */, - NULL /* url */, peg_revision, + ras, NULL /* url */, peg_revision, pool, pool)); xmlctx = svn_ra_serf__xml_context_create(getdrev_ttable, NULL, getdrev_closed, NULL, drev_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(ras, xmlctx, NULL, pool); handler->method = "REPORT"; handler->path = req_url; handler->body_type = "text/xml"; handler->body_delegate = create_getdrev_body; handler->body_delegate_baton = drev_ctx; - handler->conn = ras->conns[0]; - handler->session = ras; err = svn_ra_serf__context_run_one(handler, pool); diff --git a/subversion/libsvn_ra_serf/get_file.c b/subversion/libsvn_ra_serf/get_file.c new file mode 100644 index 0000000..cb63b7d --- /dev/null +++ b/subversion/libsvn_ra_serf/get_file.c @@ -0,0 +1,425 @@ +/* + * get_file.c : entry point for update RA functions for ra_serf + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#include <apr_version.h> +#include <apr_want.h> + +#include <apr_uri.h> + +#include <serf.h> + +#include "svn_private_config.h" +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_ra.h" +#include "svn_delta.h" +#include "svn_path.h" +#include "svn_props.h" + +#include "private/svn_dep_compat.h" +#include "private/svn_string_private.h" + +#include "ra_serf.h" +#include "../libsvn_ra/ra_loader.h" + + + + +/* + * This structure represents a single request to GET (fetch) a file with + * its associated Serf session/connection. + */ +typedef struct stream_ctx_t { + + /* The handler representing this particular fetch. */ + svn_ra_serf__handler_t *handler; + + /* Have we read our response headers yet? */ + svn_boolean_t read_headers; + + svn_boolean_t using_compression; + + /* This flag is set when our response is aborted before we reach the + * end and we decide to requeue this request. + */ + svn_boolean_t aborted_read; + apr_off_t aborted_read_size; + + /* This is the amount of data that we have read so far. */ + apr_off_t read_size; + + /* If we're writing this file to a stream, this will be non-NULL. */ + svn_stream_t *result_stream; + +} stream_ctx_t; + + + +/** Routines called when we are fetching a file */ + +static svn_error_t * +headers_fetch(serf_bucket_t *headers, + void *baton, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) +{ + stream_ctx_t *fetch_ctx = baton; + + if (fetch_ctx->using_compression) + { + serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip"); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +cancel_fetch(serf_request_t *request, + serf_bucket_t *response, + int status_code, + void *baton) +{ + stream_ctx_t *fetch_ctx = baton; + + /* Uh-oh. Our connection died on us. + * + * The core ra_serf layer will requeue our request - we just need to note + * that we got cut off in the middle of our song. + */ + if (!response) + { + /* If we already started the fetch and opened the file handle, we need + * to hold subsequent read() ops until we get back to where we were + * before the close and we can then resume the textdelta() calls. + */ + if (fetch_ctx->read_headers) + { + if (!fetch_ctx->aborted_read && fetch_ctx->read_size) + { + fetch_ctx->aborted_read = TRUE; + fetch_ctx->aborted_read_size = fetch_ctx->read_size; + } + fetch_ctx->read_size = 0; + } + + return SVN_NO_ERROR; + } + + /* We have no idea what went wrong. */ + SVN_ERR_MALFUNCTION(); +} + + +/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents + * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is + * present in PROPS. + * + * Sets *FOUND_P to TRUE if file contents was successfuly fetched. + * + * Performs all temporary allocations in POOL. + */ +static svn_error_t * +try_get_wc_contents(svn_boolean_t *found_p, + svn_ra_serf__session_t *session, + const char *sha1_checksum_prop, + svn_stream_t *dst_stream, + apr_pool_t *pool) +{ + svn_checksum_t *checksum; + svn_stream_t *wc_stream; + svn_error_t *err; + + /* No contents found by default. */ + *found_p = FALSE; + + if (!session->wc_callbacks->get_wc_contents + || sha1_checksum_prop == NULL) + { + /* Nothing to do. */ + return SVN_NO_ERROR; + } + + SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, + sha1_checksum_prop, pool)); + + err = session->wc_callbacks->get_wc_contents( + session->wc_callback_baton, &wc_stream, checksum, pool); + + if (err) + { + svn_error_clear(err); + + /* Ignore errors for now. */ + return SVN_NO_ERROR; + } + + if (wc_stream) + { + SVN_ERR(svn_stream_copy3(wc_stream, + svn_stream_disown(dst_stream, pool), + NULL, NULL, pool)); + *found_p = TRUE; + } + + return SVN_NO_ERROR; +} + +/* ----------------------------------------------------------------------- + svn_ra_get_file() specific */ + +/* Implements svn_ra_serf__response_handler_t */ +static svn_error_t * +handle_stream(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + stream_ctx_t *fetch_ctx = handler_baton; + apr_status_t status; + + if (fetch_ctx->handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(fetch_ctx->handler)); + + while (1) + { + const char *data; + apr_size_t len; + + status = serf_bucket_read(response, 8000, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + + fetch_ctx->read_size += len; + + if (fetch_ctx->aborted_read) + { + apr_off_t skip; + + /* We haven't caught up to where we were before. */ + if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) + { + /* Eek. What did the file shrink or something? */ + if (APR_STATUS_IS_EOF(status)) + { + SVN_ERR_MALFUNCTION(); + } + + /* Skip on to the next iteration of this loop. */ + if (APR_STATUS_IS_EAGAIN(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + continue; + } + + /* Woo-hoo. We're back. */ + fetch_ctx->aborted_read = FALSE; + + /* Increment data and len by the difference. */ + skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size); + data += skip; + len -= (apr_size_t)skip; + } + + if (len) + { + apr_size_t written_len; + + written_len = len; + + SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, + &written_len)); + } + + if (status) + { + return svn_ra_serf__wrap_err(status, NULL); + } + } + /* not reached */ +} + +/* Baton for get_file_prop_cb */ +struct file_prop_baton_t +{ + apr_pool_t *result_pool; + svn_node_kind_t kind; + apr_hash_t *props; + const char *sha1_checksum; +}; + +/* Implements svn_ra_serf__prop_func_t for svn_ra_serf__get_file */ +static svn_error_t * +get_file_prop_cb(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct file_prop_baton_t *fb = baton; + const char *svn_name; + + if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) + { + const char *val = value->data; + + if (strcmp(val, "collection") == 0) + fb->kind = svn_node_dir; + else + fb->kind = svn_node_file; + + return SVN_NO_ERROR; + } + else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0 + && strcmp(name, "sha1-checksum") == 0) + { + fb->sha1_checksum = apr_pstrdup(fb->result_pool, value->data); + } + + if (!fb->props) + return SVN_NO_ERROR; + + svn_name = svn_ra_serf__svnname_from_wirename(ns, name, fb->result_pool); + if (svn_name) + { + svn_hash_sets(fb->props, svn_name, + svn_string_dup(value, fb->result_pool)); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__get_file(svn_ra_session_t *ra_session, + const char *path, + svn_revnum_t revision, + svn_stream_t *stream, + svn_revnum_t *fetched_rev, + apr_hash_t **props, + apr_pool_t *pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + const char *fetch_url; + const svn_ra_serf__dav_props_t *which_props; + svn_ra_serf__handler_t *propfind_handler; + struct file_prop_baton_t fb; + + /* Fetch properties. */ + + fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool); + + /* The simple case is if we want HEAD - then a GET on the fetch_url is fine. + * + * Otherwise, we need to get the baseline version for this particular + * revision and then fetch that file. + */ + if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) + { + SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev, + session, + fetch_url, revision, + pool, pool)); + revision = SVN_INVALID_REVNUM; + } + /* REVISION is always SVN_INVALID_REVNUM */ + SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); + + if (props) + which_props = all_props; + else if (stream && session->wc_callbacks->get_wc_contents) + which_props = type_and_checksum_props; + else + which_props = check_path_props; + + fb.result_pool = pool; + fb.props = props ? apr_hash_make(pool) : NULL; + fb.kind = svn_node_unknown; + fb.sha1_checksum = NULL; + + SVN_ERR(svn_ra_serf__create_propfind_handler(&propfind_handler, session, + fetch_url, SVN_INVALID_REVNUM, + "0", which_props, + get_file_prop_cb, &fb, + pool)); + + SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, pool)); + + /* Verify that resource type is not collection. */ + if (fb.kind != svn_node_file) + { + return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, + _("Can't get text contents of a directory")); + } + + if (props) + *props = fb.props; + + if (stream) + { + svn_boolean_t found; + SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream, pool)); + + /* No contents found in the WC, let's fetch from server. */ + if (!found) + { + stream_ctx_t *stream_ctx; + svn_ra_serf__handler_t *handler; + + /* Create the fetch context. */ + stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx)); + stream_ctx->result_stream = stream; + stream_ctx->using_compression = session->using_compression; + + handler = svn_ra_serf__create_handler(session, pool); + + handler->method = "GET"; + handler->path = fetch_url; + + handler->custom_accept_encoding = TRUE; + handler->no_dav_headers = TRUE; + + handler->header_delegate = headers_fetch; + handler->header_delegate_baton = stream_ctx; + + handler->response_handler = handle_stream; + handler->response_baton = stream_ctx; + + handler->response_error = cancel_fetch; + handler->response_error_baton = stream_ctx; + + stream_ctx->handler = handler; + + SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); + + if (handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + } + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_ra_serf/get_lock.c b/subversion/libsvn_ra_serf/get_lock.c new file mode 100644 index 0000000..24d7100 --- /dev/null +++ b/subversion/libsvn_ra_serf/get_lock.c @@ -0,0 +1,337 @@ +/* + * get_lock.c : obtain single lock information functions for ra_serf + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <apr_uri.h> +#include <serf.h> + +#include "svn_dav.h" +#include "svn_pools.h" +#include "svn_ra.h" + +#include "../libsvn_ra/ra_loader.h" +#include "svn_config.h" +#include "svn_path.h" +#include "svn_time.h" +#include "svn_private_config.h" + +#include "ra_serf.h" + + +/* + * This enum represents the current state of our XML parsing for a REPORT. + */ +enum { + INITIAL = 0, + MULTISTATUS, + RESPONSE, + PROPSTAT, + PROP, + LOCK_DISCOVERY, + ACTIVE_LOCK, + LOCK_TYPE, + LOCK_SCOPE, + DEPTH, + TIMEOUT, + LOCK_TOKEN, + OWNER, + HREF +}; + +typedef struct lock_info_t { + apr_pool_t *pool; + + const char *path; + + svn_lock_t *lock; + + svn_boolean_t read_headers; + + svn_ra_serf__handler_t *handler; + + /* The expat handler. We wrap this to do a bit more work. */ + svn_ra_serf__response_handler_t inner_handler; + void *inner_baton; + +} lock_info_t; + +#define D_ "DAV:" +#define S_ SVN_XML_NAMESPACE +static const svn_ra_serf__xml_transition_t locks_ttable[] = { + /* The INITIAL state can transition into D:prop (LOCK) or + to D:multistatus (PROPFIND) */ + { INITIAL, D_, "multistatus", MULTISTATUS, + FALSE, { NULL }, FALSE }, + + { MULTISTATUS, D_, "response", RESPONSE, + FALSE, { NULL }, FALSE }, + + { RESPONSE, D_, "propstat", PROPSTAT, + FALSE, { NULL }, FALSE }, + + { PROPSTAT, D_, "prop", PROP, + FALSE, { NULL }, FALSE }, + + { PROP, D_, "lockdiscovery", LOCK_DISCOVERY, + FALSE, { NULL }, FALSE }, + + { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK, + FALSE, { NULL }, FALSE }, + +#if 0 + /* ### we don't really need to parse locktype/lockscope. we know what + ### the values are going to be. we *could* validate that the only + ### possible children are D:write and D:exclusive. we'd need to + ### modify the state transition to tell us about all children + ### (ie. maybe support "*" for the name) and then validate. but it + ### just isn't important to validate, so disable this for now... */ + + { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE, + FALSE, { NULL }, FALSE }, + + { LOCK_TYPE, D_, "write", WRITE, + FALSE, { NULL }, TRUE }, + + { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE, + FALSE, { NULL }, FALSE }, + + { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE, + FALSE, { NULL }, TRUE }, +#endif /* 0 */ + + { ACTIVE_LOCK, D_, "timeout", TIMEOUT, + TRUE, { NULL }, TRUE }, + + { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN, + FALSE, { NULL }, FALSE }, + + { LOCK_TOKEN, D_, "href", HREF, + TRUE, { NULL }, TRUE }, + + { ACTIVE_LOCK, D_, "owner", OWNER, + TRUE, { NULL }, TRUE }, + + /* ACTIVE_LOCK has a D:depth child, but we can ignore that. */ + + { 0 } +}; + +static const int locks_expected_status[] = { + 207, + 0 +}; + +/* Conforms to svn_ra_serf__xml_closed_t */ +static svn_error_t * +locks_closed(svn_ra_serf__xml_estate_t *xes, + void *baton, + int leaving_state, + const svn_string_t *cdata, + apr_hash_t *attrs, + apr_pool_t *scratch_pool) +{ + lock_info_t *lock_ctx = baton; + + if (leaving_state == TIMEOUT) + { + if (strcasecmp(cdata->data, "Infinite") == 0) + lock_ctx->lock->expiration_date = 0; + else if (strncasecmp(cdata->data, "Second-", 7) == 0) + { + unsigned n; + SVN_ERR(svn_cstring_atoui(&n, cdata->data+7)); + + lock_ctx->lock->expiration_date = apr_time_now() + + apr_time_from_sec(n); + } + else + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Invalid LOCK timeout value '%s'"), + cdata->data); + } + else if (leaving_state == HREF) + { + if (cdata->len) + { + char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len); + + apr_collapse_spaces(buf, buf); + lock_ctx->lock->token = buf; + } + } + else if (leaving_state == OWNER) + { + if (cdata->len) + { + lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool, + cdata->data, cdata->len); + } + } + + return SVN_NO_ERROR; +} + +/* Implements svn_ra_serf__response_handler_t */ +static svn_error_t * +handle_lock(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + lock_info_t *ctx = handler_baton; + + if (!ctx->read_headers) + { + serf_bucket_t *headers; + const char *val; + + headers = serf_bucket_response_get_headers(response); + + val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER); + if (val) + { + ctx->lock->owner = apr_pstrdup(ctx->pool, val); + } + + val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER); + if (val) + { + SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val, + ctx->pool)); + } + + ctx->read_headers = TRUE; + } + + return ctx->inner_handler(request, response, ctx->inner_baton, pool); +} + +/* Implements svn_ra_serf__request_body_delegate_t */ +static svn_error_t * +create_getlock_body(serf_bucket_t **body_bkt, + void *baton, + serf_bucket_alloc_t *alloc, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) +{ + serf_bucket_t *buckets; + + buckets = serf_bucket_aggregate_create(alloc); + + svn_ra_serf__add_xml_header_buckets(buckets, alloc); + svn_ra_serf__add_open_tag_buckets(buckets, alloc, "propfind", + "xmlns", "DAV:", + SVN_VA_NULL); + svn_ra_serf__add_open_tag_buckets(buckets, alloc, "prop", SVN_VA_NULL); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, + "lockdiscovery", SVN_VA_NULL); + svn_ra_serf__add_close_tag_buckets(buckets, alloc, "prop"); + svn_ra_serf__add_close_tag_buckets(buckets, alloc, "propfind"); + + *body_bkt = buckets; + return SVN_NO_ERROR; +} + +static svn_error_t* +setup_getlock_headers(serf_bucket_t *headers, + void *baton, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) +{ + serf_bucket_headers_setn(headers, "Depth", "0"); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__get_lock(svn_ra_session_t *ra_session, + svn_lock_t **lock, + const char *path, + apr_pool_t *result_pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + svn_ra_serf__handler_t *handler; + svn_ra_serf__xml_context_t *xmlctx; + apr_pool_t *scratch_pool = svn_pool_create(result_pool); + lock_info_t *lock_ctx; + const char *req_url; + svn_error_t *err; + + req_url = svn_path_url_add_component2(session->session_url.path, path, + scratch_pool); + + lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx)); + lock_ctx->pool = result_pool; + lock_ctx->path = req_url; + lock_ctx->lock = svn_lock_create(result_pool); + lock_ctx->lock->path = apr_pstrdup(result_pool, path); + + xmlctx = svn_ra_serf__xml_context_create(locks_ttable, + NULL, locks_closed, NULL, + lock_ctx, + scratch_pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, + locks_expected_status, + scratch_pool); + + handler->method = "PROPFIND"; + handler->path = req_url; + handler->body_type = "text/xml"; + + handler->body_delegate = create_getlock_body; + handler->body_delegate_baton = lock_ctx; + + handler->header_delegate = setup_getlock_headers; + handler->header_delegate_baton = lock_ctx; + + handler->no_dav_headers = TRUE; + + lock_ctx->inner_handler = handler->response_handler; + lock_ctx->inner_baton = handler->response_baton; + handler->response_handler = handle_lock; + handler->response_baton = lock_ctx; + + lock_ctx->handler = handler; + + err = svn_ra_serf__context_run_one(handler, scratch_pool); + + if ((err && (handler->sline.code == 500 || handler->sline.code == 501)) + || svn_error_find_cause(err, SVN_ERR_UNSUPPORTED_FEATURE)) + return svn_error_trace( + svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, + _("Server does not support locking features"))); + else if (svn_error_find_cause(err, SVN_ERR_FS_NOT_FOUND)) + svn_error_clear(err); /* Behave like the other RA layers */ + else if (handler->sline.code != 207) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + + if (lock_ctx->lock && lock_ctx->lock->token) + *lock = lock_ctx->lock; + else + *lock = NULL; + + svn_pool_destroy(scratch_pool); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_ra_serf/getdate.c b/subversion/libsvn_ra_serf/getdate.c index cc1014e..30d57f3 100644 --- a/subversion/libsvn_ra_serf/getdate.c +++ b/subversion/libsvn_ra_serf/getdate.c @@ -44,7 +44,7 @@ * This enum represents the current state of our XML parsing for a REPORT. */ enum date_state_e { - INITIAL = 0, + INITIAL = XML_STATE_INITIAL, REPORT, VERSION_NAME }; @@ -82,11 +82,14 @@ date_closed(svn_ra_serf__xml_estate_t *xes, apr_pool_t *scratch_pool) { date_context_t *date_ctx = baton; + apr_int64_t rev; SVN_ERR_ASSERT(leaving_state == VERSION_NAME); SVN_ERR_ASSERT(cdata != NULL); - *date_ctx->revision = SVN_STR_TO_REV(cdata->data); + SVN_ERR(svn_cstring_atoi64(&rev, cdata->data)); + + *date_ctx->revision = (svn_revnum_t)rev; return SVN_NO_ERROR; } @@ -97,7 +100,8 @@ static svn_error_t * create_getdate_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { serf_bucket_t *buckets; date_context_t *date_ctx = baton; @@ -107,7 +111,7 @@ create_getdate_body(serf_bucket_t **body_bkt, svn_ra_serf__add_open_tag_buckets(buckets, alloc, "S:dated-rev-report", "xmlns:S", SVN_XML_NAMESPACE, "xmlns:D", "DAV:", - NULL); + SVN_VA_NULL); svn_ra_serf__add_tag_buckets(buckets, "D:" SVN_DAV__CREATIONDATE, @@ -131,40 +135,37 @@ svn_ra_serf__get_dated_revision(svn_ra_session_t *ra_session, svn_ra_serf__handler_t *handler; svn_ra_serf__xml_context_t *xmlctx; const char *report_target; - svn_error_t *err; date_ctx = apr_palloc(pool, sizeof(*date_ctx)); date_ctx->time = tm; date_ctx->revision = revision; - SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool)); + SVN_ERR(svn_ra_serf__report_resource(&report_target, session, pool)); xmlctx = svn_ra_serf__xml_context_create(date_ttable, NULL, date_closed, NULL, date_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); handler->method = "REPORT"; handler->path = report_target; handler->body_type = "text/xml"; - handler->conn = session->conns[0]; - handler->session = session; handler->body_delegate = create_getdate_body; handler->body_delegate_baton = date_ctx; *date_ctx->revision = SVN_INVALID_REVNUM; - err = svn_ra_serf__context_run_one(handler, pool); + SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); - SVN_ERR(svn_error_compose_create( - svn_ra_serf__error_on_status(handler->sline, - report_target, - handler->location), - err)); + if (handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); - SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*revision)); + if (!SVN_IS_VALID_REVNUM(*revision)) + return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, + _("The REPORT response did not include " + "the requested properties")); return SVN_NO_ERROR; } diff --git a/subversion/libsvn_ra_serf/getlocations.c b/subversion/libsvn_ra_serf/getlocations.c index 1ae4f82..063272e 100644 --- a/subversion/libsvn_ra_serf/getlocations.c +++ b/subversion/libsvn_ra_serf/getlocations.c @@ -43,7 +43,7 @@ * This enum represents the current state of our XML parsing for a REPORT. */ enum loc_state_e { - INITIAL = 0, + INITIAL = XML_STATE_INITIAL, REPORT, LOCATION }; @@ -94,7 +94,12 @@ getloc_closed(svn_ra_serf__xml_estate_t *xes, path = svn_hash_gets(attrs, "path"); if (revstr != NULL && path != NULL) { - svn_revnum_t rev = SVN_STR_TO_REV(revstr); + apr_int64_t rev_val; + svn_revnum_t rev; + + SVN_ERR(svn_cstring_atoi64(&rev_val, revstr)); + rev = (svn_revnum_t)rev_val; + apr_hash_set(loc_ctx->paths, apr_pmemdup(loc_ctx->pool, &rev, sizeof(rev)), sizeof(rev), apr_pstrdup(loc_ctx->pool, path)); @@ -109,7 +114,8 @@ static svn_error_t * create_get_locations_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { serf_bucket_t *buckets; loc_context_t *loc_ctx = baton; @@ -121,7 +127,7 @@ create_get_locations_body(serf_bucket_t **body_bkt, "S:get-locations", "xmlns:S", SVN_XML_NAMESPACE, "xmlns:D", "DAV:", - NULL); + SVN_VA_NULL); svn_ra_serf__add_tag_buckets(buckets, "S:path", loc_ctx->path, @@ -159,7 +165,6 @@ svn_ra_serf__get_locations(svn_ra_session_t *ra_session, svn_ra_serf__handler_t *handler; svn_ra_serf__xml_context_t *xmlctx; const char *req_url; - svn_error_t *err; loc_ctx = apr_pcalloc(pool, sizeof(*loc_ctx)); loc_ctx->pool = pool; @@ -171,31 +176,26 @@ svn_ra_serf__get_locations(svn_ra_session_t *ra_session, *locations = loc_ctx->paths; SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, - session, NULL /* conn */, - NULL /* url */, peg_revision, + session, NULL /* url */, peg_revision, pool, pool)); xmlctx = svn_ra_serf__xml_context_create(getloc_ttable, NULL, getloc_closed, NULL, loc_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); handler->method = "REPORT"; handler->path = req_url; handler->body_delegate = create_get_locations_body; handler->body_delegate_baton = loc_ctx; handler->body_type = "text/xml"; - handler->conn = session->conns[0]; - handler->session = session; - err = svn_ra_serf__context_run_one(handler, pool); + SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); - SVN_ERR(svn_error_compose_create( - svn_ra_serf__error_on_status(handler->sline, - req_url, - handler->location), - err)); + SVN_ERR(svn_ra_serf__error_on_status(handler->sline, + handler->path, + handler->location)); return SVN_NO_ERROR; } diff --git a/subversion/libsvn_ra_serf/getlocationsegments.c b/subversion/libsvn_ra_serf/getlocationsegments.c index e5c58a9..884ab5d 100644 --- a/subversion/libsvn_ra_serf/getlocationsegments.c +++ b/subversion/libsvn_ra_serf/getlocationsegments.c @@ -52,8 +52,8 @@ typedef struct gls_context_t { } gls_context_t; -enum { - INITIAL = 0, +enum locseg_state_e { + INITIAL = XML_STATE_INITIAL, REPORT, SEGMENT }; @@ -84,6 +84,8 @@ gls_closed(svn_ra_serf__xml_estate_t *xes, const char *path; const char *start_str; const char *end_str; + apr_int64_t start_val; + apr_int64_t end_val; svn_location_segment_t segment; SVN_ERR_ASSERT(leaving_state == SEGMENT); @@ -95,9 +97,12 @@ gls_closed(svn_ra_serf__xml_estate_t *xes, /* The transition table said these must exist. */ SVN_ERR_ASSERT(start_str && end_str); + SVN_ERR(svn_cstring_atoi64(&start_val, start_str)); + SVN_ERR(svn_cstring_atoi64(&end_val, end_str)); + segment.path = path; /* may be NULL */ - segment.range_start = SVN_STR_TO_REV(start_str); - segment.range_end = SVN_STR_TO_REV(end_str); + segment.range_start = (svn_revnum_t)start_val; + segment.range_end = (svn_revnum_t)end_val; SVN_ERR(gls_ctx->receiver(&segment, gls_ctx->receiver_baton, scratch_pool)); return SVN_NO_ERROR; @@ -109,7 +114,8 @@ static svn_error_t * create_gls_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { serf_bucket_t *buckets; gls_context_t *gls_ctx = baton; @@ -119,7 +125,7 @@ create_gls_body(serf_bucket_t **body_bkt, svn_ra_serf__add_open_tag_buckets(buckets, alloc, "S:get-location-segments", "xmlns:S", SVN_XML_NAMESPACE, - NULL); + SVN_VA_NULL); svn_ra_serf__add_tag_buckets(buckets, "S:path", gls_ctx->path, @@ -173,31 +179,29 @@ svn_ra_serf__get_location_segments(svn_ra_session_t *ra_session, gls_ctx->receiver_baton = receiver_baton; SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, - session, NULL /* conn */, - NULL /* url */, peg_revision, + session, NULL /* url */, peg_revision, pool, pool)); xmlctx = svn_ra_serf__xml_context_create(gls_ttable, NULL, gls_closed, NULL, gls_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); handler->method = "REPORT"; handler->path = req_url; handler->body_delegate = create_gls_body; handler->body_delegate_baton = gls_ctx; handler->body_type = "text/xml"; - handler->conn = session->conns[0]; - handler->session = session; err = svn_ra_serf__context_run_one(handler, pool); - err = svn_error_compose_create( - svn_ra_serf__error_on_status(handler->sline, - handler->path, - handler->location), - err); + if (!err) + { + err = svn_ra_serf__error_on_status(handler->sline, + handler->path, + handler->location); + } 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/getlocks.c b/subversion/libsvn_ra_serf/getlocks.c index df201a7..518d2aa 100644 --- a/subversion/libsvn_ra_serf/getlocks.c +++ b/subversion/libsvn_ra_serf/getlocks.c @@ -47,8 +47,8 @@ /* * This enum represents the current state of our XML parsing for a REPORT. */ -enum { - INITIAL = 0, +enum getlocks_state_e { + INITIAL = XML_STATE_INITIAL, REPORT, LOCK, PATH, @@ -213,7 +213,8 @@ static svn_error_t * create_getlocks_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { lock_context_t *lock_ctx = baton; serf_bucket_t *buckets; @@ -222,7 +223,7 @@ create_getlocks_body(serf_bucket_t **body_bkt, svn_ra_serf__add_open_tag_buckets( buckets, alloc, "S:get-locks-report", "xmlns:S", SVN_XML_NAMESPACE, - "depth", svn_depth_to_word(lock_ctx->requested_depth), NULL); + "depth", svn_depth_to_word(lock_ctx->requested_depth), SVN_VA_NULL); svn_ra_serf__add_close_tag_buckets(buckets, alloc, "S:get-locks-report"); *body_bkt = buckets; @@ -244,12 +245,11 @@ svn_ra_serf__get_locks(svn_ra_session_t *ra_session, svn_error_t *err; req_url = svn_path_url_add_component2(session->session_url.path, path, pool); - SVN_ERR(svn_ra_serf__get_relative_path(&rel_path, req_url, session, - NULL, pool)); + SVN_ERR(svn_ra_serf__get_relative_path(&rel_path, req_url, session, pool)); lock_ctx = apr_pcalloc(pool, sizeof(*lock_ctx)); lock_ctx->pool = pool; - lock_ctx->path = apr_pstrcat(pool, "/", rel_path, (char *)NULL); + lock_ctx->path = apr_pstrcat(pool, "/", rel_path, SVN_VA_NULL); lock_ctx->requested_depth = depth; lock_ctx->hash = apr_hash_make(pool); @@ -257,33 +257,41 @@ svn_ra_serf__get_locks(svn_ra_session_t *ra_session, NULL, getlocks_closed, NULL, lock_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); handler->method = "REPORT"; handler->path = req_url; handler->body_type = "text/xml"; - handler->conn = session->conns[0]; - handler->session = session; handler->body_delegate = create_getlocks_body; handler->body_delegate_baton = lock_ctx; err = svn_ra_serf__context_run_one(handler, pool); - - /* Wrap the server generated error for an unsupported report with the - documented error for this ra function. */ - if (svn_error_find_cause(err, SVN_ERR_UNSUPPORTED_FEATURE)) - err = svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, NULL); - - SVN_ERR(err); + + if (err) + { + if (svn_error_find_cause(err, SVN_ERR_UNSUPPORTED_FEATURE)) + { + /* The server told us that it doesn't support this report type. + We return the documented error for svn_ra_get_locks(), but + with the original error report */ + return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, NULL); + } + else if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + /* File doesn't exist in HEAD: Not an error */ + svn_error_clear(err); + } + else + return svn_error_trace(err); + } /* We get a 404 when a path doesn't exist in HEAD, but it might have existed earlier (E.g. 'svn ls http://s/svn/trunk/file@1' */ - if (handler->sline.code != 404) + if (handler->sline.code != 200 + && handler->sline.code != 404) { - SVN_ERR(svn_ra_serf__error_on_status(handler->sline, - handler->path, - handler->location)); + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); } *locks = lock_ctx->hash; diff --git a/subversion/libsvn_ra_serf/inherited_props.c b/subversion/libsvn_ra_serf/inherited_props.c index f1172e7..6edafb1 100644 --- a/subversion/libsvn_ra_serf/inherited_props.c +++ b/subversion/libsvn_ra_serf/inherited_props.c @@ -28,12 +28,14 @@ #include "svn_hash.h" #include "svn_path.h" #include "svn_ra.h" +#include "svn_sorts.h" #include "svn_string.h" #include "svn_xml.h" #include "svn_props.h" #include "svn_base64.h" #include "private/svn_dav_protocol.h" +#include "private/svn_sorts_private.h" #include "../libsvn_ra/ra_loader.h" #include "svn_private_config.h" #include "ra_serf.h" @@ -41,7 +43,7 @@ /* The current state of our XML parsing. */ typedef enum iprops_state_e { - INITIAL = 0, + INITIAL = XML_STATE_INITIAL, IPROPS_REPORT, IPROPS_ITEM, IPROPS_PATH, @@ -141,9 +143,7 @@ iprops_closed(svn_ra_serf__xml_estate_t *xes, return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); iprops_ctx->curr_iprop->path_or_url = - svn_path_url_add_component2(iprops_ctx->repos_root_url, - cdata->data, - iprops_ctx->pool); + apr_pstrdup(iprops_ctx->pool, cdata->data); } else if (leaving_state == IPROPS_PROPNAME) { @@ -197,7 +197,8 @@ static svn_error_t * create_iprops_body(serf_bucket_t **bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { iprops_context_t *iprops_ctx = baton; serf_bucket_t *body_bkt; @@ -207,7 +208,7 @@ create_iprops_body(serf_bucket_t **bkt, svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "S:" SVN_DAV__INHERITED_PROPS_REPORT, "xmlns:S", SVN_XML_NAMESPACE, - NULL); + SVN_VA_NULL); svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__REVISION, apr_ltoa(pool, iprops_ctx->revision), @@ -220,6 +221,131 @@ create_iprops_body(serf_bucket_t **bkt, return SVN_NO_ERROR; } +/* Per request information for get_iprops_via_more_requests */ +typedef struct iprop_rq_info_t +{ + const char *relpath; + const char *urlpath; + apr_hash_t *props; + svn_ra_serf__handler_t *handler; +} iprop_rq_info_t; + + +/* Assumes session reparented to the repository root. The old session + root is passed as session_url */ +static svn_error_t * +get_iprops_via_more_requests(svn_ra_session_t *ra_session, + apr_array_header_t **iprops, + const char *session_url, + const char *path, + svn_revnum_t revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + const char *url; + const char *relpath; + apr_array_header_t *rq_info; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_interval_time_t waittime_left = session->timeout; + const svn_revnum_t rev_marker = SVN_INVALID_REVNUM; + int i; + + rq_info = apr_array_make(scratch_pool, 16, sizeof(iprop_rq_info_t *)); + + if (!svn_path_is_empty(path)) + url = svn_path_url_add_component2(session_url, path, scratch_pool); + else + url = session_url; + + relpath = svn_uri_skip_ancestor(session->repos_root_str, url, scratch_pool); + + /* Create all requests */ + while (relpath[0] != '\0') + { + iprop_rq_info_t *rq = apr_pcalloc(scratch_pool, sizeof(*rq)); + + relpath = svn_relpath_dirname(relpath, scratch_pool); + + rq->relpath = relpath; + rq->props = apr_hash_make(scratch_pool); + + SVN_ERR(svn_ra_serf__get_stable_url(&rq->urlpath, NULL, session, + svn_path_url_add_component2( + session->repos_root.path, + relpath, scratch_pool), + revision, + scratch_pool, scratch_pool)); + + SVN_ERR(svn_ra_serf__create_propfind_handler( + &rq->handler, session, + rq->urlpath, + rev_marker, "0", all_props, + svn_ra_serf__deliver_svn_props, + rq->props, + scratch_pool)); + + /* Allow ignoring authz problems */ + rq->handler->no_fail_on_http_failure_status = TRUE; + + svn_ra_serf__request_create(rq->handler); + + APR_ARRAY_PUSH(rq_info, iprop_rq_info_t *) = rq; + } + + while (TRUE) + { + svn_pool_clear(iterpool); + + SVN_ERR(svn_ra_serf__context_run(session, &waittime_left, iterpool)); + + for (i = 0; i < rq_info->nelts; i++) + { + iprop_rq_info_t *rq = APR_ARRAY_IDX(rq_info, i, iprop_rq_info_t *); + + if (!rq->handler->done) + break; + } + + if (i >= rq_info->nelts) + break; /* All requests done */ + } + + *iprops = apr_array_make(result_pool, rq_info->nelts, + sizeof(svn_prop_inherited_item_t *)); + + /* And now create the result set */ + for (i = 0; i < rq_info->nelts; i++) + { + iprop_rq_info_t *rq = APR_ARRAY_IDX(rq_info, i, iprop_rq_info_t *); + apr_hash_t *node_props; + svn_prop_inherited_item_t *new_iprop; + + if (rq->handler->sline.code != 207 && rq->handler->sline.code != 403) + { + if (rq->handler->server_error) + SVN_ERR(svn_ra_serf__server_error_create(rq->handler, + scratch_pool)); + + return svn_error_trace(svn_ra_serf__unexpected_status(rq->handler)); + } + + node_props = rq->props; + + svn_ra_serf__keep_only_regular_props(node_props, scratch_pool); + + if (!apr_hash_count(node_props)) + continue; + + new_iprop = apr_palloc(result_pool, sizeof(*new_iprop)); + new_iprop->path_or_url = apr_pstrdup(result_pool, rq->relpath); + new_iprop->prop_hash = svn_prop_hash_dup(node_props, result_pool); + svn_sort__array_insert(*iprops, &new_iprop, 0); + } + + return SVN_NO_ERROR; +} + /* Request a inherited-props-report from the URL attached to RA_SESSION, and fill the IPROPS array hash with the results. */ svn_error_t * @@ -230,20 +356,53 @@ svn_ra_serf__get_inherited_props(svn_ra_session_t *ra_session, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_error_t *err; iprops_context_t *iprops_ctx; svn_ra_serf__session_t *session = ra_session->priv; svn_ra_serf__handler_t *handler; svn_ra_serf__xml_context_t *xmlctx; const char *req_url; + svn_boolean_t iprop_capable; + + SVN_ERR(svn_ra_serf__has_capability(ra_session, &iprop_capable, + SVN_RA_CAPABILITY_INHERITED_PROPS, + scratch_pool)); + + if (!iprop_capable) + { + svn_error_t *err; + const char *reparent_uri = NULL; + const char *session_uri; + const char *repos_root_url; + + SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &repos_root_url, + scratch_pool)); + + session_uri = apr_pstrdup(scratch_pool, session->session_url_str); + if (strcmp(repos_root_url, session->session_url_str) != 0) + { + reparent_uri = session_uri; + SVN_ERR(svn_ra_serf__reparent(ra_session, repos_root_url, + scratch_pool)); + } + + err = get_iprops_via_more_requests(ra_session, iprops, session_uri, path, + revision, result_pool, scratch_pool); + + if (reparent_uri) + err = svn_error_compose_create(err, + svn_ra_serf__reparent(ra_session, + reparent_uri , + scratch_pool)); + + return svn_error_trace(err); + } SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, session, - NULL /* conn */, NULL /* url */, revision, - result_pool, scratch_pool)); + scratch_pool, scratch_pool)); SVN_ERR_ASSERT(session->repos_root_str); @@ -258,26 +417,24 @@ svn_ra_serf__get_inherited_props(svn_ra_session_t *ra_session, iprops_ctx->revision = revision; xmlctx = svn_ra_serf__xml_context_create(iprops_table, - iprops_opened, iprops_closed, NULL, + iprops_opened, iprops_closed, + NULL, iprops_ctx, scratch_pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, + scratch_pool); handler->method = "REPORT"; handler->path = req_url; - handler->conn = session->conns[0]; - handler->session = session; + handler->body_delegate = create_iprops_body; handler->body_delegate_baton = iprops_ctx; handler->body_type = "text/xml"; - handler->handler_pool = scratch_pool; - - err = svn_ra_serf__context_run_one(handler, scratch_pool); - SVN_ERR(svn_error_compose_create( - svn_ra_serf__error_on_status(handler->sline, - handler->path, - handler->location), - err)); + + 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)); *iprops = iprops_ctx->iprops; diff --git a/subversion/libsvn_ra_serf/libsvn_ra_serf.pc.in b/subversion/libsvn_ra_serf/libsvn_ra_serf.pc.in new file mode 100644 index 0000000..e02ef12 --- /dev/null +++ b/subversion/libsvn_ra_serf/libsvn_ra_serf.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libsvn_ra_serf +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@ +Cflags: -I${includedir} diff --git a/subversion/libsvn_ra_serf/lock.c b/subversion/libsvn_ra_serf/lock.c new file mode 100644 index 0000000..dd045e3 --- /dev/null +++ b/subversion/libsvn_ra_serf/lock.c @@ -0,0 +1,679 @@ +/* + * lock.c : entry point for locking RA functions for ra_serf + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <apr_uri.h> +#include <serf.h> +#include <assert.h> + +#include "svn_dav.h" +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_ra.h" + +#include "../libsvn_ra/ra_loader.h" +#include "svn_config.h" +#include "svn_path.h" +#include "svn_sorts.h" +#include "svn_time.h" +#include "svn_private_config.h" +#include "private/svn_sorts_private.h" + +#include "ra_serf.h" + + +/* + * This enum represents the current state of our XML parsing for a REPORT. + */ +enum { + INITIAL = 0, + PROP, + LOCK_DISCOVERY, + ACTIVE_LOCK, + LOCK_TYPE, + LOCK_SCOPE, + DEPTH, + TIMEOUT, + LOCK_TOKEN, + OWNER, + HREF +}; + + +typedef struct lock_ctx_t { + apr_pool_t *pool; + + const char *path; + + const char *token; /* For unlock */ + svn_lock_t *lock; /* For lock */ + + svn_boolean_t force; + svn_revnum_t revision; + + svn_boolean_t read_headers; + + svn_ra_serf__handler_t *handler; + + /* The expat handler. We wrap this to do a bit more work. */ + svn_ra_serf__response_handler_t inner_handler; + void *inner_baton; + +} lock_ctx_t; + + +#define D_ "DAV:" +#define S_ SVN_XML_NAMESPACE +static const svn_ra_serf__xml_transition_t locks_ttable[] = { + /* The INITIAL state can transition into D:prop (LOCK) or + to D:multistatus (PROPFIND) */ + { INITIAL, D_, "prop", PROP, + FALSE, { NULL }, FALSE }, + + { PROP, D_, "lockdiscovery", LOCK_DISCOVERY, + FALSE, { NULL }, FALSE }, + + { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK, + FALSE, { NULL }, FALSE }, + +#if 0 + /* ### we don't really need to parse locktype/lockscope. we know what + ### the values are going to be. we *could* validate that the only + ### possible children are D:write and D:exclusive. we'd need to + ### modify the state transition to tell us about all children + ### (ie. maybe support "*" for the name) and then validate. but it + ### just isn't important to validate, so disable this for now... */ + + { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE, + FALSE, { NULL }, FALSE }, + + { LOCK_TYPE, D_, "write", WRITE, + FALSE, { NULL }, TRUE }, + + { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE, + FALSE, { NULL }, FALSE }, + + { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE, + FALSE, { NULL }, TRUE }, +#endif /* 0 */ + + { ACTIVE_LOCK, D_, "timeout", TIMEOUT, + TRUE, { NULL }, TRUE }, + + { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN, + FALSE, { NULL }, FALSE }, + + { LOCK_TOKEN, D_, "href", HREF, + TRUE, { NULL }, TRUE }, + + { ACTIVE_LOCK, D_, "owner", OWNER, + TRUE, { NULL }, TRUE }, + + /* ACTIVE_LOCK has a D:depth child, but we can ignore that. */ + + { 0 } +}; + +/* Conforms to svn_ra_serf__xml_closed_t */ +static svn_error_t * +locks_closed(svn_ra_serf__xml_estate_t *xes, + void *baton, + int leaving_state, + const svn_string_t *cdata, + apr_hash_t *attrs, + apr_pool_t *scratch_pool) +{ + lock_ctx_t *lock_ctx = baton; + + if (leaving_state == TIMEOUT) + { + /* This function just parses the result of our own lock request, + so on a normal server we will only encounter 'Infinite' here. */ + if (strcasecmp(cdata->data, "Infinite") == 0) + lock_ctx->lock->expiration_date = 0; + else if (strncasecmp(cdata->data, "Second-", 7) == 0) + { + unsigned n; + SVN_ERR(svn_cstring_atoui(&n, cdata->data+7)); + + lock_ctx->lock->expiration_date = apr_time_now() + + apr_time_from_sec(n); + } + else + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Invalid LOCK timeout value '%s'"), + cdata->data); + } + else if (leaving_state == HREF) + { + if (cdata->len) + { + char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len); + + apr_collapse_spaces(buf, buf); + lock_ctx->lock->token = buf; + } + } + else if (leaving_state == OWNER) + { + if (cdata->len) + { + lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool, + cdata->data, cdata->len); + } + } + + return SVN_NO_ERROR; +} + + +static svn_error_t * +set_lock_headers(serf_bucket_t *headers, + void *baton, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) +{ + lock_ctx_t *lock_ctx = baton; + + if (lock_ctx->force) + { + serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, + SVN_DAV_OPTION_LOCK_STEAL); + } + + if (SVN_IS_VALID_REVNUM(lock_ctx->revision)) + { + serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER, + apr_ltoa(pool, lock_ctx->revision)); + } + + return APR_SUCCESS; +} + +/* Helper function for svn_ra_serf__lock and svn_ra_serf__unlock */ +static svn_error_t * +run_locks(svn_ra_serf__session_t *sess, + apr_array_header_t *lock_ctxs, + svn_boolean_t locking, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool; + apr_interval_time_t waittime_left = sess->timeout; + + assert(sess->pending_error == SVN_NO_ERROR); + + iterpool = svn_pool_create(scratch_pool); + while (lock_ctxs->nelts) + { + int i; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool)); + + for (i = 0; i < lock_ctxs->nelts; i++) + { + lock_ctx_t *ctx = APR_ARRAY_IDX(lock_ctxs, i, lock_ctx_t *); + + if (ctx->handler->done) + { + svn_error_t *server_err = NULL; + svn_error_t *cb_err = NULL; + svn_error_t *err; + + if (ctx->handler->server_error) + server_err = svn_ra_serf__server_error_create(ctx->handler, iterpool); + + /* Api users expect specific error code to detect failures, + pass the rest to svn_ra_serf__error_on_status */ + switch (ctx->handler->sline.code) + { + case 200: + case 204: + err = NULL; /* (un)lock succeeded */ + break; + + case 400: + err = svn_error_createf(SVN_ERR_FS_NO_SUCH_LOCK, NULL, + _("No lock on path '%s' (%d %s)"), + ctx->path, + ctx->handler->sline.code, + ctx->handler->sline.reason); + break; + case 403: + /* ### 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); + break; + case 405: + err = svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, + NULL, + _("Path '%s' doesn't exist in " + "HEAD revision (%d %s)"), + ctx->path, + ctx->handler->sline.code, + 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); + break; + + case 404: + case 409: + case 500: + if (server_err) + { + /* Handle out of date, etc by just passing the server + error */ + err = NULL; + break; + } + + /* Fall through */ + default: + err = svn_ra_serf__unexpected_status(ctx->handler); + break; + } + + if (server_err && err && server_err->apr_err == err->apr_err) + err = svn_error_compose_create(server_err, err); + else + err = svn_error_compose_create(err, server_err); + + if (err + && !SVN_ERR_IS_UNLOCK_ERROR(err) + && !SVN_ERR_IS_LOCK_ERROR(err)) + { + /* If the error that we are going to report is just about the + POST unlock hook, we should first report that the operation + succeeded, or the repository and working copy will be + out of sync... */ + + if (lock_func && + err->apr_err == SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED) + { + err = svn_error_compose_create( + err, lock_func(lock_baton, ctx->path, locking, + NULL, NULL, ctx->pool)); + } + + return svn_error_trace(err); /* Don't go through callbacks */ + } + + if (lock_func) + { + svn_lock_t *report_lock = NULL; + + if (locking && ctx->lock->token) + report_lock = ctx->lock; + + cb_err = lock_func(lock_baton, ctx->path, locking, + report_lock, err, ctx->pool); + } + svn_error_clear(err); + + SVN_ERR(cb_err); + + waittime_left = sess->timeout; + svn_sort__array_delete(lock_ctxs, i, 1); + i--; + + svn_pool_destroy(ctx->pool); + continue; + } + } + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Implements svn_ra_serf__response_handler_t */ +static svn_error_t * +handle_lock(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *pool) +{ + lock_ctx_t *ctx = handler_baton; + + if (!ctx->read_headers) + { + serf_bucket_t *headers; + const char *val; + + headers = serf_bucket_response_get_headers(response); + + val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER); + if (val) + { + ctx->lock->owner = apr_pstrdup(ctx->pool, val); + } + + val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER); + if (val) + { + SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val, + ctx->pool)); + } + + ctx->read_headers = TRUE; + } + + return ctx->inner_handler(request, response, ctx->inner_baton, pool); +} + +/* Implements svn_ra_serf__request_body_delegate_t */ +static svn_error_t * +create_lock_body(serf_bucket_t **body_bkt, + void *baton, + serf_bucket_alloc_t *alloc, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) +{ + lock_ctx_t *ctx = baton; + serf_bucket_t *buckets; + + buckets = serf_bucket_aggregate_create(alloc); + + svn_ra_serf__add_xml_header_buckets(buckets, alloc); + svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo", + "xmlns", "DAV:", + SVN_VA_NULL); + + svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", SVN_VA_NULL); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, "exclusive", SVN_VA_NULL); + svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope"); + + svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", SVN_VA_NULL); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, "write", SVN_VA_NULL); + svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype"); + + if (ctx->lock->comment) + { + svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment, + alloc); + } + + svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo"); + + *body_bkt = buckets; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__lock(svn_ra_session_t *ra_session, + apr_hash_t *path_revs, + const char *comment, + svn_boolean_t force, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + apr_array_header_t *lock_requests; + + lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_revs), + sizeof(lock_ctx_t*)); + + /* ### Perhaps we should open more connections than just one? See update.c */ + + iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, path_revs); + hi; + hi = apr_hash_next(hi)) + { + svn_ra_serf__handler_t *handler; + svn_ra_serf__xml_context_t *xmlctx; + const char *req_url; + lock_ctx_t *lock_ctx; + apr_pool_t *lock_pool; + + svn_pool_clear(iterpool); + + lock_pool = svn_pool_create(scratch_pool); + lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx)); + + lock_ctx->pool = lock_pool; + lock_ctx->path = apr_hash_this_key(hi); + lock_ctx->revision = *((svn_revnum_t*)apr_hash_this_val(hi)); + lock_ctx->lock = svn_lock_create(lock_pool); + lock_ctx->lock->path = lock_ctx->path; + lock_ctx->lock->comment = comment; + + lock_ctx->force = force; + req_url = svn_path_url_add_component2(session->session_url.path, + lock_ctx->path, lock_pool); + + xmlctx = svn_ra_serf__xml_context_create(locks_ttable, + NULL, locks_closed, NULL, + lock_ctx, + lock_pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, + lock_pool); + + handler->method = "LOCK"; + handler->path = req_url; + handler->body_type = "text/xml"; + + /* Same stupid algorithm from get_best_connection() in update.c */ + handler->conn = session->conns[session->cur_conn]; + session->cur_conn++; + + if (session->cur_conn >= session->num_conns) + session->cur_conn = 0; + + handler->header_delegate = set_lock_headers; + handler->header_delegate_baton = lock_ctx; + + handler->body_delegate = create_lock_body; + handler->body_delegate_baton = lock_ctx; + + lock_ctx->inner_handler = handler->response_handler; + lock_ctx->inner_baton = handler->response_baton; + handler->response_handler = handle_lock; + handler->response_baton = lock_ctx; + + handler->no_fail_on_http_failure_status = TRUE; + + lock_ctx->handler = handler; + + APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx; + + svn_ra_serf__request_create(handler); + } + + SVN_ERR(run_locks(session, lock_requests, TRUE, lock_func, lock_baton, + iterpool)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +static svn_error_t * +set_unlock_headers(serf_bucket_t *headers, + void *baton, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) +{ + lock_ctx_t *ctx = baton; + + serf_bucket_headers_set(headers, "Lock-Token", ctx->token); + if (ctx->force) + { + serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, + SVN_DAV_OPTION_LOCK_BREAK); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__unlock(svn_ra_session_t *ra_session, + apr_hash_t *path_tokens, + svn_boolean_t force, + svn_ra_lock_callback_t lock_func, + void *lock_baton, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + apr_array_header_t *lock_requests; + + iterpool = svn_pool_create(scratch_pool); + + /* If we are stealing locks we need the lock tokens */ + if (force) + { + /* Theoretically this part can be improved (for performance) by using + svn_ra_get_locks() to obtain all the locks in a single request, but + do we really want to improve the performance of + $ svn unlock --force * + */ + + for (hi = apr_hash_first(scratch_pool, path_tokens); + hi; + hi = apr_hash_next(hi)) + { + const char *path; + const char *token; + svn_lock_t *existing_lock; + svn_error_t *err; + + svn_pool_clear(iterpool); + + path = apr_hash_this_key(hi); + token = apr_hash_this_val(hi); + + if (token && token[0]) + continue; + + if (session->cancel_func) + SVN_ERR(session->cancel_func(session->cancel_baton)); + + err = svn_ra_serf__get_lock(ra_session, &existing_lock, path, + iterpool); + + if (!err && existing_lock) + { + svn_hash_sets(path_tokens, path, + apr_pstrdup(scratch_pool, existing_lock->token)); + continue; + } + + err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, err, + _("'%s' is not locked in the repository"), + path); + + if (lock_func) + { + svn_error_t *err2; + err2 = lock_func(lock_baton, path, FALSE, NULL, err, iterpool); + svn_error_clear(err); + + SVN_ERR(err2); + } + else + { + svn_error_clear(err); + } + + svn_hash_sets(path_tokens, path, NULL); + } + } + + /* ### Perhaps we should open more connections than just one? See update.c */ + + lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_tokens), + sizeof(lock_ctx_t*)); + + for (hi = apr_hash_first(scratch_pool, path_tokens); + hi; + hi = apr_hash_next(hi)) + { + svn_ra_serf__handler_t *handler; + const char *req_url, *token; + lock_ctx_t *lock_ctx; + apr_pool_t *lock_pool; + + svn_pool_clear(iterpool); + + lock_pool = svn_pool_create(scratch_pool); + lock_ctx = apr_pcalloc(lock_pool, sizeof(*lock_ctx)); + + lock_ctx->pool = lock_pool; + + lock_ctx->path = apr_hash_this_key(hi); + token = apr_hash_this_val(hi); + + lock_ctx->force = force; + lock_ctx->token = apr_pstrcat(lock_pool, "<", token, ">", SVN_VA_NULL); + + req_url = svn_path_url_add_component2(session->session_url.path, lock_ctx->path, + lock_pool); + + handler = svn_ra_serf__create_handler(session, lock_pool); + + handler->method = "UNLOCK"; + handler->path = req_url; + + handler->header_delegate = set_unlock_headers; + handler->header_delegate_baton = lock_ctx; + + handler->response_handler = svn_ra_serf__expect_empty_body; + handler->response_baton = handler; + + handler->no_fail_on_http_failure_status = TRUE; + + lock_ctx->handler = handler; + + APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx; + + svn_ra_serf__request_create(handler); + } + + SVN_ERR(run_locks(session, lock_requests, FALSE, lock_func, lock_baton, + iterpool)); + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_ra_serf/locks.c b/subversion/libsvn_ra_serf/locks.c deleted file mode 100644 index 252c301..0000000 --- a/subversion/libsvn_ra_serf/locks.c +++ /dev/null @@ -1,669 +0,0 @@ -/* - * locks.c : entry point for locking RA functions for ra_serf - * - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - */ - - - -#include <apr_uri.h> -#include <serf.h> - -#include "svn_dav.h" -#include "svn_pools.h" -#include "svn_ra.h" - -#include "../libsvn_ra/ra_loader.h" -#include "svn_config.h" -#include "svn_path.h" -#include "svn_time.h" -#include "svn_private_config.h" - -#include "ra_serf.h" - - -/* - * This enum represents the current state of our XML parsing for a REPORT. - */ -enum { - INITIAL = 0, - MULTISTATUS, - RESPONSE, - PROPSTAT, - PROP, - LOCK_DISCOVERY, - ACTIVE_LOCK, - LOCK_TYPE, - LOCK_SCOPE, - DEPTH, - TIMEOUT, - LOCK_TOKEN, - OWNER, - HREF -}; - -typedef struct lock_info_t { - apr_pool_t *pool; - - const char *path; - - svn_lock_t *lock; - - svn_boolean_t force; - svn_revnum_t revision; - - svn_boolean_t read_headers; - - svn_ra_serf__handler_t *handler; - - /* The expat handler. We wrap this to do a bit more work. */ - svn_ra_serf__response_handler_t inner_handler; - void *inner_baton; - -} lock_info_t; - -#define D_ "DAV:" -#define S_ SVN_XML_NAMESPACE -static const svn_ra_serf__xml_transition_t locks_ttable[] = { - /* The INITIAL state can transition into D:prop (LOCK) or - to D:multistatus (PROPFIND) */ - { INITIAL, D_, "prop", PROP, - FALSE, { NULL }, FALSE }, - { INITIAL, D_, "multistatus", MULTISTATUS, - FALSE, { NULL }, FALSE }, - - { MULTISTATUS, D_, "response", RESPONSE, - FALSE, { NULL }, FALSE }, - - { RESPONSE, D_, "propstat", PROPSTAT, - FALSE, { NULL }, FALSE }, - - { PROPSTAT, D_, "prop", PROP, - FALSE, { NULL }, FALSE }, - - { PROP, D_, "lockdiscovery", LOCK_DISCOVERY, - FALSE, { NULL }, FALSE }, - - { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK, - FALSE, { NULL }, FALSE }, - -#if 0 - /* ### we don't really need to parse locktype/lockscope. we know what - ### the values are going to be. we *could* validate that the only - ### possible children are D:write and D:exclusive. we'd need to - ### modify the state transition to tell us about all children - ### (ie. maybe support "*" for the name) and then validate. but it - ### just isn't important to validate, so disable this for now... */ - - { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE, - FALSE, { NULL }, FALSE }, - - { LOCK_TYPE, D_, "write", WRITE, - FALSE, { NULL }, TRUE }, - - { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE, - FALSE, { NULL }, FALSE }, - - { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE, - FALSE, { NULL }, TRUE }, -#endif /* 0 */ - - { ACTIVE_LOCK, D_, "timeout", TIMEOUT, - TRUE, { NULL }, TRUE }, - - { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN, - FALSE, { NULL }, FALSE }, - - { LOCK_TOKEN, D_, "href", HREF, - TRUE, { NULL }, TRUE }, - - { ACTIVE_LOCK, D_, "owner", OWNER, - TRUE, { NULL }, TRUE }, - - /* ACTIVE_LOCK has a D:depth child, but we can ignore that. */ - - { 0 } -}; - - -/* Conforms to svn_ra_serf__xml_closed_t */ -static svn_error_t * -locks_closed(svn_ra_serf__xml_estate_t *xes, - void *baton, - int leaving_state, - const svn_string_t *cdata, - apr_hash_t *attrs, - apr_pool_t *scratch_pool) -{ - lock_info_t *lock_ctx = baton; - - if (leaving_state == TIMEOUT) - { - if (strcasecmp(cdata->data, "Infinite") == 0) - lock_ctx->lock->expiration_date = 0; - else if (strncasecmp(cdata->data, "Second-", 7) == 0) - { - unsigned n; - SVN_ERR(svn_cstring_atoui(&n, cdata->data+7)); - - lock_ctx->lock->expiration_date = apr_time_now() + - apr_time_from_sec(n); - } - else - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Invalid LOCK timeout value '%s'"), - cdata->data); - } - else if (leaving_state == HREF) - { - if (cdata->len) - { - char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len); - - apr_collapse_spaces(buf, buf); - lock_ctx->lock->token = buf; - } - } - else if (leaving_state == OWNER) - { - if (cdata->len) - { - lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool, - cdata->data, cdata->len); - } - } - - return SVN_NO_ERROR; -} - - -static svn_error_t * -set_lock_headers(serf_bucket_t *headers, - void *baton, - apr_pool_t *pool) -{ - lock_info_t *lock_ctx = baton; - - if (lock_ctx->force) - { - serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, - SVN_DAV_OPTION_LOCK_STEAL); - } - - if (SVN_IS_VALID_REVNUM(lock_ctx->revision)) - { - serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER, - apr_ltoa(pool, lock_ctx->revision)); - } - - return APR_SUCCESS; -} - - -/* Register an error within the session. If something is already there, - then it will take precedence. */ -static svn_error_t * -determine_error(svn_ra_serf__handler_t *handler, - svn_error_t *err) -{ - { - apr_status_t errcode; - - if (handler->sline.code == 423) - errcode = SVN_ERR_FS_PATH_ALREADY_LOCKED; - else if (handler->sline.code == 403) - errcode = SVN_ERR_RA_DAV_FORBIDDEN; - else - return err; - - /* Client-side or server-side error already. Return it. */ - if (err != NULL) - return err; - - /* The server did not send us a detailed human-readable error. - Provide a generic error. */ - err = svn_error_createf(errcode, NULL, - _("Lock request failed: %d %s"), - handler->sline.code, - handler->sline.reason); - } - - return err; -} - - -/* Implements svn_ra_serf__response_handler_t */ -static svn_error_t * -handle_lock(serf_request_t *request, - serf_bucket_t *response, - void *handler_baton, - apr_pool_t *pool) -{ - lock_info_t *ctx = handler_baton; - - /* 403 (Forbidden) when a lock doesn't exist. - 423 (Locked) when a lock already exists. */ - if (ctx->handler->sline.code == 403 - || ctx->handler->sline.code == 423) - { - /* Go look in the body for a server-provided error. This will - reset flags for the core handler to Do The Right Thing. We - won't be back to this handler again. */ - return svn_error_trace(svn_ra_serf__expect_empty_body( - request, response, ctx->handler, pool)); - } - - if (!ctx->read_headers) - { - serf_bucket_t *headers; - const char *val; - - headers = serf_bucket_response_get_headers(response); - - val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER); - if (val) - { - ctx->lock->owner = apr_pstrdup(ctx->pool, val); - } - - val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER); - if (val) - { - SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val, - ctx->pool)); - } - - ctx->read_headers = TRUE; - } - - return ctx->inner_handler(request, response, ctx->inner_baton, pool); -} - -/* Implements svn_ra_serf__request_body_delegate_t */ -static svn_error_t * -create_getlock_body(serf_bucket_t **body_bkt, - void *baton, - serf_bucket_alloc_t *alloc, - apr_pool_t *pool) -{ - serf_bucket_t *buckets; - - buckets = serf_bucket_aggregate_create(alloc); - - svn_ra_serf__add_xml_header_buckets(buckets, alloc); - svn_ra_serf__add_open_tag_buckets(buckets, alloc, "propfind", - "xmlns", "DAV:", - NULL); - svn_ra_serf__add_open_tag_buckets(buckets, alloc, "prop", NULL); - svn_ra_serf__add_tag_buckets(buckets, "lockdiscovery", NULL, alloc); - svn_ra_serf__add_close_tag_buckets(buckets, alloc, "prop"); - svn_ra_serf__add_close_tag_buckets(buckets, alloc, "propfind"); - - *body_bkt = buckets; - return SVN_NO_ERROR; -} - -static svn_error_t* -setup_getlock_headers(serf_bucket_t *headers, - void *baton, - apr_pool_t *pool) -{ - serf_bucket_headers_setn(headers, "Depth", "0"); - - return SVN_NO_ERROR; -} - -/* Implements svn_ra_serf__request_body_delegate_t */ -static svn_error_t * -create_lock_body(serf_bucket_t **body_bkt, - void *baton, - serf_bucket_alloc_t *alloc, - apr_pool_t *pool) -{ - lock_info_t *ctx = baton; - serf_bucket_t *buckets; - - buckets = serf_bucket_aggregate_create(alloc); - - svn_ra_serf__add_xml_header_buckets(buckets, alloc); - svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo", - "xmlns", "DAV:", - NULL); - - svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", NULL); - svn_ra_serf__add_tag_buckets(buckets, "exclusive", NULL, alloc); - svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope"); - - svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", NULL); - svn_ra_serf__add_tag_buckets(buckets, "write", NULL, alloc); - svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype"); - - if (ctx->lock->comment) - { - svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment, - alloc); - } - - svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo"); - - *body_bkt = buckets; - return SVN_NO_ERROR; -} - -svn_error_t * -svn_ra_serf__get_lock(svn_ra_session_t *ra_session, - svn_lock_t **lock, - const char *path, - apr_pool_t *result_pool) -{ - svn_ra_serf__session_t *session = ra_session->priv; - svn_ra_serf__handler_t *handler; - svn_ra_serf__xml_context_t *xmlctx; - apr_pool_t *scratch_pool = svn_pool_create(result_pool); - lock_info_t *lock_ctx; - const char *req_url; - svn_error_t *err; - - req_url = svn_path_url_add_component2(session->session_url.path, path, - scratch_pool); - - lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx)); - lock_ctx->pool = result_pool; - lock_ctx->path = req_url; - lock_ctx->lock = svn_lock_create(result_pool); - lock_ctx->lock->path = apr_pstrdup(result_pool, path); - - xmlctx = svn_ra_serf__xml_context_create(locks_ttable, - NULL, locks_closed, NULL, - lock_ctx, - scratch_pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool); - - handler->method = "PROPFIND"; - handler->path = req_url; - handler->body_type = "text/xml"; - handler->conn = session->conns[0]; - handler->session = session; - - handler->body_delegate = create_getlock_body; - handler->body_delegate_baton = lock_ctx; - - handler->header_delegate = setup_getlock_headers; - handler->header_delegate_baton = lock_ctx; - - lock_ctx->inner_handler = handler->response_handler; - lock_ctx->inner_baton = handler->response_baton; - handler->response_handler = handle_lock; - handler->response_baton = lock_ctx; - - lock_ctx->handler = handler; - - err = svn_ra_serf__context_run_one(handler, scratch_pool); - err = determine_error(handler, err); - - if (handler->sline.code == 404) - { - return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, err, - _("Malformed URL for repository")); - } - if (err) - { - /* TODO Shh. We're telling a white lie for now. */ - return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err, - _("Server does not support locking features")); - } - - if (lock_ctx->lock && lock_ctx->lock->token) - *lock = lock_ctx->lock; - else - *lock = NULL; - - svn_pool_destroy(scratch_pool); - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_ra_serf__lock(svn_ra_session_t *ra_session, - apr_hash_t *path_revs, - const char *comment, - svn_boolean_t force, - svn_ra_lock_callback_t lock_func, - void *lock_baton, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__session_t *session = ra_session->priv; - apr_hash_index_t *hi; - apr_pool_t *iterpool; - - iterpool = svn_pool_create(scratch_pool); - - /* ### TODO for issue 2263: Send all the locks over the wire at once. This - ### loop is just a temporary shim. - ### an alternative, which is backwards-compat with all servers is to - ### pipeline these requests. ie. stop using run_wait/run_one. */ - - for (hi = apr_hash_first(scratch_pool, path_revs); - hi; - hi = apr_hash_next(hi)) - { - svn_ra_serf__handler_t *handler; - svn_ra_serf__xml_context_t *xmlctx; - const char *req_url; - lock_info_t *lock_ctx; - svn_error_t *err; - svn_error_t *new_err = NULL; - - svn_pool_clear(iterpool); - - lock_ctx = apr_pcalloc(iterpool, sizeof(*lock_ctx)); - - lock_ctx->pool = iterpool; - lock_ctx->path = svn__apr_hash_index_key(hi); - lock_ctx->revision = *((svn_revnum_t*)svn__apr_hash_index_val(hi)); - lock_ctx->lock = svn_lock_create(iterpool); - lock_ctx->lock->path = lock_ctx->path; - lock_ctx->lock->comment = comment; - - lock_ctx->force = force; - req_url = svn_path_url_add_component2(session->session_url.path, - lock_ctx->path, iterpool); - - xmlctx = svn_ra_serf__xml_context_create(locks_ttable, - NULL, locks_closed, NULL, - lock_ctx, - iterpool); - handler = svn_ra_serf__create_expat_handler(xmlctx, iterpool); - - handler->method = "LOCK"; - handler->path = req_url; - handler->body_type = "text/xml"; - handler->conn = session->conns[0]; - handler->session = session; - - handler->header_delegate = set_lock_headers; - handler->header_delegate_baton = lock_ctx; - - handler->body_delegate = create_lock_body; - handler->body_delegate_baton = lock_ctx; - - lock_ctx->inner_handler = handler->response_handler; - lock_ctx->inner_baton = handler->response_baton; - handler->response_handler = handle_lock; - handler->response_baton = lock_ctx; - - lock_ctx->handler = handler; - - err = svn_ra_serf__context_run_one(handler, iterpool); - err = determine_error(handler, err); - - if (lock_func) - new_err = lock_func(lock_baton, lock_ctx->path, TRUE, lock_ctx->lock, - err, iterpool); - svn_error_clear(err); - - SVN_ERR(new_err); - } - - svn_pool_destroy(iterpool); - - return SVN_NO_ERROR; -} - -struct unlock_context_t { - const char *token; - svn_boolean_t force; -}; - -static svn_error_t * -set_unlock_headers(serf_bucket_t *headers, - void *baton, - apr_pool_t *pool) -{ - struct unlock_context_t *ctx = baton; - - serf_bucket_headers_set(headers, "Lock-Token", ctx->token); - if (ctx->force) - { - serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, - SVN_DAV_OPTION_LOCK_BREAK); - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_ra_serf__unlock(svn_ra_session_t *ra_session, - apr_hash_t *path_tokens, - svn_boolean_t force, - svn_ra_lock_callback_t lock_func, - void *lock_baton, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__session_t *session = ra_session->priv; - apr_hash_index_t *hi; - apr_pool_t *iterpool; - - iterpool = svn_pool_create(scratch_pool); - - /* ### TODO for issue 2263: Send all the locks over the wire at once. This - ### loop is just a temporary shim. - ### an alternative, which is backwards-compat with all servers is to - ### pipeline these requests. ie. stop using run_wait/run_one. */ - - for (hi = apr_hash_first(scratch_pool, path_tokens); - hi; - hi = apr_hash_next(hi)) - { - svn_ra_serf__handler_t *handler; - const char *req_url, *path, *token; - svn_lock_t *existing_lock = NULL; - struct unlock_context_t unlock_ctx; - svn_error_t *err = NULL; - svn_error_t *new_err = NULL; - - - svn_pool_clear(iterpool); - - path = svn__apr_hash_index_key(hi); - token = svn__apr_hash_index_val(hi); - - if (force && (!token || token[0] == '\0')) - { - SVN_ERR(svn_ra_serf__get_lock(ra_session, &existing_lock, path, - iterpool)); - token = existing_lock ? existing_lock->token : NULL; - if (!token) - { - err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, NULL, - _("'%s' is not locked in the repository"), - path); - - if (lock_func) - { - svn_error_t *err2; - err2 = lock_func(lock_baton, path, FALSE, NULL, err, - iterpool); - svn_error_clear(err); - err = NULL; - if (err2) - return svn_error_trace(err2); - } - else - { - svn_error_clear(err); - err = NULL; - } - continue; - } - } - - unlock_ctx.force = force; - unlock_ctx.token = apr_pstrcat(iterpool, "<", token, ">", (char *)NULL); - - req_url = svn_path_url_add_component2(session->session_url.path, path, - iterpool); - - handler = apr_pcalloc(iterpool, sizeof(*handler)); - - handler->handler_pool = iterpool; - handler->method = "UNLOCK"; - handler->path = req_url; - handler->conn = session->conns[0]; - handler->session = session; - - handler->header_delegate = set_unlock_headers; - handler->header_delegate_baton = &unlock_ctx; - - handler->response_handler = svn_ra_serf__expect_empty_body; - handler->response_baton = handler; - - SVN_ERR(svn_ra_serf__context_run_one(handler, iterpool)); - - switch (handler->sline.code) - { - case 204: - break; /* OK */ - case 403: - /* Api users expect this specific error code to detect failures */ - err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL, - _("Unlock request failed: %d %s"), - handler->sline.code, - handler->sline.reason); - break; - default: - err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, - _("Unlock request failed: %d %s"), - handler->sline.code, - handler->sline.reason); - } - - if (lock_func) - new_err = lock_func(lock_baton, path, FALSE, existing_lock, err, - iterpool); - - svn_error_clear(err); - SVN_ERR(new_err); - } - - svn_pool_destroy(iterpool); - - return SVN_NO_ERROR; -} diff --git a/subversion/libsvn_ra_serf/log.c b/subversion/libsvn_ra_serf/log.c index 02f2f29..773ae5c 100644 --- a/subversion/libsvn_ra_serf/log.c +++ b/subversion/libsvn_ra_serf/log.c @@ -22,7 +22,8 @@ */ - + + #include <apr_uri.h> #include <serf.h> @@ -44,12 +45,13 @@ #include "ra_serf.h" #include "../libsvn_ra/ra_loader.h" - + + /* * This enum represents the current state of our XML parsing for a REPORT. */ -enum { - INITIAL = 0, +enum log_state_e { + INITIAL = XML_STATE_INITIAL, REPORT, ITEM, VERSION, @@ -145,7 +147,8 @@ static const svn_ra_serf__xml_transition_t log_ttable[] = { { 0 } }; - + + /* Store CDATA into REVPROPS, associated with PROPNAME. If ENCODING is not NULL, then it must base "base64" and CDATA will be decoded first. @@ -207,12 +210,14 @@ collect_path(apr_hash_t *paths, copyfrom_rev = svn_hash_gets(attrs, "copyfrom-rev"); if (copyfrom_path && copyfrom_rev) { - svn_revnum_t rev = SVN_STR_TO_REV(copyfrom_rev); + apr_int64_t rev; + + SVN_ERR(svn_cstring_atoi64(&rev, copyfrom_rev)); - if (SVN_IS_VALID_REVNUM(rev)) + if (SVN_IS_VALID_REVNUM((svn_revnum_t)rev)) { lcp->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path); - lcp->copyfrom_rev = rev; + lcp->copyfrom_rev = (svn_revnum_t)rev; } } @@ -296,7 +301,12 @@ log_closed(svn_ra_serf__xml_estate_t *xes, rev_str = svn_hash_gets(attrs, "revision"); if (rev_str) - log_entry->revision = SVN_STR_TO_REV(rev_str); + { + apr_int64_t rev; + + SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); + log_entry->revision = (svn_revnum_t)rev; + } else log_entry->revision = SVN_INVALID_REVNUM; @@ -397,12 +407,13 @@ log_closed(svn_ra_serf__xml_estate_t *xes, return SVN_NO_ERROR; } - +/* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_log_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { serf_bucket_t *buckets; log_context_t *log_ctx = baton; @@ -412,7 +423,7 @@ create_log_body(serf_bucket_t **body_bkt, svn_ra_serf__add_open_tag_buckets(buckets, alloc, "S:log-report", "xmlns:S", SVN_XML_NAMESPACE, - NULL); + SVN_VA_NULL); svn_ra_serf__add_tag_buckets(buckets, "S:start-revision", @@ -432,23 +443,22 @@ create_log_body(serf_bucket_t **body_bkt, if (log_ctx->changed_paths) { - svn_ra_serf__add_tag_buckets(buckets, - "S:discover-changed-paths", NULL, - alloc); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, + "S:discover-changed-paths", + SVN_VA_NULL); } if (log_ctx->strict_node_history) { - svn_ra_serf__add_tag_buckets(buckets, - "S:strict-node-history", NULL, - alloc); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, + "S:strict-node-history", SVN_VA_NULL); } if (log_ctx->include_merged_revisions) { - svn_ra_serf__add_tag_buckets(buckets, - "S:include-merged-revisions", NULL, - alloc); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, + "S:include-merged-revisions", + SVN_VA_NULL); } if (log_ctx->revprops) @@ -463,16 +473,14 @@ create_log_body(serf_bucket_t **body_bkt, } if (log_ctx->revprops->nelts == 0) { - svn_ra_serf__add_tag_buckets(buckets, - "S:no-revprops", NULL, - alloc); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, + "S:no-revprops", SVN_VA_NULL); } } else { - svn_ra_serf__add_tag_buckets(buckets, - "S:all-revprops", NULL, - alloc); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, + "S:all-revprops", SVN_VA_NULL); } if (log_ctx->paths) @@ -487,9 +495,8 @@ create_log_body(serf_bucket_t **body_bkt, } } - svn_ra_serf__add_tag_buckets(buckets, - "S:encode-binary-props", NULL, - alloc); + svn_ra_serf__add_empty_tag_buckets(buckets, alloc, + "S:encode-binary-props", SVN_VA_NULL); svn_ra_serf__add_close_tag_buckets(buckets, alloc, "S:log-report"); @@ -518,7 +525,6 @@ svn_ra_serf__get_log(svn_ra_session_t *ra_session, svn_ra_serf__xml_context_t *xmlctx; svn_boolean_t want_custom_revprops; svn_revnum_t peg_rev; - svn_error_t *err; const char *req_url; log_ctx = apr_pcalloc(pool, sizeof(*log_ctx)); @@ -574,7 +580,7 @@ svn_ra_serf__get_log(svn_ra_session_t *ra_session, peg_rev = (start == SVN_INVALID_REVNUM || start > end) ? start : end; SVN_ERR(svn_ra_serf__get_stable_url(&req_url, NULL /* latest_revnum */, - session, NULL /* conn */, + session, NULL /* url */, peg_rev, pool, pool)); @@ -582,23 +588,18 @@ svn_ra_serf__get_log(svn_ra_session_t *ra_session, log_opened, log_closed, NULL, log_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); handler->method = "REPORT"; handler->path = req_url; handler->body_delegate = create_log_body; handler->body_delegate_baton = log_ctx; handler->body_type = "text/xml"; - handler->conn = session->conns[0]; - handler->session = session; - err = svn_ra_serf__context_run_one(handler, pool); + SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); - SVN_ERR(svn_error_compose_create( + return svn_error_trace( svn_ra_serf__error_on_status(handler->sline, req_url, - handler->location), - err)); - - return SVN_NO_ERROR; + handler->location)); } diff --git a/subversion/libsvn_ra_serf/merge.c b/subversion/libsvn_ra_serf/merge.c index 670e421..0a2fd54 100644 --- a/subversion/libsvn_ra_serf/merge.c +++ b/subversion/libsvn_ra_serf/merge.c @@ -48,7 +48,7 @@ * This enum represents the current state of our XML parsing for a MERGE. */ typedef enum merge_state_e { - INITIAL = 0, + INITIAL = XML_STATE_INITIAL, MERGE_RESPONSE, UPDATED_SET, RESPONSE, @@ -65,7 +65,7 @@ typedef enum merge_state_e { AUTHOR, POST_COMMIT_ERR, - PROP_VAL + STATUS } merge_state_e; @@ -171,7 +171,12 @@ merge_closed(svn_ra_serf__xml_estate_t *xes, rev_str = svn_hash_gets(attrs, "revision"); if (rev_str) - merge_ctx->commit_info->revision = SVN_STR_TO_REV(rev_str); + { + apr_int64_t rev; + + SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); + merge_ctx->commit_info->revision = (svn_revnum_t)rev; + } else merge_ctx->commit_info->revision = SVN_INVALID_REVNUM; @@ -266,7 +271,8 @@ merge_closed(svn_ra_serf__xml_estate_t *xes, static svn_error_t * setup_merge_headers(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { merge_context_t *ctx = baton; @@ -294,7 +300,7 @@ svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens, svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock-token-list", "xmlns:S", SVN_XML_NAMESPACE, - NULL); + SVN_VA_NULL); for (hi = apr_hash_first(pool, lock_tokens); hi; @@ -313,9 +319,9 @@ svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens, if (parent && !svn_relpath_skip_ancestor(parent, key)) continue; - svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock", NULL); + svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock", SVN_VA_NULL); - svn_ra_serf__add_open_tag_buckets(body, alloc, "lock-path", NULL); + svn_ra_serf__add_open_tag_buckets(body, alloc, "lock-path", SVN_VA_NULL); svn_ra_serf__add_cdata_len_buckets(body, alloc, path.data, path.len); svn_ra_serf__add_close_tag_buckets(body, alloc, "lock-path"); @@ -327,11 +333,13 @@ svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens, svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock-token-list"); } +/* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t* create_merge_body(serf_bucket_t **bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { merge_context_t *ctx = baton; serf_bucket_t *body_bkt; @@ -341,9 +349,9 @@ create_merge_body(serf_bucket_t **bkt, svn_ra_serf__add_xml_header_buckets(body_bkt, alloc); svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:merge", "xmlns:D", "DAV:", - NULL); - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:source", NULL); - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL); + SVN_VA_NULL); + svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:source", SVN_VA_NULL); + svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", SVN_VA_NULL); svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc, ctx->merge_resource_url, @@ -352,19 +360,26 @@ create_merge_body(serf_bucket_t **bkt, svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href"); svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:source"); - svn_ra_serf__add_tag_buckets(body_bkt, "D:no-auto-merge", NULL, alloc); - svn_ra_serf__add_tag_buckets(body_bkt, "D:no-checkout", NULL, alloc); - - svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL); - svn_ra_serf__add_tag_buckets(body_bkt, "D:checked-in", NULL, alloc); - svn_ra_serf__add_tag_buckets(body_bkt, "D:" SVN_DAV__VERSION_NAME, NULL, alloc); - svn_ra_serf__add_tag_buckets(body_bkt, "D:resourcetype", NULL, alloc); - svn_ra_serf__add_tag_buckets(body_bkt, "D:" SVN_DAV__CREATIONDATE, NULL, alloc); - svn_ra_serf__add_tag_buckets(body_bkt, "D:creator-displayname", NULL, alloc); + svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, + "D:no-auto-merge", SVN_VA_NULL); + svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, + "D:no-checkout", SVN_VA_NULL); + + svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", SVN_VA_NULL); + svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, + "D:checked-in", SVN_VA_NULL); + svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, + "D:" SVN_DAV__VERSION_NAME, SVN_VA_NULL); + svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, + "D:resourcetype", SVN_VA_NULL); + svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, + "D:" SVN_DAV__CREATIONDATE, SVN_VA_NULL); + svn_ra_serf__add_empty_tag_buckets(body_bkt, alloc, + "D:creator-displayname", SVN_VA_NULL); svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop"); - svn_ra_serf__merge_lock_token_list(ctx->lock_tokens, NULL, body_bkt, alloc, - pool); + svn_ra_serf__merge_lock_token_list(ctx->lock_tokens, NULL, body_bkt, + alloc, pool); svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:merge"); @@ -376,9 +391,7 @@ create_merge_body(serf_bucket_t **bkt, svn_error_t * svn_ra_serf__run_merge(const svn_commit_info_t **commit_info, - int *response_code, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, const char *merge_resource_url, apr_hash_t *lock_tokens, svn_boolean_t keep_locks, @@ -407,14 +420,14 @@ svn_ra_serf__run_merge(const svn_commit_info_t **commit_info, NULL, merge_closed, NULL, merge_ctx, scratch_pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, + scratch_pool); handler->method = "MERGE"; handler->path = merge_ctx->merge_url; handler->body_delegate = create_merge_body; handler->body_delegate_baton = merge_ctx; - handler->conn = conn; - handler->session = session; + handler->body_type = "text/xml"; handler->header_delegate = setup_merge_headers; handler->header_delegate_baton = merge_ctx; @@ -423,8 +436,21 @@ svn_ra_serf__run_merge(const svn_commit_info_t **commit_info, 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)); + *commit_info = merge_ctx->commit_info; - *response_code = handler->sline.code; + + /* Sanity check (Reported to be triggered by CodePlex's svnbridge) */ + if (! SVN_IS_VALID_REVNUM(merge_ctx->commit_info->revision)) + { + return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, + _("The MERGE response did not include " + "a new revision")); + } + + merge_ctx->commit_info->repos_root = apr_pstrdup(result_pool, + session->repos_root_str); return SVN_NO_ERROR; } diff --git a/subversion/libsvn_ra_serf/mergeinfo.c b/subversion/libsvn_ra_serf/mergeinfo.c index bd4fcba..589ff07 100644 --- a/subversion/libsvn_ra_serf/mergeinfo.c +++ b/subversion/libsvn_ra_serf/mergeinfo.c @@ -41,7 +41,7 @@ /* The current state of our XML parsing. */ typedef enum mergeinfo_state_e { - INITIAL = 0, + INITIAL = XML_STATE_INITIAL, MERGEINFO_REPORT, MERGEINFO_ITEM, MERGEINFO_PATH, @@ -130,12 +130,13 @@ mergeinfo_closed(svn_ra_serf__xml_estate_t *xes, return SVN_NO_ERROR; } - +/* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_mergeinfo_body(serf_bucket_t **bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { mergeinfo_context_t *mergeinfo_ctx = baton; serf_bucket_t *body_bkt; @@ -145,7 +146,7 @@ create_mergeinfo_body(serf_bucket_t **bkt, svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "S:" SVN_DAV__MERGEINFO_REPORT, "xmlns:S", SVN_XML_NAMESPACE, - NULL); + SVN_VA_NULL); svn_ra_serf__add_tag_buckets(body_bkt, "S:" SVN_DAV__REVISION, @@ -191,7 +192,6 @@ svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session, svn_boolean_t include_descendants, apr_pool_t *pool) { - svn_error_t *err; mergeinfo_context_t *mergeinfo_ctx; svn_ra_serf__session_t *session = ra_session->priv; svn_ra_serf__handler_t *handler; @@ -201,7 +201,7 @@ svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session, *catalog = NULL; SVN_ERR(svn_ra_serf__get_stable_url(&path, NULL /* latest_revnum */, - session, NULL /* conn */, + session, NULL /* url */, revision, pool, pool)); @@ -217,24 +217,21 @@ svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session, NULL, mergeinfo_closed, NULL, mergeinfo_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); handler->method = "REPORT"; handler->path = path; - handler->conn = session->conns[0]; - handler->session = session; + handler->body_delegate = create_mergeinfo_body; handler->body_delegate_baton = mergeinfo_ctx; handler->body_type = "text/xml"; - err = svn_ra_serf__context_run_one(handler, pool); + SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); - SVN_ERR(svn_error_compose_create( - svn_ra_serf__error_on_status(handler->sline, handler->path, - handler->location), - err)); + SVN_ERR(svn_ra_serf__error_on_status(handler->sline, handler->path, + handler->location)); - if (handler->done && apr_hash_count(mergeinfo_ctx->result_catalog)) + if (apr_hash_count(mergeinfo_ctx->result_catalog)) *catalog = mergeinfo_ctx->result_catalog; return SVN_NO_ERROR; diff --git a/subversion/libsvn_ra_serf/multistatus.c b/subversion/libsvn_ra_serf/multistatus.c new file mode 100644 index 0000000..9c269c3 --- /dev/null +++ b/subversion/libsvn_ra_serf/multistatus.c @@ -0,0 +1,750 @@ +/* + * multistatus.c : parse multistatus (error) responses. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include <assert.h> + +#include <apr.h> + +#include <serf.h> +#include <serf_bucket_types.h> + +#include "svn_private_config.h" +#include "svn_hash.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_string.h" +#include "svn_xml.h" +#include "svn_props.h" +#include "svn_dirent_uri.h" + +#include "private/svn_dep_compat.h" +#include "private/svn_fspath.h" + +#include "ra_serf.h" + +/* The current state of our XML parsing. */ +typedef enum iprops_state_e { + INITIAL = XML_STATE_INITIAL, + MS_MULTISTATUS, + + MS_RESPONSE, + MS_RESPONSE_HREF, + + MS_PROPSTAT, + MS_PROPSTAT_PROP, + MS_PROPSTAT_PROP_NAME, + MS_PROPSTAT_STATUS, + MS_PROPSTAT_RESPONSEDESCRIPTION, + MS_PROPSTAT_ERROR, + MS_PROPSTAT_ERROR_HUMANREADABLE, + + MS_RESPONSE_STATUS, + MS_RESPONSE_RESPONSEDESCRIPTION, + MS_RESPONSE_ERROR, + MS_RESPONSE_ERROR_HUMANREADABLE, + + MS_MULTISTATUS_RESPONSEDESCRIPTION, + + D_ERROR, + S_ERROR, + M_ERROR_HUMANREADABLE +} iprops_state_e; + +/* + <D:multistatus xmlns:D="DAV:"> + <D:response> + <D:href>http://something</D:href> + <!-- Possibly multiple D:href elements --> + <D:status>HTTP/1.1 500 failed</D:status> + <D:error> + <S:human-readable code="12345"> + Some Subversion error + </S:human-readable> + </D:error> + <D:responsedescription> + Human readable description + </D:responsedescription> + <D:location>http://redirected</D:location> + </D:response> + ... + </D:multistatus> + + Or for property operations: + + <D:multistatus xmlns:D="DAV:"> + <D:response> + <D:href>http://somewhere-else</D:href> + <D:propstat> + <D:propname><C:myprop /></D:propname> + <D:status>HTTP/1.1 499 failed</D:status> + <D:error> + <S:human-readable code="12345"> + Some Subversion error + </S:human-readable> + </D:error> + <D:responsedescription> + Human readable description + </D:responsedescription> + </D:propstat> + <D:status>HTTP/1.1 499 failed</D:status> + <D:error> + <S:human-readable code="12345"> + Some Subversion error + </S:human-readable> + </D:error> + <D:responsedescription> + Human readable description + </D:responsedescription> + <D:location>http://redirected</D:location> + <D:responsedescription> + Global description + <D:responsedescription> + </D:multistatus> + + Or on request failures + <D:error> + <X:some-error xmlns="QQ" /> + <D:human-readable code="12345"> + Some Subversion error + </D:human-readable> + </D:error> + */ + +#define D_ "DAV:" +#define S_ SVN_XML_NAMESPACE +#define M_ "http://apache.org/dav/xmlns" +static const svn_ra_serf__xml_transition_t multistatus_ttable[] = { + { INITIAL, D_, "multistatus", MS_MULTISTATUS, + FALSE, { NULL }, FALSE }, + + { MS_MULTISTATUS, D_, "responsedescription", MS_MULTISTATUS_RESPONSEDESCRIPTION, + TRUE, { NULL }, TRUE }, + + /* <response> */ + { MS_MULTISTATUS, D_, "response", MS_RESPONSE, + FALSE, { NULL }, TRUE }, + + { MS_RESPONSE, D_, "href", MS_RESPONSE_HREF, + TRUE, { NULL }, TRUE }, + + /* <propstat> */ + { MS_RESPONSE, D_, "propstat", MS_PROPSTAT, + FALSE, { NULL }, TRUE }, + + { MS_PROPSTAT, D_, "prop", MS_PROPSTAT_PROP, + FALSE, { NULL }, FALSE }, + + { MS_PROPSTAT_PROP, "", "*", MS_PROPSTAT_PROP_NAME, + FALSE, { NULL }, FALSE }, + + { MS_PROPSTAT, D_, "status", MS_PROPSTAT_STATUS, + TRUE, { NULL }, TRUE }, + + { MS_PROPSTAT, D_, "responsedescription", MS_PROPSTAT_RESPONSEDESCRIPTION, + TRUE, { NULL }, TRUE }, + + { MS_PROPSTAT, D_, "error", MS_PROPSTAT_ERROR, + FALSE, { NULL }, FALSE }, + + { MS_PROPSTAT_ERROR, M_, "human-readable", MS_PROPSTAT_ERROR_HUMANREADABLE, + TRUE, { "?errcode", NULL }, TRUE }, + /* </propstat> */ + + + { MS_RESPONSE, D_, "status", MS_RESPONSE_STATUS, + TRUE, { NULL }, TRUE }, + + { MS_RESPONSE, D_, "responsedescription", MS_RESPONSE_RESPONSEDESCRIPTION, + TRUE, { NULL }, TRUE }, + + { MS_RESPONSE, D_, "error", MS_RESPONSE_ERROR, + FALSE, { NULL }, TRUE }, + + { MS_RESPONSE_ERROR, M_, "human-readable", MS_RESPONSE_ERROR_HUMANREADABLE, + TRUE, { "?errcode", NULL }, TRUE }, + + /* </response> */ + + { MS_MULTISTATUS, D_, "responsedescription", MS_MULTISTATUS_RESPONSEDESCRIPTION, + TRUE, { NULL }, TRUE }, + + + { INITIAL, D_, "error", D_ERROR, + FALSE, { NULL }, TRUE }, + + { D_ERROR, S_, "error", S_ERROR, + FALSE, { NULL }, FALSE }, + + { D_ERROR, M_, "human-readable", M_ERROR_HUMANREADABLE, + TRUE, { "?errcode", NULL }, TRUE }, + + { 0 } +}; + +/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric + status code into *STATUS_CODE_OUT. Ignores leading whitespace. */ +static svn_error_t * +parse_status_line(int *status_code_out, + const char **reason, + const char *status_line, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + const char *token; + char *tok_status; + svn_stringbuf_t *temp_buf = svn_stringbuf_create(status_line, scratch_pool); + + svn_stringbuf_strip_whitespace(temp_buf); + token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status); + if (token) + token = apr_strtok(NULL, " \t\r\n", &tok_status); + if (!token) + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Malformed DAV:status '%s'"), + status_line); + err = svn_cstring_atoi(status_code_out, token); + if (err) + return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err, + _("Malformed DAV:status '%s'"), + status_line); + + token = apr_strtok(NULL, " \t\r\n", &tok_status); + + *reason = apr_pstrdup(result_pool, token); + + return SVN_NO_ERROR; +} + + +typedef struct error_item_t +{ + const char *path; + const char *propname; + + int http_status; + const char *http_reason; + apr_status_t apr_err; + + const char *message; +} error_item_t; + +static svn_error_t * +multistatus_opened(svn_ra_serf__xml_estate_t *xes, + void *baton, + int entered_state, + const svn_ra_serf__dav_props_t *tag, + apr_pool_t *scratch_pool) +{ + /*struct svn_ra_serf__server_error_t *server_error = baton;*/ + const char *propname; + + switch (entered_state) + { + case MS_PROPSTAT_PROP_NAME: + if (strcmp(tag->xmlns, SVN_DAV_PROP_NS_SVN) == 0) + propname = apr_pstrcat(scratch_pool, SVN_PROP_PREFIX, tag->name, + SVN_VA_NULL); + else + propname = tag->name; + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "propname", propname); + break; + case S_ERROR: + /* This toggles an has error boolean in libsvn_ra_neon in 1.7 */ + break; + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +multistatus_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) +{ + struct svn_ra_serf__server_error_t *server_error = baton; + const char *errcode; + const char *status; + + switch (leaving_state) + { + case MS_RESPONSE_HREF: + { + apr_status_t result; + apr_uri_t uri; + + result = apr_uri_parse(scratch_pool, cdata->data, &uri); + if (result) + return svn_ra_serf__wrap_err(result, NULL); + svn_ra_serf__xml_note(xes, MS_RESPONSE, "path", + svn_urlpath__canonicalize(uri.path, scratch_pool)); + } + break; + case MS_RESPONSE_STATUS: + svn_ra_serf__xml_note(xes, MS_RESPONSE, "status", cdata->data); + break; + case MS_RESPONSE_ERROR_HUMANREADABLE: + svn_ra_serf__xml_note(xes, MS_RESPONSE, "human-readable", cdata->data); + errcode = svn_hash_gets(attrs, "errcode"); + if (errcode) + svn_ra_serf__xml_note(xes, MS_RESPONSE, "errcode", errcode); + break; + case MS_RESPONSE: + if ((status = svn_hash__get_cstring(attrs, "status", NULL)) != NULL) + { + error_item_t *item; + + item = apr_pcalloc(server_error->pool, sizeof(*item)); + + item->path = apr_pstrdup(server_error->pool, + svn_hash_gets(attrs, "path")); + + SVN_ERR(parse_status_line(&item->http_status, + &item->http_reason, + status, + server_error->pool, + scratch_pool)); + + /* Do we have a mod_dav specific message? */ + item->message = svn_hash_gets(attrs, "human-readable"); + + if (item->message) + { + if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL) + { + apr_int64_t val; + + SVN_ERR(svn_cstring_atoi64(&val, errcode)); + item->apr_err = (apr_status_t)val; + } + + item->message = apr_pstrdup(server_error->pool, item->message); + } + else + item->message = apr_pstrdup(server_error->pool, + svn_hash_gets(attrs, "description")); + + APR_ARRAY_PUSH(server_error->items, error_item_t *) = item; + } + break; + + + case MS_PROPSTAT_STATUS: + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "status", cdata->data); + break; + case MS_PROPSTAT_ERROR_HUMANREADABLE: + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "human-readable", cdata->data); + errcode = svn_hash_gets(attrs, "errcode"); + if (errcode) + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "errcode", errcode); + break; + case MS_PROPSTAT_RESPONSEDESCRIPTION: + svn_ra_serf__xml_note(xes, MS_PROPSTAT, "description", + cdata->data); + break; + + case MS_PROPSTAT: + if ((status = svn_hash__get_cstring(attrs, "status", NULL)) != NULL) + { + apr_hash_t *response_attrs; + error_item_t *item; + + response_attrs = svn_ra_serf__xml_gather_since(xes, MS_RESPONSE); + item = apr_pcalloc(server_error->pool, sizeof(*item)); + + item->path = apr_pstrdup(server_error->pool, + svn_hash_gets(response_attrs, "path")); + item->propname = apr_pstrdup(server_error->pool, + svn_hash_gets(attrs, "propname")); + + SVN_ERR(parse_status_line(&item->http_status, + &item->http_reason, + status, + server_error->pool, + scratch_pool)); + + /* Do we have a mod_dav specific message? */ + item->message = svn_hash_gets(attrs, "human-readable"); + + if (item->message) + { + if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL) + { + apr_int64_t val; + + SVN_ERR(svn_cstring_atoi64(&val, errcode)); + item->apr_err = (apr_status_t)val; + } + + item->message = apr_pstrdup(server_error->pool, item->message); + } + else + item->message = apr_pstrdup(server_error->pool, + svn_hash_gets(attrs, "description")); + + + APR_ARRAY_PUSH(server_error->items, error_item_t *) = item; + } + break; + + case M_ERROR_HUMANREADABLE: + svn_ra_serf__xml_note(xes, D_ERROR, "human-readable", cdata->data); + errcode = svn_hash_gets(attrs, "errcode"); + if (errcode) + svn_ra_serf__xml_note(xes, D_ERROR, "errcode", errcode); + break; + + case D_ERROR: + { + error_item_t *item; + + item = apr_pcalloc(server_error->pool, sizeof(*item)); + + item->http_status = server_error->handler->sline.code; + + /* Do we have a mod_dav specific message? */ + item->message = svn_hash__get_cstring(attrs, "human-readable", + NULL); + + if (item->message) + { + if ((errcode = svn_hash_gets(attrs, "errcode")) != NULL) + { + apr_int64_t val; + + SVN_ERR(svn_cstring_atoi64(&val, errcode)); + item->apr_err = (apr_status_t)val; + } + + item->message = apr_pstrdup(server_error->pool, item->message); + } + + + APR_ARRAY_PUSH(server_error->items, error_item_t *) = item; + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__server_error_create(svn_ra_serf__handler_t *handler, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__server_error_t *server_error = handler->server_error; + svn_error_t *err = NULL; + int i; + + for (i = 0; i < server_error->items->nelts; i++) + { + const error_item_t *item; + apr_status_t status; + const char *message; + svn_error_t *new_err; + + item = APR_ARRAY_IDX(server_error->items, i, error_item_t *); + + if (!item->apr_err && item->http_status == 200) + { + continue; /* Success code */ + } + else if (!item->apr_err && item->http_status == 424 && item->propname) + { + continue; /* Failed because other PROPPATCH operations failed */ + } + + if (item->apr_err) + status = item->apr_err; + else + switch (item->http_status) + { + case 0: + continue; /* Not an error */ + case 301: + case 302: + case 303: + case 307: + case 308: + status = SVN_ERR_RA_DAV_RELOCATED; + break; + case 403: + status = SVN_ERR_RA_DAV_FORBIDDEN; + break; + case 404: + status = SVN_ERR_FS_NOT_FOUND; + break; + case 409: + status = SVN_ERR_FS_CONFLICT; + break; + case 412: + status = SVN_ERR_RA_DAV_PRECONDITION_FAILED; + break; + case 423: + status = SVN_ERR_FS_NO_LOCK_TOKEN; + break; + case 500: + status = SVN_ERR_RA_DAV_REQUEST_FAILED; + break; + case 501: + status = SVN_ERR_UNSUPPORTED_FEATURE; + break; + default: + if (err) + status = err->apr_err; /* Just use previous */ + else + status = SVN_ERR_RA_DAV_REQUEST_FAILED; + break; + } + + if (item->message && *item->message) + { + svn_stringbuf_t *sb = svn_stringbuf_create(item->message, + scratch_pool); + + svn_stringbuf_strip_whitespace(sb); + message = sb->data; + } + else if (item->propname) + { + message = apr_psprintf(scratch_pool, + _("Property operation on '%s' failed"), + item->propname); + } + else + { + /* Yuck: Older servers sometimes assume that we get convertable + apr error codes, while mod_dav_svn just produces a blank + text error, because err->message is NULL. */ + serf_status_line sline; + svn_error_t *tmp_err; + + memset(&sline, 0, sizeof(sline)); + sline.code = item->http_status; + sline.reason = item->http_reason; + + tmp_err = svn_ra_serf__error_on_status(sline, item->path, NULL); + + message = (tmp_err && tmp_err->message) + ? apr_pstrdup(scratch_pool, tmp_err->message) + : _("<blank error>"); + svn_error_clear(tmp_err); + } + + SVN_ERR_ASSERT(status > 0); + new_err = svn_error_create(status, NULL, message); + + if (item->propname) + new_err = svn_error_createf(new_err->apr_err, new_err, + _("While handling the '%s' property on '%s':"), + item->propname, item->path); + else if (item->path) + new_err = svn_error_createf(new_err->apr_err, new_err, + _("While handling the '%s' path:"), + item->path); + + err = svn_error_compose_create( + err, + new_err); + } + + /* Theoretically a 207 status can have a 'global' description without a + global STATUS that summarizes the final result of property/href + operations. + + We should wrap that around the existing errors if there is one. + + But currently I don't see how mod_dav ever sets that value */ + + if (!err) + { + /* We should fail.... but why... Who installed us? */ + err = svn_error_trace(svn_ra_serf__unexpected_status(handler)); + } + + return err; +} + + +svn_error_t * +svn_ra_serf__setup_error_parsing(svn_ra_serf__server_error_t **server_err, + svn_ra_serf__handler_t *handler, + svn_boolean_t expect_207_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__server_error_t *ms_baton; + svn_ra_serf__handler_t *tmp_handler; + + int *expected_status = apr_pcalloc(result_pool, + 2 * sizeof(expected_status[0])); + + expected_status[0] = handler->sline.code; + + ms_baton = apr_pcalloc(result_pool, sizeof(*ms_baton)); + ms_baton->pool = result_pool; + + ms_baton->items = apr_array_make(result_pool, 4, sizeof(error_item_t *)); + ms_baton->handler = handler; + + ms_baton->xmlctx = svn_ra_serf__xml_context_create(multistatus_ttable, + multistatus_opened, + multistatus_closed, + NULL, + ms_baton, + ms_baton->pool); + + tmp_handler = svn_ra_serf__create_expat_handler(handler->session, + ms_baton->xmlctx, + expected_status, + result_pool); + + /* Ugly way to obtain expat_handler() */ + tmp_handler->sline = handler->sline; + ms_baton->response_handler = tmp_handler->response_handler; + ms_baton->response_baton = tmp_handler->response_baton; + + *server_err = ms_baton; + return SVN_NO_ERROR; +} + + + +/* Implements svn_ra_serf__response_handler_t */ +svn_error_t * +svn_ra_serf__handle_multistatus_only(serf_request_t *request, + serf_bucket_t *response, + void *baton, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__handler_t *handler = baton; + + /* This function is just like expect_empty_body() except for the + XML parsing callbacks. We are looking for very limited pieces of + the multistatus response. */ + + /* We should see this just once, in order to initialize SERVER_ERROR. + At that point, the core error processing will take over. If we choose + not to parse an error, then we'll never return here (because we + change the response handler). */ + SVN_ERR_ASSERT(handler->server_error == NULL); + + { + serf_bucket_t *hdrs; + const char *val; + + hdrs = serf_bucket_response_get_headers(response); + val = serf_bucket_headers_get(hdrs, "Content-Type"); + if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) + { + svn_ra_serf__server_error_t *server_err; + + SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, + handler, + TRUE, + handler->handler_pool, + handler->handler_pool)); + + handler->server_error = server_err; + } + else + { + /* The body was not text/xml, so we don't know what to do with it. + Toss anything that arrives. */ + handler->discard_body = TRUE; + } + } + + /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it + to call the response handler again. That will start up the XML parsing, + or it will be dropped on the floor (per the decision above). */ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__handle_server_error(svn_ra_serf__server_error_t *server_error, + svn_ra_serf__handler_t *handler, + serf_request_t *request, + serf_bucket_t *response, + apr_status_t *serf_status, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + + err = server_error->response_handler(request, response, + server_error->response_baton, + scratch_pool); + /* If we do not receive an error or it is a non-transient error, return + immediately. + + APR_EOF will be returned when parsing is complete. + + APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through + parsing and the network has no more data right now. If we receive that, + clear the error and return - allowing serf to wait for more data. + */ + if (!err || SERF_BUCKET_READ_ERROR(err->apr_err)) + return svn_error_trace(err); + + if (!APR_STATUS_IS_EOF(err->apr_err)) + { + *serf_status = err->apr_err; + svn_error_clear(err); + return SVN_NO_ERROR; + } + + /* Clear the EOF. We don't need it as subversion error. */ + svn_error_clear(err); + *serf_status = APR_EOF; + + /* On PROPPATCH we always get status 207, which may or may not imply an + error status, but let's keep it generic and just do the check for + any multistatus */ + if (handler->sline.code == 207 /* MULTISTATUS */) + { + svn_boolean_t have_error = FALSE; + int i; + + for (i = 0; i < server_error->items->nelts; i++) + { + const error_item_t *item; + item = APR_ARRAY_IDX(server_error->items, i, error_item_t *); + + if (!item->apr_err && item->http_status == 200) + { + continue; /* Success code */ + } + + have_error = TRUE; + break; + } + + if (! have_error) + handler->server_error = NULL; /* We didn't have a server error */ + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_ra_serf/options.c b/subversion/libsvn_ra_serf/options.c index 5389b04..a52977b 100644 --- a/subversion/libsvn_ra_serf/options.c +++ b/subversion/libsvn_ra_serf/options.c @@ -30,6 +30,7 @@ #include "svn_dirent_uri.h" #include "svn_hash.h" #include "svn_pools.h" +#include "svn_path.h" #include "svn_ra.h" #include "svn_dav.h" #include "svn_xml.h" @@ -51,7 +52,7 @@ * This enum represents the current state of our XML parsing for an OPTIONS. */ enum options_state_e { - INITIAL = 0, + INITIAL = XML_STATE_INITIAL, OPTIONS, ACTIVITY_COLLECTION, HREF @@ -65,7 +66,6 @@ typedef struct options_context_t { svn_boolean_t headers_processed; svn_ra_serf__session_t *session; - svn_ra_serf__connection_t *conn; svn_ra_serf__handler_t *handler; svn_ra_serf__response_handler_t inner_handler; @@ -112,19 +112,20 @@ options_closed(svn_ra_serf__xml_estate_t *xes, return SVN_NO_ERROR; } - +/* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_options_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { serf_bucket_t *body; body = serf_bucket_aggregate_create(alloc); svn_ra_serf__add_xml_header_buckets(body, alloc); svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options", "xmlns:D", "DAV:", - NULL); + SVN_VA_NULL); svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc); svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options"); @@ -372,7 +373,7 @@ options_response_handler(serf_request_t *request, serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback, opt_ctx); - /* Assume mergeinfo capability unsupported, if didn't recieve information + /* Assume mergeinfo capability unsupported, if didn't receive information about server or repository mergeinfo capability. */ if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO)) svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO, @@ -389,7 +390,6 @@ options_response_handler(serf_request_t *request, static svn_error_t * create_options_req(options_context_t **opt_ctx, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, apr_pool_t *pool) { options_context_t *new_ctx; @@ -399,7 +399,6 @@ create_options_req(options_context_t **opt_ctx, new_ctx = apr_pcalloc(pool, sizeof(*new_ctx)); new_ctx->pool = pool; new_ctx->session = session; - new_ctx->conn = conn; new_ctx->youngest_rev = SVN_INVALID_REVNUM; @@ -407,14 +406,12 @@ create_options_req(options_context_t **opt_ctx, NULL, options_closed, NULL, new_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool); handler->method = "OPTIONS"; handler->path = session->session_url.path; handler->body_delegate = create_options_body; handler->body_type = "text/xml"; - handler->conn = conn; - handler->session = session; new_ctx->handler = handler; @@ -431,22 +428,25 @@ create_options_req(options_context_t **opt_ctx, svn_error_t * svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, apr_pool_t *scratch_pool) { - svn_ra_serf__session_t *session = conn->session; options_context_t *opt_ctx; SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); - SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool)); + SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool)); SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); - SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline, - opt_ctx->handler->path, - opt_ctx->handler->location)); + + if (opt_ctx->handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler)); + + if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev)) + return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, + _("The OPTIONS response did not include " + "the youngest revision")); *youngest = opt_ctx->youngest_rev; - SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*youngest)); return SVN_NO_ERROR; } @@ -454,21 +454,39 @@ svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest, svn_error_t * svn_ra_serf__v1_get_activity_collection(const char **activity_url, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - svn_ra_serf__session_t *session = conn->session; options_context_t *opt_ctx; SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)); - SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool)); + if (session->activity_collection_url) + { + *activity_url = apr_pstrdup(result_pool, + session->activity_collection_url); + return SVN_NO_ERROR; + } + + SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool)); SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); - SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline, - opt_ctx->handler->path, - opt_ctx->handler->location)); + if (opt_ctx->handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler)); + + /* Cache the result. */ + if (opt_ctx->activity_collection) + { + session->activity_collection_url = + apr_pstrdup(session->pool, opt_ctx->activity_collection); + } + else + { + return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, + _("The OPTIONS response did not include the " + "requested activity-collection-set value")); + } *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection); @@ -483,15 +501,20 @@ svn_ra_serf__v1_get_activity_collection(const char **activity_url, svn_error_t * svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess, const char **corrected_url, - apr_pool_t *pool) + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { options_context_t *opt_ctx; - svn_error_t *err; + + if (corrected_url) + *corrected_url = NULL; /* This routine automatically fills in serf_sess->capabilities */ - SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool)); + SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool)); - err = svn_ra_serf__context_run_one(opt_ctx->handler, pool); + opt_ctx->handler->no_fail_on_http_redirect_status = TRUE; + + SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool)); /* If our caller cares about server redirections, and our response carries such a thing, report as much. We'll disregard ERR -- @@ -499,16 +522,47 @@ svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess, successfully parsing as XML or somesuch. */ if (corrected_url && (opt_ctx->handler->sline.code == 301)) { - svn_error_clear(err); - *corrected_url = opt_ctx->handler->location; + if (!opt_ctx->handler->location || !*opt_ctx->handler->location) + { + return svn_error_create( + SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL, + _("Location header not set on redirect response")); + } + else if (svn_path_is_url(opt_ctx->handler->location)) + { + *corrected_url = svn_uri_canonicalize(opt_ctx->handler->location, + result_pool); + } + else + { + /* RFC1945 and RFC2616 state that the Location header's value + (from whence this CORRECTED_URL comes), if present, must be an + absolute URI. But some Apache versions (those older than 2.2.11, + it seems) transmit only the path portion of the URI. + See issue #3775 for details. */ + + apr_uri_t corrected_URI = serf_sess->session_url; + + corrected_URI.path = (char *)corrected_url; + *corrected_url = svn_uri_canonicalize( + apr_uri_unparse(scratch_pool, &corrected_URI, 0), + result_pool); + } + return SVN_NO_ERROR; } + else if (opt_ctx->handler->sline.code >= 300 + && opt_ctx->handler->sline.code < 399) + { + return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL, + (opt_ctx->handler->sline.code == 301 + ? _("Repository moved permanently to '%s'") + : _("Repository moved temporarily to '%s'")), + opt_ctx->handler->location); + } - SVN_ERR(svn_error_compose_create( - svn_ra_serf__error_on_status(opt_ctx->handler->sline, - serf_sess->session_url.path, - opt_ctx->handler->location), - err)); + if (opt_ctx->handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler)); /* Opportunistically cache any reported activity URL. (We don't want to have to ask for this again later, potentially against an @@ -522,12 +576,13 @@ svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess, return SVN_NO_ERROR; } - +/* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_simple_options_body(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { serf_bucket_t *body; serf_bucket_t *s; @@ -549,18 +604,16 @@ svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess, { svn_ra_serf__handler_t *handler; - handler = apr_pcalloc(scratch_pool, sizeof(*handler)); - handler->handler_pool = scratch_pool; + handler = svn_ra_serf__create_handler(serf_sess, scratch_pool); handler->method = "OPTIONS"; handler->path = serf_sess->session_url.path; - handler->conn = serf_sess->conns[0]; - handler->session = serf_sess; /* We don't care about the response body, so discard it. */ handler->response_handler = svn_ra_serf__handle_discard_body; /* We need a simple body, in order to send it in chunked format. */ handler->body_delegate = create_simple_options_body; + handler->no_fail_on_http_failure_status = TRUE; /* No special headers. */ @@ -574,9 +627,8 @@ svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess, return SVN_NO_ERROR; } - 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; } @@ -602,7 +654,7 @@ svn_ra_serf__has_capability(svn_ra_session_t *ra_session, /* If any capability is unknown, they're all unknown, so ask. */ if (cap_result == NULL) - SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool)); + SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool, pool)); /* Try again, now that we've fetched the capabilities. */ cap_result = svn_hash_gets(serf_sess->capabilities, capability); @@ -628,7 +680,9 @@ svn_ra_serf__has_capability(svn_ra_session_t *ra_session, APR_ARRAY_PUSH(paths, const char *) = ""; err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0, - FALSE, FALSE, pool); + svn_mergeinfo_explicit, + FALSE /* include_descendants */, + pool); if (err) { @@ -646,7 +700,7 @@ svn_ra_serf__has_capability(svn_ra_session_t *ra_session, cap_result = capability_yes; } else - return err; + return svn_error_trace(err); } else cap_result = capability_yes; diff --git a/subversion/libsvn_ra_serf/property.c b/subversion/libsvn_ra_serf/property.c index 586d38f..b7e0318 100644 --- a/subversion/libsvn_ra_serf/property.c +++ b/subversion/libsvn_ra_serf/property.c @@ -42,7 +42,7 @@ /* Our current parsing state we're in for the PROPFIND response. */ typedef enum prop_state_e { - INITIAL = 0, + INITIAL = XML_STATE_INITIAL, MULTISTATUS, RESPONSE, HREF, @@ -59,20 +59,12 @@ typedef enum prop_state_e { * This structure represents a pending PROPFIND response. */ typedef struct propfind_context_t { - /* pool to issue allocations from */ - apr_pool_t *pool; - svn_ra_serf__handler_t *handler; - /* associated serf session */ - svn_ra_serf__session_t *sess; - svn_ra_serf__connection_t *conn; - /* the requested path */ const char *path; - /* the requested version (number and string form) */ - svn_revnum_t rev; + /* the requested version (in string form) */ const char *label; /* the request depth */ @@ -81,12 +73,8 @@ typedef struct propfind_context_t { /* the list of requested properties */ const svn_ra_serf__dav_props_t *find_props; - /* hash table that will be updated with the properties - * - * This can be shared between multiple propfind_context_t - * structures - */ - apr_hash_t *ret_props; + svn_ra_serf__prop_func_t prop_func; + void *prop_func_baton; /* hash table containing all the properties associated with the * "current" <propstat> tag. These will get copied into RET_PROPS @@ -94,12 +82,6 @@ typedef struct propfind_context_t { * "good"; otherwise, they'll get discarded. */ apr_hash_t *ps_props; - - /* If not-NULL, add us to this list when we're done. */ - svn_ra_serf__list_t **done_list; - - svn_ra_serf__list_t done_item; - } propfind_context_t; @@ -136,10 +118,14 @@ static const svn_ra_serf__xml_transition_t propfind_ttable[] = { { 0 } }; +static const int propfind_expected_status[] = { + 207, + 0 +}; /* Return the HTTP status code contained in STATUS_LINE, or 0 if there's a problem parsing it. */ -static int parse_status_code(const char *status_line) +static apr_int64_t parse_status_code(const char *status_line) { /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */ if (status_line[0] == 'H' && @@ -159,24 +145,6 @@ static int parse_status_code(const char *status_line) return 0; } - -/* Conforms to svn_ra_serf__path_rev_walker_t */ -static svn_error_t * -copy_into_ret_props(void *baton, - const char *path, apr_ssize_t path_len, - const char *ns, apr_ssize_t ns_len, - const char *name, apr_ssize_t name_len, - const svn_string_t *val, - apr_pool_t *pool) -{ - propfind_context_t *ctx = baton; - - svn_ra_serf__set_ver_prop(ctx->ret_props, path, ctx->rev, ns, name, - val, ctx->pool); - return SVN_NO_ERROR; -} - - /* Conforms to svn_ra_serf__xml_opened_t */ static svn_error_t * propfind_opened(svn_ra_serf__xml_estate_t *xes, @@ -189,17 +157,40 @@ propfind_opened(svn_ra_serf__xml_estate_t *xes, if (entered_state == PROPVAL) { - svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->namespace); + svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->xmlns); svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name); } else if (entered_state == PROPSTAT) { - ctx->ps_props = apr_hash_make(ctx->pool); + ctx->ps_props = apr_hash_make(svn_ra_serf__xml_state_pool(xes)); } return SVN_NO_ERROR; } +/* Set PROPS for NS:NAME VAL. Helper for propfind_closed */ +static void +set_ns_prop(apr_hash_t *ns_props, + const char *ns, const char *name, + const svn_string_t *val, apr_pool_t *result_pool) +{ + apr_hash_t *props = svn_hash_gets(ns_props, ns); + + if (!props) + { + props = apr_hash_make(result_pool); + ns = apr_pstrdup(result_pool, ns); + svn_hash_sets(ns_props, ns, props); + } + + if (val) + { + name = apr_pstrdup(result_pool, name); + val = svn_string_dup(val, result_pool); + } + + svn_hash_sets(props, name, val); +} /* Conforms to svn_ra_serf__xml_closed_t */ static svn_error_t * @@ -218,17 +209,10 @@ propfind_closed(svn_ra_serf__xml_estate_t *xes, onto the "done list". External callers will then know this request has been completed (tho stray response bytes may still arrive). */ - if (ctx->done_list) - { - ctx->done_item.data = ctx->handler; - ctx->done_item.next = *ctx->done_list; - *ctx->done_list = &ctx->done_item; - } } else if (leaving_state == HREF) { const char *path; - const svn_string_t *val_str; if (strcmp(ctx->depth, "1") == 0) path = svn_urlpath__canonicalize(cdata->data, scratch_pool); @@ -237,11 +221,10 @@ propfind_closed(svn_ra_serf__xml_estate_t *xes, svn_ra_serf__xml_note(xes, RESPONSE, "path", path); - /* Copy the value into the right pool, then save the HREF. */ - val_str = svn_string_dup(cdata, ctx->pool); - svn_ra_serf__set_ver_prop(ctx->ret_props, - path, ctx->rev, D_, "href", val_str, - ctx->pool); + SVN_ERR(ctx->prop_func(ctx->prop_func_baton, + path, + D_, "href", + cdata, scratch_pool)); } else if (leaving_state == COLLECTION) { @@ -257,21 +240,23 @@ propfind_closed(svn_ra_serf__xml_estate_t *xes, that we wish to ignore. (Typically, if it's not a 200, the status will be 404 to indicate that a property we specifically requested from the server doesn't exist.) */ - int status = parse_status_code(cdata->data); + apr_int64_t status = parse_status_code(cdata->data); if (status != 200) svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*"); } else if (leaving_state == PROPVAL) { - const char *encoding = svn_hash_gets(attrs, "V:encoding"); + const char *encoding; const svn_string_t *val_str; - apr_hash_t *gathered; - const char *path; const char *ns; const char *name; const char *altvalue; - if (encoding) + if ((altvalue = svn_hash_gets(attrs, "altvalue")) != NULL) + { + val_str = svn_string_create(altvalue, scratch_pool); + } + else if ((encoding = svn_hash_gets(attrs, "V:encoding")) != NULL) { if (strcmp(encoding, "base64") != 0) return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, @@ -280,17 +265,15 @@ propfind_closed(svn_ra_serf__xml_estate_t *xes, encoding); /* Decode into the right pool. */ - val_str = svn_base64_decode_string(cdata, ctx->pool); + val_str = svn_base64_decode_string(cdata, scratch_pool); } else { /* Copy into the right pool. */ - val_str = svn_string_dup(cdata, ctx->pool); + val_str = cdata; } - /* The current path sits on the RESPONSE state. Gather up all the - state from this PROPVAL to the (grandparent) RESPONSE state, - and grab the path from there. + /* The current path sits on the RESPONSE state. Now, it would be nice if we could, at this point, know that the status code for this property indicated a problem -- then @@ -300,24 +283,12 @@ propfind_closed(svn_ra_serf__xml_estate_t *xes, here, setting the property and value as expected. Once we know for sure the status code associate with the property, we'll decide its fate. */ - gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE); - - /* These will be dup'd into CTX->POOL, as necessary. */ - path = svn_hash_gets(gathered, "path"); - if (path == NULL) - path = ctx->path; ns = svn_hash_gets(attrs, "ns"); - name = apr_pstrdup(ctx->pool, - svn_hash_gets(attrs, "name")); - - altvalue = svn_hash_gets(attrs, "altvalue"); - if (altvalue != NULL) - val_str = svn_string_create(altvalue, ctx->pool); + name = svn_hash_gets(attrs, "name"); - svn_ra_serf__set_ver_prop(ctx->ps_props, - path, ctx->rev, ns, name, val_str, - ctx->pool); + set_ns_prop(ctx->ps_props, ns, name, val_str, + apr_hash_pool_get(ctx->ps_props)); } else { @@ -325,146 +296,60 @@ propfind_closed(svn_ra_serf__xml_estate_t *xes, SVN_ERR_ASSERT(leaving_state == PROPSTAT); - gathered = svn_ra_serf__xml_gather_since(xes, PROPSTAT); + gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE); /* If we've squirreled away a note that says we want to ignore these properties, we'll do so. Otherwise, we need to copy them from the temporary hash into the ctx->ret_props hash. */ if (! svn_hash_gets(gathered, "ignore-prop")) { - SVN_ERR(svn_ra_serf__walk_all_paths(ctx->ps_props, ctx->rev, - copy_into_ret_props, ctx, - scratch_pool)); - } + apr_hash_index_t *hi_ns; + const char *path; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); - ctx->ps_props = NULL; - } - - return SVN_NO_ERROR; -} + path = svn_hash_gets(gathered, "path"); + if (!path) + path = ctx->path; -const svn_string_t * -svn_ra_serf__get_ver_prop_string(apr_hash_t *props, - const char *path, - svn_revnum_t rev, - const char *ns, - const char *name) -{ - apr_hash_t *ver_props, *path_props, *ns_props; - void *val = NULL; - - ver_props = apr_hash_get(props, &rev, sizeof(rev)); - if (ver_props) - { - path_props = svn_hash_gets(ver_props, path); - - if (path_props) - { - ns_props = svn_hash_gets(path_props, ns); - if (ns_props) + for (hi_ns = apr_hash_first(scratch_pool, ctx->ps_props); + hi_ns; + hi_ns = apr_hash_next(hi_ns)) { - val = svn_hash_gets(ns_props, name); + const char *ns = apr_hash_this_key(hi_ns); + apr_hash_t *props = apr_hash_this_val(hi_ns); + apr_hash_index_t *hi_prop; + + svn_pool_clear(iterpool); + + for (hi_prop = apr_hash_first(iterpool, props); + hi_prop; + hi_prop = apr_hash_next(hi_prop)) + { + const char *name = apr_hash_this_key(hi_prop); + const svn_string_t *value = apr_hash_this_val(hi_prop); + + SVN_ERR(ctx->prop_func(ctx->prop_func_baton, path, + ns, name, value, iterpool)); + } } - } - } - - return val; -} - -const char * -svn_ra_serf__get_ver_prop(apr_hash_t *props, - const char *path, - svn_revnum_t rev, - const char *ns, - const char *name) -{ - const svn_string_t *val; - val = svn_ra_serf__get_ver_prop_string(props, path, rev, ns, name); - - if (val) - { - return val->data; - } - - return NULL; -} - -const svn_string_t * -svn_ra_serf__get_prop_string(apr_hash_t *props, - const char *path, - const char *ns, - const char *name) -{ - return svn_ra_serf__get_ver_prop_string(props, path, SVN_INVALID_REVNUM, - ns, name); -} - -const char * -svn_ra_serf__get_prop(apr_hash_t *props, - const char *path, - const char *ns, - const char *name) -{ - return svn_ra_serf__get_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name); -} - -void -svn_ra_serf__set_ver_prop(apr_hash_t *props, - const char *path, svn_revnum_t rev, - const char *ns, const char *name, - const svn_string_t *val, apr_pool_t *pool) -{ - apr_hash_t *ver_props, *path_props, *ns_props; - - ver_props = apr_hash_get(props, &rev, sizeof(rev)); - if (!ver_props) - { - ver_props = apr_hash_make(pool); - apr_hash_set(props, apr_pmemdup(pool, &rev, sizeof(rev)), sizeof(rev), - ver_props); - } - - path_props = svn_hash_gets(ver_props, path); - - if (!path_props) - { - path_props = apr_hash_make(pool); - path = apr_pstrdup(pool, path); - svn_hash_sets(ver_props, path, path_props); - - /* todo: we know that we'll fail the next check, but fall through - * for now for simplicity's sake. - */ - } + svn_pool_destroy(iterpool); + } - ns_props = svn_hash_gets(path_props, ns); - if (!ns_props) - { - ns_props = apr_hash_make(pool); - ns = apr_pstrdup(pool, ns); - svn_hash_sets(path_props, ns, ns_props); + ctx->ps_props = NULL; /* Allocated in PROPSTAT state pool */ } - svn_hash_sets(ns_props, name, val); + return SVN_NO_ERROR; } -void -svn_ra_serf__set_prop(apr_hash_t *props, - const char *path, - const char *ns, const char *name, - const svn_string_t *val, apr_pool_t *pool) -{ - svn_ra_serf__set_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name, - val, pool); -} static svn_error_t * setup_propfind_headers(serf_bucket_t *headers, - void *setup_baton, - apr_pool_t *pool) + void *setup_baton, + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { propfind_context_t *ctx = setup_baton; @@ -480,11 +365,13 @@ setup_propfind_headers(serf_bucket_t *headers, #define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">" #define PROPFIND_TRAILER "</propfind>" +/* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_propfind_body(serf_bucket_t **bkt, void *setup_baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { propfind_context_t *ctx = setup_baton; @@ -495,7 +382,7 @@ create_propfind_body(serf_bucket_t **bkt, body_bkt = serf_bucket_aggregate_create(alloc); prop = ctx->find_props; - while (prop && prop->namespace) + while (prop && prop->xmlns) { /* special case the allprop case. */ if (strcmp(prop->name, "allprop") == 0) @@ -515,7 +402,7 @@ create_propfind_body(serf_bucket_t **bkt, alloc); serf_bucket_aggregate_append(body_bkt, tmp); - tmp = SERF_BUCKET_SIMPLE_STRING(prop->namespace, alloc); + tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc); serf_bucket_aggregate_append(body_bkt, tmp); tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1, @@ -559,16 +446,15 @@ create_propfind_body(serf_bucket_t **bkt, svn_error_t * -svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler, - apr_hash_t *ret_props, - svn_ra_serf__session_t *sess, - svn_ra_serf__connection_t *conn, - const char *path, - svn_revnum_t rev, - const char *depth, - const svn_ra_serf__dav_props_t *find_props, - svn_ra_serf__list_t **done_list, - apr_pool_t *pool) +svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler, + svn_ra_serf__session_t *sess, + const char *path, + svn_revnum_t rev, + const char *depth, + const svn_ra_serf__dav_props_t *find_props, + svn_ra_serf__prop_func_t prop_func, + void *prop_func_baton, + apr_pool_t *pool) { propfind_context_t *new_prop_ctx; svn_ra_serf__handler_t *handler; @@ -576,15 +462,11 @@ svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler, new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx)); - new_prop_ctx->pool = apr_hash_pool_get(ret_props); new_prop_ctx->path = path; new_prop_ctx->find_props = find_props; - new_prop_ctx->ret_props = ret_props; + new_prop_ctx->prop_func = prop_func; + new_prop_ctx->prop_func_baton = prop_func_baton; new_prop_ctx->depth = depth; - new_prop_ctx->sess = sess; - new_prop_ctx->conn = conn; - new_prop_ctx->rev = rev; - new_prop_ctx->done_list = done_list; if (SVN_IS_VALID_REVNUM(rev)) { @@ -601,7 +483,9 @@ svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler, NULL, new_prop_ctx, pool); - handler = svn_ra_serf__create_expat_handler(xmlctx, pool); + handler = svn_ra_serf__create_expat_handler(sess, xmlctx, + propfind_expected_status, + pool); handler->method = "PROPFIND"; handler->path = path; @@ -611,8 +495,7 @@ svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler, handler->header_delegate = setup_propfind_headers; handler->header_delegate_baton = new_prop_ctx; - handler->session = new_prop_ctx->sess; - handler->conn = new_prop_ctx->conn; + handler->no_dav_headers = TRUE; new_prop_ctx->handler = handler; @@ -621,208 +504,85 @@ svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler, return SVN_NO_ERROR; } - -/* - * This helper function will block until the PROP_CTX indicates that is done - * or another error is returned. - */ svn_error_t * -svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler, - apr_pool_t *scratch_pool) +svn_ra_serf__deliver_svn_props(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) { - svn_error_t *err; - svn_error_t *err2; + apr_hash_t *props = baton; + apr_pool_t *result_pool = apr_hash_pool_get(props); + const char *prop_name; - err = svn_ra_serf__context_run_one(handler, scratch_pool); + prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool); + if (prop_name == NULL) + return SVN_NO_ERROR; - err2 = svn_ra_serf__error_on_status(handler->sline, - handler->path, - handler->location); + svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool)); - return svn_error_compose_create(err2, err); + return SVN_NO_ERROR; } /* - * This is a blocking version of deliver_props. + * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties + * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash + * (const char * -> svn_string_t *) to the values. */ -svn_error_t * -svn_ra_serf__retrieve_props(apr_hash_t **results, - svn_ra_serf__session_t *sess, - svn_ra_serf__connection_t *conn, - const char *url, - svn_revnum_t rev, - const char *depth, - const svn_ra_serf__dav_props_t *props, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +static svn_error_t * +deliver_node_props(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) { - svn_ra_serf__handler_t *handler; + apr_hash_t *nss = baton; + apr_hash_t *props; + apr_pool_t *result_pool = apr_hash_pool_get(nss); - *results = apr_hash_make(result_pool); + props = svn_hash_gets(nss, ns); - SVN_ERR(svn_ra_serf__deliver_props(&handler, *results, sess, conn, url, - rev, depth, props, NULL, result_pool)); - SVN_ERR(svn_ra_serf__wait_for_props(handler, scratch_pool)); + if (!props) + { + props = apr_hash_make(result_pool); + + ns = apr_pstrdup(result_pool, ns); + svn_hash_sets(nss, ns, props); + } + + name = apr_pstrdup(result_pool, name); + svn_hash_sets(props, name, svn_string_dup(value, result_pool)); return SVN_NO_ERROR; } - svn_error_t * svn_ra_serf__fetch_node_props(apr_hash_t **results, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, const char *url, svn_revnum_t revision, const svn_ra_serf__dav_props_t *which_props, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - apr_hash_t *multiprops; - apr_hash_t *ver_props; - - /* Note: a couple extra hash tables and whatnot get into RESULT_POOL. - Not a big deal at this point. Theoretically, we could fetch all - props into SCRATCH_POOL, then copy just the REVISION/URL props - into RESULT_POOL. Too much work for too little gain... */ - SVN_ERR(svn_ra_serf__retrieve_props(&multiprops, conn->session, conn, - url, revision, "0", which_props, - result_pool, scratch_pool)); - - ver_props = apr_hash_get(multiprops, &revision, sizeof(revision)); - if (ver_props != NULL) - { - *results = svn_hash_gets(ver_props, url); - if (*results != NULL) - return SVN_NO_ERROR; - } - - return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, - _("The PROPFIND response did not include " - "the requested properties")); -} - - -svn_error_t * -svn_ra_serf__walk_node_props(apr_hash_t *props, - svn_ra_serf__walker_visitor_t walker, - void *baton, - apr_pool_t *scratch_pool) -{ - apr_pool_t *iterpool; - apr_hash_index_t *ns_hi; - - iterpool = svn_pool_create(scratch_pool); - for (ns_hi = apr_hash_first(scratch_pool, props); ns_hi; - ns_hi = apr_hash_next(ns_hi)) - { - void *ns_val; - const void *ns_name; - apr_hash_index_t *name_hi; - - /* NOTE: We do not clear ITERPOOL in this loop. Generally, there are - very few namespaces, so this loop will not have many iterations. - Instead, ITERPOOL is used for the inner loop. */ - - apr_hash_this(ns_hi, &ns_name, NULL, &ns_val); - - for (name_hi = apr_hash_first(scratch_pool, ns_val); name_hi; - name_hi = apr_hash_next(name_hi)) - { - void *prop_val; - const void *prop_name; - - /* See note above, regarding clearing of this pool. */ - svn_pool_clear(iterpool); - - apr_hash_this(name_hi, &prop_name, NULL, &prop_val); - - SVN_ERR(walker(baton, ns_name, prop_name, prop_val, iterpool)); - } - } - svn_pool_destroy(iterpool); - - return SVN_NO_ERROR; -} - - -svn_error_t * -svn_ra_serf__walk_all_props(apr_hash_t *props, - const char *name, - svn_revnum_t rev, - svn_ra_serf__walker_visitor_t walker, - void *baton, - apr_pool_t *scratch_pool) -{ - apr_hash_t *ver_props; - apr_hash_t *path_props; - - ver_props = apr_hash_get(props, &rev, sizeof(rev)); - if (!ver_props) - return SVN_NO_ERROR; - - path_props = svn_hash_gets(ver_props, name); - if (!path_props) - return SVN_NO_ERROR; - - return svn_error_trace(svn_ra_serf__walk_node_props(path_props, - walker, baton, - scratch_pool)); -} - - -svn_error_t * -svn_ra_serf__walk_all_paths(apr_hash_t *props, - svn_revnum_t rev, - svn_ra_serf__path_rev_walker_t walker, - void *baton, - apr_pool_t *pool) -{ - apr_hash_index_t *path_hi; - apr_hash_t *ver_props; + apr_hash_t *props; + svn_ra_serf__handler_t *handler; - ver_props = apr_hash_get(props, &rev, sizeof(rev)); + props = apr_hash_make(result_pool); - if (!ver_props) - { - return SVN_NO_ERROR; - } + SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, + url, revision, "0", which_props, + deliver_node_props, + props, scratch_pool)); - for (path_hi = apr_hash_first(pool, ver_props); path_hi; - path_hi = apr_hash_next(path_hi)) - { - void *path_props; - const void *path_name; - apr_ssize_t path_len; - apr_hash_index_t *ns_hi; - - apr_hash_this(path_hi, &path_name, &path_len, &path_props); - for (ns_hi = apr_hash_first(pool, path_props); ns_hi; - ns_hi = apr_hash_next(ns_hi)) - { - void *ns_val; - const void *ns_name; - apr_ssize_t ns_len; - apr_hash_index_t *name_hi; - apr_hash_this(ns_hi, &ns_name, &ns_len, &ns_val); - for (name_hi = apr_hash_first(pool, ns_val); name_hi; - name_hi = apr_hash_next(name_hi)) - { - void *prop_val; - const void *prop_name; - apr_ssize_t prop_len; - - apr_hash_this(name_hi, &prop_name, &prop_len, &prop_val); - /* use a subpool? */ - SVN_ERR(walker(baton, path_name, path_len, ns_name, ns_len, - prop_name, prop_len, prop_val, pool)); - } - } - } + SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); + *results = props; return SVN_NO_ERROR; } - const char * svn_ra_serf__svnname_from_wirename(const char *ns, const char *name, @@ -832,10 +592,10 @@ svn_ra_serf__svnname_from_wirename(const char *ns, return apr_pstrdup(result_pool, name); if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) - return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL); + return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); if (strcmp(ns, SVN_PROP_PREFIX) == 0) - return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL); + return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) return SVN_PROP_ENTRY_COMMITTED_REV; @@ -863,99 +623,9 @@ svn_ra_serf__svnname_from_wirename(const char *ns, } /* An unknown namespace, must be a custom property. */ - return apr_pstrcat(result_pool, ns, name, (char *)NULL); + return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL); } - -/* Conforms to svn_ra_serf__walker_visitor_t */ -static svn_error_t * -set_flat_props(void *baton, - const char *ns, - const char *name, - const svn_string_t *value, - apr_pool_t *pool) -{ - apr_hash_t *props = baton; - apr_pool_t *result_pool = apr_hash_pool_get(props); - const char *prop_name; - - /* ### is VAL in the proper pool? */ - - prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool); - if (prop_name != NULL) - svn_hash_sets(props, prop_name, value); - - return SVN_NO_ERROR; -} - - -svn_error_t * -svn_ra_serf__flatten_props(apr_hash_t **flat_props, - apr_hash_t *props, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - *flat_props = apr_hash_make(result_pool); - - return svn_error_trace(svn_ra_serf__walk_node_props( - props, - set_flat_props, - *flat_props /* baton */, - scratch_pool)); -} - - -static svn_error_t * -select_revprops(void *baton, - const char *ns, - const char *name, - const svn_string_t *val, - apr_pool_t *scratch_pool) -{ - apr_hash_t *revprops = baton; - apr_pool_t *result_pool = apr_hash_pool_get(revprops); - const char *prop_name; - - /* ### copy NAME into the RESULT_POOL? */ - /* ### copy VAL into the RESULT_POOL? */ - - if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) - prop_name = name; - else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) - prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL); - else if (strcmp(ns, SVN_PROP_PREFIX) == 0) - prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL); - else if (strcmp(ns, "") == 0) - prop_name = name; - else - { - /* do nothing for now? */ - return SVN_NO_ERROR; - } - - svn_hash_sets(revprops, prop_name, val); - - return SVN_NO_ERROR; -} - - -svn_error_t * -svn_ra_serf__select_revprops(apr_hash_t **revprops, - const char *name, - svn_revnum_t rev, - apr_hash_t *all_revprops, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - *revprops = apr_hash_make(result_pool); - - return svn_error_trace(svn_ra_serf__walk_all_props( - all_revprops, name, rev, - select_revprops, *revprops, - scratch_pool)); -} - - /* * Contact the server (using CONN) to calculate baseline * information for BASELINE_URL at REVISION (which may be @@ -969,7 +639,7 @@ svn_ra_serf__select_revprops(apr_hash_t **revprops, static svn_error_t * retrieve_baseline_info(svn_revnum_t *actual_revision, const char **basecoll_url_p, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, const char *baseline_url, svn_revnum_t revision, apr_pool_t *result_pool, @@ -979,7 +649,7 @@ retrieve_baseline_info(svn_revnum_t *actual_revision, apr_hash_t *dav_props; const char *basecoll_url; - SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, + SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, baseline_url, revision, baseline_props, scratch_pool, scratch_pool)); @@ -1000,12 +670,18 @@ retrieve_baseline_info(svn_revnum_t *actual_revision, const char *version_name; version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME); - if (!version_name) + if (version_name) + { + apr_int64_t rev; + + SVN_ERR(svn_cstring_atoi64(&rev, version_name)); + *actual_revision = (svn_revnum_t)rev; + } + + if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision)) return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, _("The PROPFIND response did not include " "the requested version-name value")); - - *actual_revision = SVN_STR_TO_REV(version_name); } return SVN_NO_ERROR; @@ -1023,7 +699,7 @@ retrieve_baseline_info(svn_revnum_t *actual_revision, static svn_error_t * v1_get_youngest_revnum(svn_revnum_t *youngest, const char **basecoll_url, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, const char *vcc_url, apr_pool_t *result_pool, apr_pool_t *scratch_pool) @@ -1033,7 +709,7 @@ v1_get_youngest_revnum(svn_revnum_t *youngest, /* Fetching DAV:checked-in from the VCC (with no Label: to specify a revision) will return the latest Baseline resource's URL. */ - SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, conn, vcc_url, + SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url, SVN_INVALID_REVNUM, "checked-in", scratch_pool, scratch_pool)); @@ -1052,15 +728,15 @@ v1_get_youngest_revnum(svn_revnum_t *youngest, /* First check baseline information cache. */ SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url, youngest, - conn->session->blncache, + session->blncache, baseline_url, scratch_pool)); if (!bc_url) { - SVN_ERR(retrieve_baseline_info(youngest, &bc_url, conn, + SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session, baseline_url, SVN_INVALID_REVNUM, scratch_pool, scratch_pool)); - SVN_ERR(svn_ra_serf__blncache_set(conn->session->blncache, + SVN_ERR(svn_ra_serf__blncache_set(session->blncache, baseline_url, *youngest, bc_url, scratch_pool)); } @@ -1081,12 +757,12 @@ svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest, if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum( - youngest, session->conns[0], scratch_pool)); + youngest, session, scratch_pool)); - SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, scratch_pool)); + SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); return svn_error_trace(v1_get_youngest_revnum(youngest, NULL, - session->conns[0], vcc_url, + session, vcc_url, scratch_pool, scratch_pool)); } @@ -1103,9 +779,9 @@ static svn_error_t * get_baseline_info(const char **bc_url, svn_revnum_t *revnum_used, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, svn_revnum_t revision, - apr_pool_t *pool) + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { /* If we detected HTTP v2 support on the server, we can construct the baseline collection URL ourselves, and fetch the latest @@ -1119,14 +795,10 @@ get_baseline_info(const char **bc_url, else { SVN_ERR(svn_ra_serf__v2_get_youngest_revnum( - revnum_used, conn, pool)); - if (! SVN_IS_VALID_REVNUM(*revnum_used)) - return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, - _("The OPTIONS response did not include " - "the youngest revision")); + revnum_used, session, scratch_pool)); } - *bc_url = apr_psprintf(pool, "%s/%ld", + *bc_url = apr_psprintf(result_pool, "%s/%ld", session->rev_root_stub, *revnum_used); } @@ -1135,20 +807,22 @@ get_baseline_info(const char **bc_url, { const char *vcc_url; - SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, conn, pool)); + SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); if (SVN_IS_VALID_REVNUM(revision)) { /* First check baseline information cache. */ SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url, session->blncache, - revision, pool)); + revision, result_pool)); if (!*bc_url) { - SVN_ERR(retrieve_baseline_info(NULL, bc_url, conn, - vcc_url, revision, pool, pool)); + SVN_ERR(retrieve_baseline_info(NULL, bc_url, session, + vcc_url, revision, + result_pool, scratch_pool)); SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL, - revision, *bc_url, pool)); + revision, *bc_url, + scratch_pool)); } *revnum_used = revision; @@ -1156,8 +830,8 @@ get_baseline_info(const char **bc_url, else { SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url, - conn, vcc_url, - pool, pool)); + session, vcc_url, + result_pool, scratch_pool)); } } @@ -1169,7 +843,6 @@ svn_error_t * svn_ra_serf__get_stable_url(const char **stable_url, svn_revnum_t *latest_revnum, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, const char *url, svn_revnum_t revision, apr_pool_t *result_pool, @@ -1183,15 +856,10 @@ svn_ra_serf__get_stable_url(const char **stable_url, if (! url) url = session->session_url.path; - /* If the caller didn't provide a specific connection for us to use, - we'll use the default connection. */ - if (! conn) - conn = session->conns[0]; - SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used, - session, conn, revision, scratch_pool)); + session, revision, scratch_pool, scratch_pool)); SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url, - session, conn, scratch_pool)); + session, scratch_pool)); *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath, result_pool); @@ -1203,38 +871,8 @@ svn_ra_serf__get_stable_url(const char **stable_url, svn_error_t * -svn_ra_serf__get_resource_type(svn_node_kind_t *kind, - apr_hash_t *props) -{ - apr_hash_t *dav_props; - const char *res_type; - - dav_props = apr_hash_get(props, "DAV:", 4); - res_type = svn_prop_get_value(dav_props, "resourcetype"); - if (!res_type) - { - /* How did this happen? */ - return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, - _("The PROPFIND response did not include the " - "requested resourcetype value")); - } - - if (strcmp(res_type, "collection") == 0) - { - *kind = svn_node_dir; - } - else - { - *kind = svn_node_file; - } - - return SVN_NO_ERROR; -} - - -svn_error_t * svn_ra_serf__fetch_dav_prop(const char **value, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, const char *url, svn_revnum_t revision, const char *propname, @@ -1244,7 +882,7 @@ svn_ra_serf__fetch_dav_prop(const char **value, apr_hash_t *props; apr_hash_t *dav_props; - SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, url, revision, + SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision, checked_in_props, scratch_pool, scratch_pool)); dav_props = apr_hash_get(props, "DAV:", 4); @@ -1261,3 +899,19 @@ svn_ra_serf__fetch_dav_prop(const char **value, return SVN_NO_ERROR; } + +/* Removes all non regular properties from PROPS */ +void +svn_ra_serf__keep_only_regular_props(apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi)) + { + const char *propname = apr_hash_this_key(hi); + + if (svn_property_kind2(propname) != svn_prop_regular_kind) + svn_hash_sets(props, propname, NULL); + } +} diff --git a/subversion/libsvn_ra_serf/ra_serf.h b/subversion/libsvn_ra_serf/ra_serf.h index 335a9e3..fcef737 100644 --- a/subversion/libsvn_ra_serf/ra_serf.h +++ b/subversion/libsvn_ra_serf/ra_serf.h @@ -54,9 +54,6 @@ extern "C" { #error Please update your version of serf to at least 1.2.1. #endif -/** Use this to silence compiler warnings about unused parameters. */ -#define UNUSED_CTX(x) ((void)(x)) - /** Wait duration (in microseconds) used in calls to serf_context_run() */ #define SVN_RA_SERF__CONTEXT_RUN_DURATION 500000 @@ -99,10 +96,13 @@ typedef struct svn_ra_serf__connection_t { * The master serf RA session. * * This is stored in the ra session ->priv field. + * + * ### Check ra_serf_dup_session when adding fields. */ struct svn_ra_serf__session_t { /* Pool for allocations during this session */ apr_pool_t *pool; + apr_hash_t *config; /* For duplicating */ /* The current context */ serf_context_t *context; @@ -154,6 +154,7 @@ struct svn_ra_serf__session_t { /* Callback functions to get info from WC */ const svn_ra_callbacks2_t *wc_callbacks; void *wc_callback_baton; + svn_auth_baton_t *auth_baton; /* Callback function to send progress info to the client */ svn_ra_progress_notify_func_t progress_func; @@ -263,31 +264,11 @@ struct svn_ra_serf__session_t { */ typedef struct svn_ra_serf__dav_props_t { /* Element namespace */ - const char *namespace; + const char *xmlns; /* Element name */ const char *name; } svn_ra_serf__dav_props_t; -/* - * Structure which represents an XML namespace. - */ -typedef struct ns_t { - /* The assigned name. */ - const char *namespace; - /* The full URL for this namespace. */ - const char *url; - /* The next namespace in our list. */ - struct ns_t *next; -} svn_ra_serf__ns_t; - -/* - * An incredibly simple list. - */ -typedef struct ra_serf_list_t { - void *data; - struct ra_serf_list_t *next; -} svn_ra_serf__list_t; - /** DAV property sets **/ static const svn_ra_serf__dav_props_t base_props[] = @@ -378,6 +359,16 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done, svn_ra_serf__session_t *sess, apr_pool_t *scratch_pool); +/* Run the context once. Manage waittime_left to handle timing out when + nothing happens over the session->timout. + */ +svn_error_t * +svn_ra_serf__context_run(svn_ra_serf__session_t *sess, + apr_interval_time_t *waittime_left, + apr_pool_t *scratch_pool); + + + /* Callback for response handlers */ typedef svn_error_t * (*svn_ra_serf__response_handler_t)(serf_request_t *request, @@ -385,20 +376,26 @@ typedef svn_error_t * void *handler_baton, apr_pool_t *scratch_pool); +/* Callback when the request is done */ +typedef svn_error_t * +(*svn_ra_serf__response_done_delegate_t)(serf_request_t *request, + void *done_baton, + apr_pool_t *scratch_pool); + /* Callback for when a request body is needed. */ -/* ### should pass a scratch_pool */ typedef svn_error_t * (*svn_ra_serf__request_body_delegate_t)(serf_bucket_t **body_bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *request_pool); + apr_pool_t *request_pool, + apr_pool_t *scratch_pool); /* Callback for when request headers are needed. */ -/* ### should pass a scratch_pool */ typedef svn_error_t * (*svn_ra_serf__request_header_delegate_t)(serf_bucket_t *headers, void *baton, - apr_pool_t *request_pool); + apr_pool_t *request_pool, + apr_pool_t *scratch_pool); /* Callback for when a response has an error. */ typedef svn_error_t * @@ -413,6 +410,8 @@ typedef struct svn_ra_serf__server_error_t svn_ra_serf__server_error_t; /* * Structure that can be passed to our default handler to guide the * execution of the request through its lifecycle. + * + * Use svn_ra_serf__create_handler() to create instances of this struct. */ typedef struct svn_ra_serf__handler_t { /* The HTTP method string of the request */ @@ -429,8 +428,20 @@ typedef struct svn_ra_serf__handler_t { enabled. */ svn_boolean_t custom_accept_encoding; + /* If TRUE then default DAV: capabilities request headers is not configured + for request. */ + svn_boolean_t no_dav_headers; + + /* If TRUE doesn't fail requests on HTTP error statuses like 405, 408, 500 + (see util.c response_done()) */ + svn_boolean_t no_fail_on_http_failure_status; + + /* If TRUE doesn't fail requests on HTTP redirect statuses like 301, 307 */ + svn_boolean_t no_fail_on_http_redirect_status; + /* Has the request/response been completed? */ svn_boolean_t done; + svn_boolean_t scheduled; /* Is the request scheduled in a context */ /* If we captured an error from the server, then this will be non-NULL. It will be allocated from HANDLER_POOL. */ @@ -447,6 +458,19 @@ typedef struct svn_ra_serf__handler_t { serf_status_line sline; /* The parsed Status-Line */ const char *location; /* The Location: header, if any */ + /* This function and baton pair allows handling the completion of request. + * + * The default handler is responsible for the HTTP failure processing. + * + * If no_fail_on_http_failure_status is not TRUE, then the callback will + * return recorded server errors or if there is none and the http status + * specifies an error returns an error for that. + * + * The default baton is the handler itself. + */ + svn_ra_serf__response_done_delegate_t done_delegate; + void *done_delegate_baton; + /* The handler and baton pair to be executed when a non-recoverable error * is detected. If it is NULL in the presence of an error, an abort() may * be triggered. @@ -490,7 +514,6 @@ typedef struct svn_ra_serf__handler_t { /* Pool for allocating SLINE.REASON and LOCATION. If this pool is NULL, then the requestor does not care about SLINE and LOCATION. */ apr_pool_t *handler_pool; - } svn_ra_serf__handler_t; @@ -511,149 +534,6 @@ svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler, */ void svn_ra_serf__request_create(svn_ra_serf__handler_t *handler); -/* XML helper callbacks. */ - -typedef struct svn_ra_serf__xml_state_t { - /* A numeric value that represents the current state in parsing. - * - * Value 0 is reserved for use as the default state. - */ - int current_state; - - /* Private pointer set by the parsing code. */ - void *private; - - /* Allocations should be made in this pool to match the lifetime of the - * state. - */ - apr_pool_t *pool; - - /* The currently-declared namespace for this state. */ - svn_ra_serf__ns_t *ns_list; - - /* Our previous states. */ - struct svn_ra_serf__xml_state_t *prev; -} svn_ra_serf__xml_state_t; - -/* Forward declaration of the XML parser structure. */ -typedef struct svn_ra_serf__xml_parser_t svn_ra_serf__xml_parser_t; - -/* Callback invoked with @a baton by our XML @a parser when an element with - * the @a name containing @a attrs is opened. - */ -typedef svn_error_t * -(*svn_ra_serf__xml_start_element_t)(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - const char **attrs, - apr_pool_t *scratch_pool); - -/* Callback invoked with @a baton by our XML @a parser when an element with - * the @a name is closed. - */ -typedef svn_error_t * -(*svn_ra_serf__xml_end_element_t)(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - apr_pool_t *scratch_pool); - -/* Callback invoked with @a baton by our XML @a parser when a CDATA portion - * of @a data with size @a len is encountered. - * - * This may be invoked multiple times for the same tag. - */ -typedef svn_error_t * -(*svn_ra_serf__xml_cdata_chunk_handler_t)(svn_ra_serf__xml_parser_t *parser, - const char *data, - apr_size_t len, - apr_pool_t *scratch_pool); - -/* - * Helper structure associated with handle_xml_parser handler that will - * specify how an XML response will be processed. - */ -struct svn_ra_serf__xml_parser_t { - /* Temporary allocations should be made in this pool. */ - apr_pool_t *pool; - - /* What kind of response are we parsing? If set, this should typically - define the report name. */ - const char *response_type; - - /* Caller-specific data passed to the start, end, cdata callbacks. */ - void *user_data; - - /* Callback invoked when a tag is opened. */ - svn_ra_serf__xml_start_element_t start; - - /* Callback invoked when a tag is closed. */ - svn_ra_serf__xml_end_element_t end; - - /* Callback invoked when a cdata chunk is received. */ - svn_ra_serf__xml_cdata_chunk_handler_t cdata; - - /* Our associated expat-based XML parser. */ - XML_Parser xmlp; - - /* Our current state. */ - svn_ra_serf__xml_state_t *state; - - /* Our previously used states (will be reused). */ - svn_ra_serf__xml_state_t *free_state; - - /* If non-NULL, this value will be set to TRUE when the response is - * completed. - */ - svn_boolean_t *done; - - /* If non-NULL, when this parser completes, it will add done_item to - * the list. - */ - svn_ra_serf__list_t **done_list; - - /* A pointer to the item that will be inserted into the list upon - * completeion. - */ - svn_ra_serf__list_t *done_item; - - /* If this flag is TRUE, errors during parsing will be ignored. - * - * This is mainly used when we are processing an error XML response to - * avoid infinite loops. - */ - svn_boolean_t ignore_errors; - - /* If an error occurred, this value will be non-NULL. */ - svn_error_t *error; - - /* Deciding whether to pause, or not, is performed within the parsing - callbacks. If a callback decides to set this flag, then the loop - driving the parse (generally, a series of calls to serf_context_run()) - is going to need to coordinate the un-pausing of the parser by - processing pending content. Thus, deciding to pause the parser is a - coordinate effort rather than merely setting this flag. - - When an XML parsing callback sets this flag, note that additional - elements may be parsed (as the current buffer is consumed). At some - point, the flag will be recognized and arriving network content will - be stashed away in the PENDING structure (see below). - - At some point, the controlling loop should clear this value. The - underlying network processing will note the change and begin passing - content into the XML callbacks. - - Note that the controlling loop should also process pending content - since the arriving network content will typically finish first. */ - svn_boolean_t paused; - - /* While the XML parser is paused, content arriving from the server - must be saved locally. We cannot stop reading, or the server may - decide to drop the connection. The content will be stored in memory - up to a certain limit, and will then be spilled over to disk. - - See libsvn_ra_serf/util.c */ - struct svn_ra_serf__pending_t *pending; -}; - - /* v2 of the XML parsing functions */ /* The XML parsing context. */ @@ -719,6 +599,10 @@ typedef svn_error_t * apr_pool_t *scratch_pool); +/* Magic state value for the initial state in a svn_ra_serf__xml_transition_t + table */ +#define XML_STATE_INITIAL 0 + /* State transition table. When the XML Context is constructed, it is in state 0. User states are @@ -727,6 +611,8 @@ typedef svn_error_t * In a list of transitions, use { 0 } to indicate the end. Specifically, the code looks for NS == NULL. + The initial state for each transition table is XML_STATE_INITIAL. + ### more docco */ typedef struct svn_ra_serf__xml_transition_t { @@ -757,6 +643,11 @@ typedef struct svn_ra_serf__xml_transition_t { } svn_ra_serf__xml_transition_t; +/* Constructor for svn_ra_serf__handler_t. Initializes a new handler + with default settings for SESSION. */ +svn_ra_serf__handler_t * +svn_ra_serf__create_handler(svn_ra_serf__session_t *session, + apr_pool_t *result_pool); /* Construct an XML parsing context, based on the TTABLE transition table. As content is parsed, the CLOSED_CB callback will be invoked according @@ -783,18 +674,26 @@ svn_ra_serf__xml_context_create( void *baton, apr_pool_t *result_pool); -/* Destroy all subpools for this structure. */ -void -svn_ra_serf__xml_context_destroy( - svn_ra_serf__xml_context_t *xmlctx); +/* Verifies if the parsing completed successfully and destroys + all subpools. */ +svn_error_t * +svn_ra_serf__xml_context_done(svn_ra_serf__xml_context_t *xmlctx); /* Construct a handler with the response function/baton set up to parse a response body using the given XML context. The handler and its internal structures are allocated in RESULT_POOL. + As part of the handling the http status value is compared to 200, or + if EXPECTED_STATUS is not NULL to all the values in EXPECTED_STATUS. + EXPECTED_STATUS is expected to be a list of integers ending with a 0 + that lives at least as long as RESULT_POOL. If the status doesn't + match the request has failed and will be parsed as en error response. + This also initializes HANDLER_POOL to the given RESULT_POOL. */ svn_ra_serf__handler_t * -svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx, +svn_ra_serf__create_expat_handler(svn_ra_serf__session_t *session, + svn_ra_serf__xml_context_t *xmlctx, + const int *expected_status, apr_pool_t *result_pool); @@ -833,57 +732,25 @@ svn_ra_serf__xml_note(svn_ra_serf__xml_estate_t *xes, apr_pool_t * svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes); - -/* Any XML parser may be used. When an opening tag is seen, call this - function to feed the information into XMLCTX. */ -svn_error_t * -svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx, - const char *raw_name, - const char *const *attrs); - - -/* When a close tag is seen, call this function to feed the information - into XMLCTX. */ -svn_error_t * -svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx, - const char *raw_name); - - -/* When cdata is parsed by the wrapping XML parser, call this function to - feed the cdata into the XMLCTX. */ -svn_error_t * -svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx, - const char *data, - apr_size_t len); - - /* * Parses a server-side error message into a local Subversion error. */ struct svn_ra_serf__server_error_t { - /* Our local representation of the error. */ - svn_error_t *error; - - /* Are we done with the response? */ - svn_boolean_t done; - - /* Have we seen an error tag? */ - svn_boolean_t in_error; + apr_pool_t *pool; - /* Have we seen a HTTP "412 Precondition Failed" error? */ - svn_boolean_t contains_precondition_error; + /* XML parser and namespace used to parse the remote response */ + svn_ra_serf__xml_context_t *xmlctx; - /* Should we be collecting the XML cdata? */ - svn_boolean_t collect_cdata; + svn_ra_serf__response_handler_t response_handler; + void *response_baton; - /* Collected cdata. NULL if cdata not needed. */ - svn_stringbuf_t *cdata; + /* The partial errors to construct the final error from */ + apr_array_header_t *items; - /* XML parser and namespace used to parse the remote response */ - svn_ra_serf__xml_parser_t parser; + /* The hooked handler */ + svn_ra_serf__handler_t *handler; }; - /* * Handler that discards the entire @a response body associated with a * @a request. Implements svn_ra_serf__response_handler_t. @@ -940,20 +807,33 @@ svn_ra_serf__expect_empty_body(serf_request_t *request, /* - * This function will feed the RESPONSE body into XMLP. When parsing is - * completed (i.e. an EOF is received), *DONE is set to TRUE. - * Implements svn_ra_serf__response_handler_t. - * - * If an error occurs during processing RESP_ERR is invoked with the - * RESP_ERR_BATON. - * - * Temporary allocations are made in POOL. + * This function sets up error parsing for an existing request */ svn_error_t * -svn_ra_serf__handle_xml_parser(serf_request_t *request, - serf_bucket_t *response, - void *handler_baton, - apr_pool_t *pool); +svn_ra_serf__setup_error_parsing(svn_ra_serf__server_error_t **server_err, + svn_ra_serf__handler_t *handler, + svn_boolean_t expect_207_only, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* + * Forwards response data to the server error parser + */ +svn_error_t * +svn_ra_serf__handle_server_error(svn_ra_serf__server_error_t *server_error, + svn_ra_serf__handler_t *handler, + serf_request_t *request, + serf_bucket_t *response, + apr_status_t *serf_status, + apr_pool_t *scratch_pool); + +/* + * Creates the svn_error_t * instance from the error recorded in + * HANDLER->server_error + */ +svn_error_t * +svn_ra_serf__server_error_create(svn_ra_serf__handler_t *handler, + apr_pool_t *scratch_pool); /* serf_response_handler_t implementation that completely discards * the response. @@ -967,28 +847,6 @@ svn_ra_serf__response_discard_handler(serf_request_t *request, apr_pool_t *pool); -/** XML helper functions. **/ - -/* - * Advance the internal XML @a parser to the @a state. - */ -void -svn_ra_serf__xml_push_state(svn_ra_serf__xml_parser_t *parser, - int state); - -/* - * Return to the previous internal XML @a parser state. - */ -void -svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser); - - -svn_error_t * -svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser, - svn_boolean_t *network_eof, - apr_pool_t *scratch_pool); - - /* * Add the appropriate serf buckets to @a agg_bucket represented by * the XML * @a tag and @a value. @@ -1028,7 +886,7 @@ void svn_ra_serf__add_open_tag_buckets(serf_bucket_t *agg_bucket, serf_bucket_alloc_t *bkt_alloc, const char *tag, - ...); + ...) SVN_NEEDS_SENTINEL_NULL; /* * Add the appropriate serf buckets to AGG_BUCKET representing xml tag close @@ -1041,6 +899,16 @@ svn_ra_serf__add_close_tag_buckets(serf_bucket_t *agg_bucket, serf_bucket_alloc_t *bkt_alloc, const char *tag); +/* Add the appropriate serf buckets to AGG_BUCKET representing the XML + * open tag with name TAG, and then immediately closes the tag using the /> + * notation + */ +void +svn_ra_serf__add_empty_tag_buckets(serf_bucket_t *agg_bucket, + serf_bucket_alloc_t *bkt_alloc, + const char *tag, + ...) SVN_NEEDS_SENTINEL_NULL; + /* * Add the appropriate serf buckets to AGG_BUCKET with xml-escaped * version of DATA. @@ -1051,82 +919,55 @@ void svn_ra_serf__add_cdata_len_buckets(serf_bucket_t *agg_bucket, serf_bucket_alloc_t *bkt_alloc, const char *data, apr_size_t len); -/* - * Look up the @a attrs array for namespace definitions and add each one - * to the @a ns_list of namespaces. - * - * New namespaces will be allocated in RESULT_POOL. - */ -void -svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list, - const char *const *attrs, - apr_pool_t *result_pool); -/* - * Look up @a name in the @a ns_list list for previously declared namespace - * definitions. - * - * Return (in @a *returned_prop_name) a #svn_ra_serf__dav_props_t tuple - * representing the expanded name. - */ + +/** PROPFIND-related functions **/ + +/* Removes all non regular properties from PROPS */ void -svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name, - const svn_ra_serf__ns_t *ns_list, - const char *name); +svn_ra_serf__keep_only_regular_props(apr_hash_t *props, + apr_pool_t *scratch_pool); -/** PROPFIND-related functions **/ +/* Callback used via svn_ra_serf__deliver_props2 */ +typedef svn_error_t * +(*svn_ra_serf__prop_func_t)(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool); /* - * This function will deliver a PROP_CTX PROPFIND request in the SESS - * serf context for the properties listed in LOOKUP_PROPS at URL for - * DEPTH ("0","1","infinity"). - * - * This function will not block waiting for the response. Callers are - * expected to call svn_ra_serf__wait_for_props(). + * Implementation of svn_ra_serf__prop_func_t that just delivers svn compatible + * properties in the apr_hash_t * that is used as baton. */ svn_error_t * -svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler, - apr_hash_t *prop_vals, - svn_ra_serf__session_t *sess, - svn_ra_serf__connection_t *conn, - const char *url, - svn_revnum_t rev, - const char *depth, - const svn_ra_serf__dav_props_t *lookup_props, - svn_ra_serf__list_t **done_list, - apr_pool_t *pool); +svn_ra_serf__deliver_svn_props(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool); /* - * This helper function will block until PROPFIND_HANDLER indicates that is - * done or another error is returned. + * This function will create a handler for a PROPFIND request, which will deliver + * properties to PROP_FUNC() with PROP_BATON for the properties listed in LOOKUP_PROPS + * at URL for DEPTH ("0","1","infinity"). */ svn_error_t * -svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler, - apr_pool_t *scratch_pool); - -/* This is a blocking version of deliver_props. - - The properties are fetched and placed into RESULTS, allocated in - RESULT_POOL. - - ### more docco about the other params. - - Temporary allocations are made in SCRATCH_POOL. -*/ -svn_error_t * -svn_ra_serf__retrieve_props(apr_hash_t **results, - svn_ra_serf__session_t *sess, - svn_ra_serf__connection_t *conn, - const char *url, - svn_revnum_t rev, - const char *depth, - const svn_ra_serf__dav_props_t *props, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - - -/* Using CONN, fetch the properties specified by WHICH_PROPS using CONN +svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **handler, + svn_ra_serf__session_t *session, + const char *path, + svn_revnum_t rev, + const char *depth, + const svn_ra_serf__dav_props_t *find_props, + svn_ra_serf__prop_func_t prop_func, + void *prop_func_baton, + apr_pool_t *result_pool); + + +/* Using SESSION, fetch the properties specified by WHICH_PROPS using CONN for URL at REVISION. The resulting properties are placed into a 2-level hash in RESULTS, mapping NAMESPACE -> hash<PROPNAME, PROPVALUE>, which is allocated in RESULT_POOL. @@ -1139,7 +980,7 @@ svn_ra_serf__retrieve_props(apr_hash_t **results, Temporary allocations are made in SCRATCH_POOL. */ svn_error_t * svn_ra_serf__fetch_node_props(apr_hash_t **results, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, const char *url, svn_revnum_t revision, const svn_ra_serf__dav_props_t *which_props, @@ -1147,7 +988,7 @@ svn_ra_serf__fetch_node_props(apr_hash_t **results, apr_pool_t *scratch_pool); -/* Using CONN, fetch a DAV: property from the resource identified by URL +/* Using SESSION, fetch a DAV: property from the resource identified by URL within REVISION. The PROPNAME may be one of: "checked-in" @@ -1161,66 +1002,13 @@ svn_ra_serf__fetch_node_props(apr_hash_t **results, Temporary allocations are made in SCRATCH_POOL. */ svn_error_t * svn_ra_serf__fetch_dav_prop(const char **value, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, const char *url, svn_revnum_t revision, const char *propname, apr_pool_t *result_pool, apr_pool_t *scratch_pool); - -/* Set PROPS for PATH at REV revision with a NS:NAME VAL. - * - * The POOL governs allocation. - */ -void -svn_ra_serf__set_ver_prop(apr_hash_t *props, - const char *path, svn_revnum_t rev, - const char *ns, const char *name, - const svn_string_t *val, apr_pool_t *pool); -#define svn_ra_serf__set_rev_prop svn_ra_serf__set_ver_prop - -/** Property walker functions **/ - -typedef svn_error_t * -(*svn_ra_serf__walker_visitor_t)(void *baton, - const char *ns, - const char *name, - const svn_string_t *val, - apr_pool_t *pool); - -svn_error_t * -svn_ra_serf__walk_all_props(apr_hash_t *props, - const char *name, - svn_revnum_t rev, - svn_ra_serf__walker_visitor_t walker, - void *baton, - apr_pool_t *pool); - - -/* Like walk_all_props(), but a 2-level hash. */ -svn_error_t * -svn_ra_serf__walk_node_props(apr_hash_t *props, - svn_ra_serf__walker_visitor_t walker, - void *baton, - apr_pool_t *scratch_pool); - - -typedef svn_error_t * -(*svn_ra_serf__path_rev_walker_t)(void *baton, - const char *path, apr_ssize_t path_len, - const char *ns, apr_ssize_t ns_len, - const char *name, apr_ssize_t name_len, - const svn_string_t *val, - apr_pool_t *pool); -svn_error_t * -svn_ra_serf__walk_all_paths(apr_hash_t *props, - svn_revnum_t rev, - svn_ra_serf__path_rev_walker_t walker, - void *baton, - apr_pool_t *pool); - - /* Map a property name, as passed over the wire, into its corresponding Subversion-internal name. The returned name will be a static value, or allocated within RESULT_POOL. @@ -1232,75 +1020,6 @@ svn_ra_serf__svnname_from_wirename(const char *ns, const char *name, apr_pool_t *result_pool); - -/* Select the basic revision properties from the set of "all" properties. - Return these in *REVPROPS, allocated from RESULT_POOL. */ -svn_error_t * -svn_ra_serf__select_revprops(apr_hash_t **revprops, - const char *name, - svn_revnum_t rev, - apr_hash_t *all_revprops, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - - -/* PROPS is nested hash tables mapping NS -> NAME -> VALUE. - This function takes the NS:NAME:VALUE hashes and flattens them into a set of - names to VALUE. The names are composed of NS:NAME, with specific - rewrite from wire names (DAV) to SVN names. This mapping is managed - by the svn_ra_serf__set_baton_props() function. - - FLAT_PROPS is allocated in RESULT_POOL. - ### right now, we do a shallow copy from PROPS to FLAT_PROPS. therefore, - ### the names and values in PROPS must be in the proper pool. - - Temporary allocations are made in SCRATCH_POOL. */ -svn_error_t * -svn_ra_serf__flatten_props(apr_hash_t **flat_props, - apr_hash_t *props, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - - -/* Return the property value for PATH at REV revision with a NS:NAME. - * PROPS is a four-level nested hash: (svn_revnum_t => char *path => - * char *ns => char *name => svn_string_t *). */ -const svn_string_t * -svn_ra_serf__get_ver_prop_string(apr_hash_t *props, - const char *path, svn_revnum_t rev, - const char *ns, const char *name); - -/* Same as svn_ra_serf__get_ver_prop_string(), but returns a C string. */ -const char * -svn_ra_serf__get_ver_prop(apr_hash_t *props, - const char *path, svn_revnum_t rev, - const char *ns, const char *name); - -/* Same as svn_ra_serf__get_ver_prop_string(), but for the unknown revision. */ -const svn_string_t * -svn_ra_serf__get_prop_string(apr_hash_t *props, - const char *path, - const char *ns, - const char *name); - -/* Same as svn_ra_serf__get_ver_prop(), but for the unknown revision. */ -const char * -svn_ra_serf__get_prop(apr_hash_t *props, - const char *path, - const char *ns, - const char *name); - -/* Same as svn_ra_serf__set_rev_prop(), but for the unknown revision. */ -void -svn_ra_serf__set_prop(apr_hash_t *props, const char *path, - const char *ns, const char *name, - const svn_string_t *val, apr_pool_t *pool); - -svn_error_t * -svn_ra_serf__get_resource_type(svn_node_kind_t *kind, - apr_hash_t *props); - - /** MERGE-related functions **/ void @@ -1317,9 +1036,7 @@ svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens, locks set on the paths included in this commit. */ svn_error_t * svn_ra_serf__run_merge(const svn_commit_info_t **commit_info, - int *response_code, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, const char *merge_resource_url, apr_hash_t *lock_tokens, svn_boolean_t keep_locks, @@ -1347,7 +1064,7 @@ svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess, All temporary allocations will be made in SCRATCH_POOL. */ svn_error_t * svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, apr_pool_t *scratch_pool); @@ -1362,35 +1079,29 @@ svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest, All temporary allocations will be made in SCRATCH_POOL. */ svn_error_t * svn_ra_serf__v1_get_activity_collection(const char **activity_url, - svn_ra_serf__connection_t *conn, + svn_ra_serf__session_t *session, apr_pool_t *result_pool, apr_pool_t *scratch_pool); /* Set @a VCC_URL to the default VCC for our repository based on @a * ORIG_PATH for the session @a SESSION, ensuring that the VCC URL and - * repository root URLs are cached in @a SESSION. Use @a CONN for any - * required network communications if it is non-NULL; otherwise use the - * default connection. + * repository root URLs are cached in @a SESSION. * - * All temporary allocations will be made in @a POOL. */ + * All temporary allocations will be made in @a SCRATCH_POOL. */ svn_error_t * svn_ra_serf__discover_vcc(const char **vcc_url, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, - apr_pool_t *pool); + apr_pool_t *scratch_pool); /* Set @a REPORT_TARGET to the URI of the resource at which generic - * (path-agnostic) REPORTs should be aimed for @a SESSION. Use @a - * CONN for any required network communications if it is non-NULL; - * otherwise use the default connection. + * (path-agnostic) REPORTs should be aimed for @a SESSION. * * All temporary allocations will be made in @a POOL. */ svn_error_t * svn_ra_serf__report_resource(const char **report_target, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, apr_pool_t *pool); /* Set @a REL_PATH to a path (not URI-encoded) relative to the root of @@ -1402,7 +1113,6 @@ svn_error_t * svn_ra_serf__get_relative_path(const char **rel_path, const char *orig_path, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, apr_pool_t *pool); @@ -1429,11 +1139,9 @@ svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest, The DAV RA provider(s) solve this by generating a URL that is specific to a revision by using a URL into a "baseline collection". - For a specified SESSION, with an optional CONN (if NULL, then the - session's default connection will be used; specifically SESSION->conns[0]), - generate a revision-stable URL for URL at REVISION. If REVISION is - SVN_INVALID_REVNUM, then the stable URL will refer to the youngest - revision at the time this function was called. + For a specified SESSION, generate a revision-stable URL for URL at + REVISION. If REVISION is SVN_INVALID_REVNUM, then the stable URL will + refer to the youngest revision at the time this function was called. If URL is NULL, then the session root will be used. @@ -1452,7 +1160,6 @@ svn_error_t * svn_ra_serf__get_stable_url(const char **stable_url, svn_revnum_t *latest_revnum, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, const char *url, svn_revnum_t revision, apr_pool_t *result_pool, @@ -1461,6 +1168,20 @@ svn_ra_serf__get_stable_url(const char **stable_url, /** RA functions **/ +/* Implements svn_ra__vtable_t.reparent(). */ +svn_error_t * +svn_ra_serf__reparent(svn_ra_session_t *ra_session, + const char *url, + apr_pool_t *pool); + +/* Implements svn_ra__vtable_t.rev_prop(). */ +svn_error_t * +svn_ra_serf__rev_prop(svn_ra_session_t *session, + svn_revnum_t rev, + const char *name, + svn_string_t **value, + apr_pool_t *pool); + /* Implements svn_ra__vtable_t.get_log(). */ svn_error_t * svn_ra_serf__get_log(svn_ra_session_t *session, @@ -1476,6 +1197,22 @@ svn_ra_serf__get_log(svn_ra_session_t *session, void *receiver_baton, apr_pool_t *pool); +/* Implements svn_ra__vtable_t.check_path(). */ +svn_error_t * +svn_ra_serf__check_path(svn_ra_session_t *ra_session, + const char *rel_path, + svn_revnum_t revision, + svn_node_kind_t *kind, + apr_pool_t *pool); + +/* Implements svn_ra__vtable_t.stat(). */ +svn_error_t * +svn_ra_serf__stat(svn_ra_session_t *ra_session, + const char *rel_path, + svn_revnum_t revision, + svn_dirent_t **dirent, + apr_pool_t *pool); + /* Implements svn_ra__vtable_t.get_locations(). */ svn_error_t * svn_ra_serf__get_locations(svn_ra_session_t *session, @@ -1572,7 +1309,12 @@ svn_ra_serf__get_dated_revision(svn_ra_session_t *session, apr_time_t tm, apr_pool_t *pool); -/* Implements svn_ra__vtable_t.get_commit_editor(). */ +/* Implements svn_ra__vtable_t.get_commit_editor(). + * + * Note: Like other commit editors, the returned editor requires that the + * @c copyfrom_path parameter passed to its @c add_file and @c add_directory + * methods is a URL, not a relative path. + */ svn_error_t * svn_ra_serf__get_commit_editor(svn_ra_session_t *session, const svn_delta_editor_t **editor, @@ -1594,6 +1336,17 @@ svn_ra_serf__get_file(svn_ra_session_t *session, apr_hash_t **props, apr_pool_t *pool); +/* Implements svn_ra__vtable_t.get_dir(). */ +svn_error_t * +svn_ra_serf__get_dir(svn_ra_session_t *ra_session, + apr_hash_t **dirents, + svn_revnum_t *fetched_rev, + apr_hash_t **ret_props, + const char *rel_path, + svn_revnum_t revision, + apr_uint32_t dirent_fields, + apr_pool_t *result_pool); + /* Implements svn_ra__vtable_t.change_rev_prop(). */ svn_error_t * svn_ra_serf__change_rev_prop(svn_ra_session_t *session, @@ -1688,7 +1441,8 @@ svn_ra_serf__get_mergeinfo(svn_ra_session_t *ra_session, svn_error_t * svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess, const char **corrected_url, - apr_pool_t *pool); + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); /* Implements svn_ra__vtable_t.has_capability(). */ svn_error_t * @@ -1745,12 +1499,23 @@ svn_ra_serf__credentials_callback(char **username, char **password, * Convert an HTTP STATUS_CODE resulting from a WebDAV request against * PATH to the relevant error code. Use the response-supplied LOCATION * where it necessary. + * + * Returns SVN_NO_ERROR if sline doesn't specify an error condition */ svn_error_t * svn_ra_serf__error_on_status(serf_status_line sline, const char *path, const char *location); +/** + * Convert an unexpected HTTP STATUS_CODE from a request to the relevant error + * code. Unlike svn_ra_serf__error_on_status() this function creates an error + * for any result + */ +svn_error_t * +svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler); + + /* ###? */ svn_error_t * svn_ra_serf__copy_into_spillbuf(svn_spillbuf_t **spillbuf, @@ -1773,6 +1538,25 @@ svn_ra_serf__wrap_err(apr_status_t status, const char *fmt, ...); +/* Create a bucket that just returns DATA (with length LEN) and then returns + the APR_EAGAIN status */ +serf_bucket_t * +svn_ra_serf__create_bucket_with_eagain(const char *data, + apr_size_t len, + serf_bucket_alloc_t *allocator); + +/* Parse a given URL_STR, fill in all supplied fields of URI + * structure. + * + * This function is a compatibility wrapper around apr_uri_parse(). + * Different apr-util versions set apr_uri_t.path to either NULL or "" + * for root paths, and serf expects to see "/". This function always + * sets URI.path to "/" for these paths. */ +svn_error_t * +svn_ra_serf__uri_parse(apr_uri_t *uri, + const char *url_str, + apr_pool_t *result_pool); + #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 66e2f58..8d2da69 100644 --- a/subversion/libsvn_ra_serf/replay.c +++ b/subversion/libsvn_ra_serf/replay.c @@ -29,6 +29,7 @@ #include "svn_pools.h" #include "svn_ra.h" #include "svn_dav.h" +#include "svn_hash.h" #include "svn_xml.h" #include "../libsvn_ra/ra_loader.h" #include "svn_config.h" @@ -46,55 +47,93 @@ * This enum represents the current state of our XML parsing. */ typedef enum replay_state_e { - NONE = 0, - REPORT, - OPEN_DIR, - ADD_DIR, - OPEN_FILE, - ADD_FILE, - DELETE_ENTRY, - APPLY_TEXTDELTA, - CHANGE_PROP + INITIAL = XML_STATE_INITIAL, + + REPLAY_REPORT, + REPLAY_TARGET_REVISION, + REPLAY_OPEN_ROOT, + REPLAY_OPEN_DIRECTORY, + REPLAY_OPEN_FILE, + REPLAY_ADD_DIRECTORY, + REPLAY_ADD_FILE, + REPLAY_DELETE_ENTRY, + REPLAY_CLOSE_FILE, + REPLAY_CLOSE_DIRECTORY, + REPLAY_CHANGE_DIRECTORY_PROP, + REPLAY_CHANGE_FILE_PROP, + REPLAY_APPLY_TEXTDELTA } replay_state_e; -typedef struct replay_info_t replay_info_t; +#define S_ SVN_XML_NAMESPACE +static const svn_ra_serf__xml_transition_t replay_ttable[] = { + { INITIAL, S_, "editor-report", REPLAY_REPORT, + FALSE, { NULL }, TRUE }, -struct replay_info_t { - apr_pool_t *pool; + /* Replay just throws every operation as xml element directly + in the replay report, so we can't really use the nice exit + handling of the transition parser to handle clean callbacks */ - void *baton; - svn_stream_t *stream; + { REPLAY_REPORT, S_, "target-revision", REPLAY_TARGET_REVISION, + FALSE, { "rev", NULL }, TRUE }, - replay_info_t *parent; -}; + { REPLAY_REPORT, S_, "open-root", REPLAY_OPEN_ROOT, + FALSE, { "rev", NULL }, TRUE }, -typedef svn_error_t * -(*change_prop_t)(void *baton, - const char *name, - const svn_string_t *value, - apr_pool_t *pool); + { REPLAY_REPORT, S_, "open-directory", REPLAY_OPEN_DIRECTORY, + FALSE, { "name", "rev", NULL }, TRUE }, -typedef struct prop_info_t { - apr_pool_t *pool; + { REPLAY_REPORT, S_, "open-file", REPLAY_OPEN_FILE, + FALSE, { "name", "rev", NULL }, TRUE }, - change_prop_t change; + { REPLAY_REPORT, S_, "add-directory", REPLAY_ADD_DIRECTORY, + FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", NULL}, TRUE }, - const char *name; - svn_boolean_t del_prop; + { REPLAY_REPORT, S_, "add-file", REPLAY_ADD_FILE, + FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", NULL}, TRUE }, - svn_stringbuf_t *prop_value; + { REPLAY_REPORT, S_, "delete-entry", REPLAY_DELETE_ENTRY, + FALSE, { "name", "rev", NULL }, TRUE }, - replay_info_t *parent; -} prop_info_t; + { REPLAY_REPORT, S_, "close-file", REPLAY_CLOSE_FILE, + FALSE, { "?checksum", NULL }, TRUE }, -typedef struct replay_context_t { - apr_pool_t *src_rev_pool; - apr_pool_t *dst_rev_pool; + { REPLAY_REPORT, S_, "close-directory", REPLAY_CLOSE_DIRECTORY, + FALSE, { NULL }, TRUE }, - /* Are we done fetching this file? */ - svn_boolean_t done; - svn_ra_serf__list_t **done_list; - svn_ra_serf__list_t done_item; + { REPLAY_REPORT, S_, "change-dir-prop", REPLAY_CHANGE_DIRECTORY_PROP, + TRUE, { "name", "?del", NULL }, TRUE }, + + { REPLAY_REPORT, S_, "change-file-prop", REPLAY_CHANGE_FILE_PROP, + TRUE, { "name", "?del", NULL }, TRUE }, + + { REPLAY_REPORT, S_, "apply-textdelta", REPLAY_APPLY_TEXTDELTA, + FALSE, { "?checksum", NULL }, TRUE }, + + { 0 } +}; + +/* Per directory/file state */ +typedef struct replay_node_t { + apr_pool_t *pool; /* pool allocating this node's data */ + svn_boolean_t file; /* file or dir */ + + void *baton; /* node baton */ + svn_stream_t *stream; /* stream while handling txdata */ + + struct replay_node_t *parent; /* parent node or NULL */ +} replay_node_t; + +/* Per revision replay report state */ +typedef struct revision_report_t { + apr_pool_t *pool; /* per revision pool */ + + struct replay_node_t *current_node; + struct replay_node_t *root_node; + + /* Are we done fetching this file? + Handles book-keeping in multi-report case */ + svn_boolean_t *done; + int *replay_reports; /* NULL or number of outstanding reports */ /* callback to get an editor */ svn_ra_replay_revstart_callback_t revstart_func; @@ -121,479 +160,324 @@ typedef struct replay_context_t { svn_revnum_t revprop_rev; /* Revision properties for this revision. */ - apr_hash_t *revs_props; - apr_hash_t *props; - - /* Keep a reference to the XML parser ctx to report any errors. */ - svn_ra_serf__xml_parser_t *parser_ctx; + apr_hash_t *rev_props; /* Handlers for the PROPFIND and REPORT for the current revision. */ svn_ra_serf__handler_t *propfind_handler; - svn_ra_serf__handler_t *report_handler; - -} replay_context_t; - - -static void * -push_state(svn_ra_serf__xml_parser_t *parser, - replay_context_t *replay_ctx, - replay_state_e state) -{ - svn_ra_serf__xml_push_state(parser, state); - - if (state == OPEN_DIR || state == ADD_DIR || - state == OPEN_FILE || state == ADD_FILE) - { - replay_info_t *info; - apr_pool_t *pool = svn_pool_create(replay_ctx->dst_rev_pool); - - info = apr_palloc(pool, sizeof(*info)); - - info->pool = pool; - info->parent = parser->state->private; - info->baton = NULL; - info->stream = NULL; + svn_ra_serf__handler_t *report_handler; /* For done handler */ - parser->state->private = info; - } - else if (state == CHANGE_PROP) - { - prop_info_t *info; - apr_pool_t *pool = svn_pool_create(replay_ctx->dst_rev_pool); - - info = apr_pcalloc(pool, sizeof(*info)); - - info->pool = pool; - info->parent = parser->state->private; - info->prop_value = svn_stringbuf_create_empty(pool); - - parser->state->private = info; - } - - return parser->state->private; -} +} revision_report_t; +/* Conforms to svn_ra_serf__xml_opened_t */ static svn_error_t * -start_replay(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - const char **attrs, - apr_pool_t *scratch_pool) +replay_opened(svn_ra_serf__xml_estate_t *xes, + void *baton, + int entered_state, + const svn_ra_serf__dav_props_t *tag, + apr_pool_t *scratch_pool) { - replay_context_t *ctx = parser->user_data; - replay_state_e state; + struct revision_report_t *ctx = baton; - state = parser->state->current_state; - - if (state == NONE && - strcmp(name.name, "editor-report") == 0) + if (entered_state == REPLAY_REPORT) { - push_state(parser, ctx, REPORT); - /* Before we can continue, we need the revision properties. */ SVN_ERR_ASSERT(!ctx->propfind_handler || ctx->propfind_handler->done); - /* Create a pool for the commit editor. */ - ctx->dst_rev_pool = svn_pool_create(ctx->src_rev_pool); - - SVN_ERR(svn_ra_serf__select_revprops(&ctx->props, - ctx->revprop_target, - ctx->revprop_rev, - ctx->revs_props, - ctx->dst_rev_pool, - scratch_pool)); + svn_ra_serf__keep_only_regular_props(ctx->rev_props, scratch_pool); if (ctx->revstart_func) { SVN_ERR(ctx->revstart_func(ctx->revision, ctx->replay_baton, &ctx->editor, &ctx->editor_baton, - ctx->props, - ctx->dst_rev_pool)); + ctx->rev_props, + ctx->pool)); } } - else if (state == REPORT && - strcmp(name.name, "target-revision") == 0) + else if (entered_state == REPLAY_APPLY_TEXTDELTA) { - const char *rev; - - rev = svn_xml_get_attr_value("rev", attrs); - if (!rev) - { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing revision attr in target-revision element")); - } - - SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton, - SVN_STR_TO_REV(rev), - scratch_pool)); + struct replay_node_t *node = ctx->current_node; + apr_hash_t *attrs; + const char *checksum; + svn_txdelta_window_handler_t handler; + void *handler_baton; + + if (! node || ! node->file || node->stream) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); + + /* ### Is there a better way to access a specific attr here? */ + attrs = svn_ra_serf__xml_gather_since(xes, REPLAY_APPLY_TEXTDELTA); + checksum = svn_hash_gets(attrs, "checksum"); + + SVN_ERR(ctx->editor->apply_textdelta(node->baton, checksum, node->pool, + &handler, &handler_baton)); + + if (handler != svn_delta_noop_window_handler) + { + node->stream = svn_base64_decode( + svn_txdelta_parse_svndiff(handler, + handler_baton, + TRUE, + node->pool), + node->pool); + } } - else if (state == REPORT && - strcmp(name.name, "open-root") == 0) - { - const char *rev; - replay_info_t *info; - rev = svn_xml_get_attr_value("rev", attrs); - - if (!rev) - { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing revision attr in open-root element")); - } + return SVN_NO_ERROR; +} - info = push_state(parser, ctx, OPEN_DIR); +/* Conforms to svn_ra_serf__xml_closed_t */ +static svn_error_t * +replay_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) +{ + struct revision_report_t *ctx = baton; - SVN_ERR(ctx->editor->open_root(ctx->editor_baton, - SVN_STR_TO_REV(rev), - ctx->dst_rev_pool, - &info->baton)); - } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "delete-entry") == 0) + if (leaving_state == REPLAY_REPORT) { - const char *file_name, *rev; - replay_info_t *info; + if (ctx->current_node) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); - file_name = svn_xml_get_attr_value("name", attrs); - if (!file_name) - { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in delete-entry element")); - } - rev = svn_xml_get_attr_value("rev", attrs); - if (!rev) + if (ctx->revfinish_func) { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing revision attr in delete-entry element")); + SVN_ERR(ctx->revfinish_func(ctx->revision, ctx->replay_baton, + ctx->editor, ctx->editor_baton, + ctx->rev_props, scratch_pool)); } + } + else if (leaving_state == REPLAY_TARGET_REVISION) + { + const char *revstr = svn_hash_gets(attrs, "rev"); + apr_int64_t rev; - info = push_state(parser, ctx, DELETE_ENTRY); - - SVN_ERR(ctx->editor->delete_entry(file_name, SVN_STR_TO_REV(rev), - info->baton, scratch_pool)); - - svn_ra_serf__xml_pop_state(parser); + SVN_ERR(svn_cstring_atoi64(&rev, revstr)); + SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton, + (svn_revnum_t)rev, + scratch_pool)); } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "open-directory") == 0) + else if (leaving_state == REPLAY_OPEN_ROOT) { - const char *rev, *dir_name; - replay_info_t *info; + const char *revstr = svn_hash_gets(attrs, "rev"); + apr_int64_t rev; + apr_pool_t *root_pool = svn_pool_create(ctx->pool); - dir_name = svn_xml_get_attr_value("name", attrs); - if (!dir_name) - { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in open-directory element")); - } - rev = svn_xml_get_attr_value("rev", attrs); - if (!rev) - { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing revision attr in open-directory element")); - } + if (ctx->current_node || ctx->root_node) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); - info = push_state(parser, ctx, OPEN_DIR); + ctx->root_node = apr_pcalloc(root_pool, sizeof(*ctx->root_node)); + ctx->root_node->pool = root_pool; - SVN_ERR(ctx->editor->open_directory(dir_name, info->parent->baton, - SVN_STR_TO_REV(rev), - ctx->dst_rev_pool, &info->baton)); + ctx->current_node = ctx->root_node; + + SVN_ERR(svn_cstring_atoi64(&rev, revstr)); + SVN_ERR(ctx->editor->open_root(ctx->editor_baton, (svn_revnum_t)rev, + root_pool, + &ctx->current_node->baton)); } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "add-directory") == 0) + else if (leaving_state == REPLAY_OPEN_DIRECTORY + || leaving_state == REPLAY_OPEN_FILE + || leaving_state == REPLAY_ADD_DIRECTORY + || leaving_state == REPLAY_ADD_FILE) { - const char *dir_name, *copyfrom, *copyrev; - svn_revnum_t rev; - replay_info_t *info; - - dir_name = svn_xml_get_attr_value("name", attrs); - if (!dir_name) + struct replay_node_t *node; + apr_pool_t *node_pool; + const char *name = svn_hash_gets(attrs, "name"); + const char *rev_str; + apr_int64_t rev; + + if (!ctx->current_node || ctx->current_node->file) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); + + node_pool = svn_pool_create(ctx->current_node->pool); + node = apr_pcalloc(node_pool, sizeof(*node)); + node->pool = node_pool; + node->parent = ctx->current_node; + + if (leaving_state == REPLAY_OPEN_DIRECTORY + || leaving_state == REPLAY_OPEN_FILE) { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in add-directory element")); + rev_str = svn_hash_gets(attrs, "rev"); } - copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs); - copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs); + else + rev_str = svn_hash_gets(attrs, "copyfrom-rev"); - if (copyrev) - rev = SVN_STR_TO_REV(copyrev); + if (rev_str) + SVN_ERR(svn_cstring_atoi64(&rev, rev_str)); else rev = SVN_INVALID_REVNUM; - info = push_state(parser, ctx, ADD_DIR); - - SVN_ERR(ctx->editor->add_directory(dir_name, info->parent->baton, - copyfrom, rev, - ctx->dst_rev_pool, &info->baton)); - } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "close-directory") == 0) - { - replay_info_t *info = parser->state->private; - - SVN_ERR(ctx->editor->close_directory(info->baton, scratch_pool)); - - svn_ra_serf__xml_pop_state(parser); - - svn_pool_destroy(info->pool); - } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "open-file") == 0) - { - const char *file_name, *rev; - replay_info_t *info; - - file_name = svn_xml_get_attr_value("name", attrs); - if (!file_name) + switch (leaving_state) { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in open-file element")); + case REPLAY_OPEN_DIRECTORY: + node->file = FALSE; + SVN_ERR(ctx->editor->open_directory(name, + ctx->current_node->baton, + (svn_revnum_t)rev, + node->pool, + &node->baton)); + break; + case REPLAY_OPEN_FILE: + node->file = TRUE; + SVN_ERR(ctx->editor->open_file(name, + ctx->current_node->baton, + (svn_revnum_t)rev, + node->pool, + &node->baton)); + break; + case REPLAY_ADD_DIRECTORY: + node->file = FALSE; + SVN_ERR(ctx->editor->add_directory( + name, + ctx->current_node->baton, + SVN_IS_VALID_REVNUM(rev) + ? svn_hash_gets(attrs, "copyfrom-path") + : NULL, + (svn_revnum_t)rev, + node->pool, + &node->baton)); + break; + case REPLAY_ADD_FILE: + node->file = TRUE; + SVN_ERR(ctx->editor->add_file( + name, + ctx->current_node->baton, + SVN_IS_VALID_REVNUM(rev) + ? svn_hash_gets(attrs, "copyfrom-path") + : NULL, + (svn_revnum_t)rev, + node->pool, + &node->baton)); + break; + /* default: unreachable */ } - rev = svn_xml_get_attr_value("rev", attrs); - if (!rev) - { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing revision attr in open-file element")); - } - - info = push_state(parser, ctx, OPEN_FILE); - - SVN_ERR(ctx->editor->open_file(file_name, info->parent->baton, - SVN_STR_TO_REV(rev), - info->pool, &info->baton)); + ctx->current_node = node; } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "add-file") == 0) + else if (leaving_state == REPLAY_CLOSE_FILE) { - const char *file_name, *copyfrom, *copyrev; - svn_revnum_t rev; - replay_info_t *info; - - file_name = svn_xml_get_attr_value("name", attrs); - if (!file_name) - { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in add-file element")); - } - copyfrom = svn_xml_get_attr_value("copyfrom-path", attrs); - copyrev = svn_xml_get_attr_value("copyfrom-rev", attrs); - - info = push_state(parser, ctx, ADD_FILE); + struct replay_node_t *node = ctx->current_node; - if (copyrev) - rev = SVN_STR_TO_REV(copyrev); - else - rev = SVN_INVALID_REVNUM; + if (! node || ! node->file) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); - SVN_ERR(ctx->editor->add_file(file_name, info->parent->baton, - copyfrom, rev, - info->pool, &info->baton)); + SVN_ERR(ctx->editor->close_file(node->baton, + svn_hash_gets(attrs, "checksum"), + node->pool)); + ctx->current_node = node->parent; + svn_pool_destroy(node->pool); } - else if ((state == OPEN_FILE || state == ADD_FILE) && - strcmp(name.name, "apply-textdelta") == 0) + else if (leaving_state == REPLAY_CLOSE_DIRECTORY) { - const char *checksum; - replay_info_t *info; - svn_txdelta_window_handler_t textdelta; - void *textdelta_baton; - svn_stream_t *delta_stream; - - info = push_state(parser, ctx, APPLY_TEXTDELTA); - - checksum = svn_xml_get_attr_value("checksum", attrs); - if (checksum) - { - checksum = apr_pstrdup(info->pool, checksum); - } + struct replay_node_t *node = ctx->current_node; - SVN_ERR(ctx->editor->apply_textdelta(info->baton, checksum, - info->pool, - &textdelta, - &textdelta_baton)); + if (! node || node->file) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); - delta_stream = svn_txdelta_parse_svndiff(textdelta, textdelta_baton, - TRUE, info->pool); - info->stream = svn_base64_decode(delta_stream, info->pool); + SVN_ERR(ctx->editor->close_directory(node->baton, node->pool)); + ctx->current_node = node->parent; + svn_pool_destroy(node->pool); } - else if ((state == OPEN_FILE || state == ADD_FILE) && - strcmp(name.name, "close-file") == 0) + else if (leaving_state == REPLAY_DELETE_ENTRY) { - replay_info_t *info = parser->state->private; - const char *checksum; - - checksum = svn_xml_get_attr_value("checksum", attrs); - - SVN_ERR(ctx->editor->close_file(info->baton, checksum, scratch_pool)); - - svn_ra_serf__xml_pop_state(parser); - - svn_pool_destroy(info->pool); + struct replay_node_t *parent_node = ctx->current_node; + const char *name = svn_hash_gets(attrs, "name"); + const char *revstr = svn_hash_gets(attrs, "rev"); + apr_int64_t rev; + + if (! parent_node || parent_node->file) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); + + SVN_ERR(svn_cstring_atoi64(&rev, revstr)); + SVN_ERR(ctx->editor->delete_entry(name, + (svn_revnum_t)rev, + parent_node->baton, + scratch_pool)); } - else if (((state == OPEN_FILE || state == ADD_FILE) && - strcmp(name.name, "change-file-prop") == 0) || - ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "change-dir-prop") == 0)) + else if (leaving_state == REPLAY_CHANGE_FILE_PROP + || leaving_state == REPLAY_CHANGE_DIRECTORY_PROP) { - const char *prop_name; - prop_info_t *info; + struct replay_node_t *node = ctx->current_node; + const char *name; + const svn_string_t *value; - prop_name = svn_xml_get_attr_value("name", attrs); - if (!prop_name) - { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in %s element"), - name.name); - } - - info = push_state(parser, ctx, CHANGE_PROP); + if (! node || node->file != (leaving_state == REPLAY_CHANGE_FILE_PROP)) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); + name = svn_hash_gets(attrs, "name"); - if (svn_xml_get_attr_value("del", attrs)) - info->del_prop = TRUE; + if (svn_hash_gets(attrs, "del")) + value = NULL; else - info->del_prop = FALSE; + value = svn_base64_decode_string(cdata, scratch_pool); - info->name = apr_pstrdup(info->pool, prop_name); - if (state == OPEN_FILE || state == ADD_FILE) + if (node->file) { - info->change = ctx->editor->change_file_prop; + SVN_ERR(ctx->editor->change_file_prop(node->baton, name, value, + scratch_pool)); } else { - info->change = ctx->editor->change_dir_prop; - } - - } - - return SVN_NO_ERROR; -} - -static svn_error_t * -end_replay(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - apr_pool_t *scratch_pool) -{ - replay_context_t *ctx = parser->user_data; - replay_state_e state; - - state = parser->state->current_state; - - if (state == REPORT && - strcmp(name.name, "editor-report") == 0) - { - svn_ra_serf__xml_pop_state(parser); - if (ctx->revfinish_func) - { - SVN_ERR(ctx->revfinish_func(ctx->revision, ctx->replay_baton, - ctx->editor, ctx->editor_baton, - ctx->props, - ctx->dst_rev_pool)); + SVN_ERR(ctx->editor->change_dir_prop(node->baton, name, value, + scratch_pool)); } - svn_pool_destroy(ctx->dst_rev_pool); - } - else if (state == OPEN_DIR && strcmp(name.name, "open-directory") == 0) - { - /* Don't do anything. */ - } - else if (state == ADD_DIR && strcmp(name.name, "add-directory") == 0) - { - /* Don't do anything. */ - } - else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0) - { - /* Don't do anything. */ - } - else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0) - { - /* Don't do anything. */ - } - else if ((state == OPEN_FILE || state == ADD_FILE) && - strcmp(name.name, "close-file") == 0) - { - /* Don't do anything. */ - } - else if ((state == APPLY_TEXTDELTA) && - strcmp(name.name, "apply-textdelta") == 0) - { - replay_info_t *info = parser->state->private; - SVN_ERR(svn_stream_close(info->stream)); - svn_ra_serf__xml_pop_state(parser); } - else if (state == CHANGE_PROP && - (strcmp(name.name, "change-file-prop") == 0 || - strcmp(name.name, "change-dir-prop") == 0)) + else if (leaving_state == REPLAY_APPLY_TEXTDELTA) { - prop_info_t *info = parser->state->private; - const svn_string_t *prop_val; + struct replay_node_t *node = ctx->current_node; - if (info->del_prop) - { - prop_val = NULL; - } - else - { - const svn_string_t *morph; - - morph = svn_stringbuf__morph_into_string(info->prop_value); -#ifdef SVN_DEBUG - info->prop_value = NULL; /* morph killed the stringbuf. */ -#endif + if (! node || ! node->file || ! node->stream) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); - prop_val = svn_base64_decode_string(morph, info->pool); - } + SVN_ERR(svn_stream_close(node->stream)); - SVN_ERR(info->change(info->parent->baton, info->name, prop_val, - info->parent->pool)); - svn_ra_serf__xml_pop_state(parser); - - svn_pool_destroy(info->pool); + node->stream = NULL; } - return SVN_NO_ERROR; } +/* Conforms to svn_ra_serf__xml_cdata_t */ static svn_error_t * -cdata_replay(svn_ra_serf__xml_parser_t *parser, +replay_cdata(svn_ra_serf__xml_estate_t *xes, + void *baton, + int current_state, const char *data, apr_size_t len, apr_pool_t *scratch_pool) { - replay_context_t *replay_ctx = parser->user_data; - replay_state_e state; - - UNUSED_CTX(replay_ctx); + struct revision_report_t *ctx = baton; - state = parser->state->current_state; - - if (state == APPLY_TEXTDELTA) + if (current_state == REPLAY_APPLY_TEXTDELTA) { - replay_info_t *info = parser->state->private; - apr_size_t written; - - written = len; + struct replay_node_t *node = ctx->current_node; - SVN_ERR(svn_stream_write(info->stream, data, &written)); + if (! node || ! node->file) + return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, NULL); - if (written != len) - return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, - _("Error writing stream: unexpected EOF")); - } - else if (state == CHANGE_PROP) - { - prop_info_t *info = parser->state->private; + if (node->stream) + { + apr_size_t written = len; - svn_stringbuf_appendbytes(info->prop_value, data, len); + SVN_ERR(svn_stream_write(node->stream, data, &written)); + if (written != len) + return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, + _("Error writing stream: unexpected EOF")); + } } return SVN_NO_ERROR; } +/* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_replay_body(serf_bucket_t **bkt, void *baton, serf_bucket_alloc_t *alloc, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { - replay_context_t *ctx = baton; + struct revision_report_t *ctx = baton; serf_bucket_t *body_bkt; body_bkt = serf_bucket_aggregate_create(alloc); @@ -601,7 +485,7 @@ create_replay_body(serf_bucket_t **bkt, svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "S:replay-report", "xmlns:S", SVN_XML_NAMESPACE, - NULL); + SVN_VA_NULL); /* If we have a non-NULL include path, we add it to the body and omit the revision; otherwise, the reverse. */ @@ -616,17 +500,17 @@ create_replay_body(serf_bucket_t **bkt, { svn_ra_serf__add_tag_buckets(body_bkt, "S:revision", - apr_ltoa(ctx->src_rev_pool, ctx->revision), + apr_ltoa(pool, ctx->revision), alloc); } svn_ra_serf__add_tag_buckets(body_bkt, "S:low-water-mark", - apr_ltoa(ctx->src_rev_pool, ctx->low_water_mark), + apr_ltoa(pool, ctx->low_water_mark), alloc); svn_ra_serf__add_tag_buckets(body_bkt, "S:send-deltas", - apr_ltoa(ctx->src_rev_pool, ctx->send_deltas), + apr_ltoa(pool, ctx->send_deltas), alloc); svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "S:replay-report"); @@ -642,65 +526,49 @@ svn_ra_serf__replay(svn_ra_session_t *ra_session, svn_boolean_t send_deltas, const svn_delta_editor_t *editor, void *edit_baton, - apr_pool_t *pool) + apr_pool_t *scratch_pool) { - replay_context_t *replay_ctx; + struct revision_report_t ctx = { NULL }; svn_ra_serf__session_t *session = ra_session->priv; svn_ra_serf__handler_t *handler; - svn_ra_serf__xml_parser_t *parser_ctx; - svn_error_t *err; + svn_ra_serf__xml_context_t *xmlctx; const char *report_target; - SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool)); + SVN_ERR(svn_ra_serf__report_resource(&report_target, session, + scratch_pool)); + + ctx.pool = svn_pool_create(scratch_pool); + ctx.editor = editor; + ctx.editor_baton = edit_baton; + ctx.done = FALSE; + ctx.revision = revision; + ctx.low_water_mark = low_water_mark; + ctx.send_deltas = send_deltas; + ctx.rev_props = apr_hash_make(scratch_pool); - replay_ctx = apr_pcalloc(pool, sizeof(*replay_ctx)); - replay_ctx->src_rev_pool = pool; - replay_ctx->editor = editor; - replay_ctx->editor_baton = edit_baton; - replay_ctx->done = FALSE; - replay_ctx->revision = revision; - replay_ctx->low_water_mark = low_water_mark; - replay_ctx->send_deltas = send_deltas; - replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool); + xmlctx = svn_ra_serf__xml_context_create(replay_ttable, + replay_opened, replay_closed, + replay_cdata, + &ctx, + scratch_pool); - handler = apr_pcalloc(pool, sizeof(*handler)); + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, + scratch_pool); - handler->handler_pool = pool; handler->method = "REPORT"; handler->path = session->session_url.path; handler->body_delegate = create_replay_body; - handler->body_delegate_baton = replay_ctx; + handler->body_delegate_baton = &ctx; handler->body_type = "text/xml"; - handler->conn = session->conns[0]; - handler->session = session; - parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx)); + /* Not setting up done handler as we don't use a global context */ - parser_ctx->pool = pool; - parser_ctx->user_data = replay_ctx; - parser_ctx->start = start_replay; - parser_ctx->end = end_replay; - parser_ctx->cdata = cdata_replay; - parser_ctx->done = &replay_ctx->done; + SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); - handler->response_handler = svn_ra_serf__handle_xml_parser; - handler->response_baton = parser_ctx; - - /* This is only needed to handle errors during XML parsing. */ - replay_ctx->parser_ctx = parser_ctx; - replay_ctx->report_handler = handler; /* unused */ - - svn_ra_serf__request_create(handler); - - err = svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool); - - SVN_ERR(svn_error_compose_create( + return svn_error_trace( svn_ra_serf__error_on_status(handler->sline, handler->path, - handler->location), - err)); - - return SVN_NO_ERROR; + handler->location)); } /* The maximum number of outstanding requests at any time. When this @@ -734,6 +602,33 @@ svn_ra_serf__replay(svn_ra_session_t *ra_session, */ #define MAX_OUTSTANDING_REQUESTS 50 +/* Implements svn_ra_serf__response_done_delegate_t for svn_ra_serf__replay_range */ +static svn_error_t * +replay_done(serf_request_t *request, + void *baton, + apr_pool_t *scratch_pool) +{ + struct revision_report_t *ctx = baton; + svn_ra_serf__handler_t *handler = ctx->report_handler; + + if (handler->server_error) + return svn_ra_serf__server_error_create(handler, scratch_pool); + else if (handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); + + *ctx->done = TRUE; /* Breaks out svn_ra_serf__context_run_wait */ + + /* Are re replaying multiple revisions? */ + if (ctx->replay_reports) + { + (*ctx->replay_reports)--; + } + + svn_pool_destroy(ctx->pool); /* Destroys handler and request! */ + + return SVN_NO_ERROR; +} + svn_error_t * svn_ra_serf__replay_range(svn_ra_session_t *ra_session, svn_revnum_t start_revision, @@ -743,15 +638,17 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session, svn_ra_replay_revstart_callback_t revstart_func, svn_ra_replay_revfinish_callback_t revfinish_func, void *replay_baton, - apr_pool_t *pool) + apr_pool_t *scratch_pool) { svn_ra_serf__session_t *session = ra_session->priv; svn_revnum_t rev = start_revision; const char *report_target; int active_reports = 0; const char *include_path; + svn_boolean_t done; - SVN_ERR(svn_ra_serf__report_resource(&report_target, session, NULL, pool)); + SVN_ERR(svn_ra_serf__report_resource(&report_target, session, + scratch_pool)); /* Prior to 1.8, mod_dav_svn expect to get replay REPORT requests aimed at the session URL. But that's incorrect -- these reports @@ -774,8 +671,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, session->conns[0], - pool)); + session, scratch_pool)); } else { @@ -784,10 +680,6 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session, while (active_reports || rev <= end_revision) { - svn_ra_serf__list_t *done_list; - svn_ra_serf__list_t *done_reports = NULL; - replay_context_t *replay_ctx; - if (session->cancel_func) SVN_ERR(session->cancel_func(session->cancel_baton)); @@ -795,54 +687,56 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session, requests to MAX_OUTSTANDING_REQUESTS. */ if (rev <= end_revision && active_reports < MAX_OUTSTANDING_REQUESTS) { + struct revision_report_t *rev_ctx; svn_ra_serf__handler_t *handler; - svn_ra_serf__xml_parser_t *parser_ctx; - apr_pool_t *ctx_pool = svn_pool_create(pool); + apr_pool_t *rev_pool = svn_pool_create(scratch_pool); + svn_ra_serf__xml_context_t *xmlctx; const char *replay_target; - replay_ctx = apr_pcalloc(ctx_pool, sizeof(*replay_ctx)); - replay_ctx->src_rev_pool = ctx_pool; - replay_ctx->revstart_func = revstart_func; - replay_ctx->revfinish_func = revfinish_func; - replay_ctx->replay_baton = replay_baton; - replay_ctx->done = FALSE; - replay_ctx->include_path = include_path; - replay_ctx->revision = rev; - replay_ctx->low_water_mark = low_water_mark; - replay_ctx->send_deltas = send_deltas; - replay_ctx->done_item.data = replay_ctx; + rev_ctx = apr_pcalloc(rev_pool, sizeof(*rev_ctx)); + rev_ctx->pool = rev_pool; + rev_ctx->revstart_func = revstart_func; + rev_ctx->revfinish_func = revfinish_func; + rev_ctx->replay_baton = replay_baton; + rev_ctx->done = &done; + rev_ctx->replay_reports = &active_reports; + rev_ctx->include_path = include_path; + rev_ctx->revision = rev; + rev_ctx->low_water_mark = low_water_mark; + rev_ctx->send_deltas = send_deltas; /* Request all properties of a certain revision. */ - replay_ctx->revs_props = apr_hash_make(replay_ctx->src_rev_pool); + rev_ctx->rev_props = apr_hash_make(rev_ctx->pool); if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) { - replay_ctx->revprop_target = apr_psprintf(pool, "%s/%ld", - session->rev_stub, rev); - replay_ctx->revprop_rev = SVN_INVALID_REVNUM; + rev_ctx->revprop_target = apr_psprintf(rev_pool, "%s/%ld", + session->rev_stub, rev); + rev_ctx->revprop_rev = SVN_INVALID_REVNUM; } else { - replay_ctx->revprop_target = report_target; - replay_ctx->revprop_rev = rev; + rev_ctx->revprop_target = report_target; + rev_ctx->revprop_rev = rev; } - SVN_ERR(svn_ra_serf__deliver_props(&replay_ctx->propfind_handler, - replay_ctx->revs_props, session, - session->conns[0], - replay_ctx->revprop_target, - replay_ctx->revprop_rev, - "0", all_props, - NULL, - replay_ctx->src_rev_pool)); + SVN_ERR(svn_ra_serf__create_propfind_handler( + &rev_ctx->propfind_handler, + session, + rev_ctx->revprop_target, + rev_ctx->revprop_rev, + "0", all_props, + svn_ra_serf__deliver_svn_props, + rev_ctx->rev_props, + rev_pool)); /* Spin up the serf request for the PROPFIND. */ - svn_ra_serf__request_create(replay_ctx->propfind_handler); + svn_ra_serf__request_create(rev_ctx->propfind_handler); /* Send the replay REPORT request. */ if (session->supports_rev_rsrc_replay) { - replay_target = apr_psprintf(pool, "%s/%ld", + replay_target = apr_psprintf(rev_pool, "%s/%ld", session->rev_stub, rev); } else @@ -850,41 +744,24 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session, replay_target = session->session_url.path; } - handler = apr_pcalloc(replay_ctx->src_rev_pool, sizeof(*handler)); + xmlctx = svn_ra_serf__xml_context_create(replay_ttable, + replay_opened, replay_closed, + replay_cdata, rev_ctx, + rev_pool); + + handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, + rev_pool); - handler->handler_pool = replay_ctx->src_rev_pool; handler->method = "REPORT"; handler->path = replay_target; handler->body_delegate = create_replay_body; - handler->body_delegate_baton = replay_ctx; - handler->conn = session->conns[0]; - handler->session = session; - - parser_ctx = apr_pcalloc(replay_ctx->src_rev_pool, - sizeof(*parser_ctx)); - - /* Setup the XML parser context. - Because we have not one but a list of requests, the 'done' property - on the replay_ctx is not of much use. Instead, use 'done_list'. - On each handled response (succesfully or not), the parser will add - done_item to done_list, so by keeping track of the state of - done_list we know how many requests have been handled completely. - */ - parser_ctx->pool = replay_ctx->src_rev_pool; - parser_ctx->user_data = replay_ctx; - parser_ctx->start = start_replay; - parser_ctx->end = end_replay; - parser_ctx->cdata = cdata_replay; - parser_ctx->done = &replay_ctx->done; - parser_ctx->done_list = &done_reports; - parser_ctx->done_item = &replay_ctx->done_item; - handler->response_handler = svn_ra_serf__handle_xml_parser; - handler->response_baton = parser_ctx; - replay_ctx->report_handler = handler; - - /* This is only needed to handle errors during XML parsing. */ - replay_ctx->parser_ctx = parser_ctx; + handler->body_delegate_baton = rev_ctx; + handler->body_type = "text/xml"; + + handler->done_delegate = replay_done; + handler->done_delegate_baton = rev_ctx; + rev_ctx->report_handler = handler; svn_ra_serf__request_create(handler); rev++; @@ -892,26 +769,12 @@ svn_ra_serf__replay_range(svn_ra_session_t *ra_session, } /* Run the serf loop. */ - SVN_ERR(svn_ra_serf__context_run_wait(&replay_ctx->done, session, pool)); - - /* Substract the number of completely handled responses from our - total nr. of open requests', so we'll know when to stop this loop. - Since the message is completely handled, we can destroy its pool. */ - done_list = done_reports; - while (done_list) - { - replay_context_t *ctx = (replay_context_t *)done_list->data; - svn_ra_serf__handler_t *done_handler = ctx->report_handler; - - done_list = done_list->next; - SVN_ERR(svn_ra_serf__error_on_status(done_handler->sline, - done_handler->path, - done_handler->location)); - svn_pool_destroy(ctx->src_rev_pool); - active_reports--; - } + done = FALSE; + SVN_ERR(svn_ra_serf__context_run_wait(&done, session, scratch_pool)); - done_reports = NULL; + /* 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. */ } return SVN_NO_ERROR; diff --git a/subversion/libsvn_ra_serf/serf.c b/subversion/libsvn_ra_serf/serf.c index 66f9962..3c47d5e 100644 --- a/subversion/libsvn_ra_serf/serf.c +++ b/subversion/libsvn_ra_serf/serf.c @@ -39,6 +39,7 @@ #include "svn_dirent_uri.h" #include "svn_hash.h" #include "svn_path.h" +#include "svn_props.h" #include "svn_time.h" #include "svn_version.h" @@ -63,7 +64,7 @@ ra_serf_version(void) #define RA_SERF_DESCRIPTION_VER \ N_("Module for accessing a repository via WebDAV protocol using serf.\n" \ - " - using serf %d.%d.%d") + " - using serf %d.%d.%d (compiled with %d.%d.%d)") /* Implements svn_ra__vtable_t.get_description(). */ static const char * @@ -72,7 +73,12 @@ ra_serf_get_description(apr_pool_t *pool) int major, minor, patch; serf_lib_version(&major, &minor, &patch); - return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), major, minor, patch); + return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), + major, minor, patch, + SERF_MAJOR_VERSION, + SERF_MINOR_VERSION, + SERF_PATCH_VERSION + ); } /* Implements svn_ra__vtable_t.get_schemes(). */ @@ -144,10 +150,6 @@ load_http_auth_types(apr_pool_t *pool, svn_config_t *config, runtime configuration variable. */ #define DEFAULT_HTTP_TIMEOUT 600 -/* Private symbol for the 1.9-public SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS */ -#define OPTION_HTTP_CHUNKED_REQUESTS "http-chunked-requests" - - static svn_error_t * load_config(svn_ra_serf__session_t *session, apr_hash_t *config_hash, @@ -161,6 +163,10 @@ load_config(svn_ra_serf__session_t *session, const char *exceptions; apr_port_t proxy_port; svn_tristate_t chunked_requests; +#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) + apr_int64_t log_components; + apr_int64_t log_level; +#endif if (config_hash) { @@ -179,17 +185,17 @@ load_config(svn_ra_serf__session_t *session, svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL, SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL); - if (session->wc_callbacks->auth_baton) + if (session->auth_baton) { if (config_client) { - svn_auth_set_parameter(session->wc_callbacks->auth_baton, + svn_auth_set_parameter(session->auth_baton, SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG, config_client); } if (config) { - svn_auth_set_parameter(session->wc_callbacks->auth_baton, + svn_auth_set_parameter(session->auth_baton, SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS, config); } @@ -237,18 +243,25 @@ load_config(svn_ra_serf__session_t *session, SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS)); - /* Should we use chunked transfer encoding. */ + /* Should we use chunked transfer encoding. */ SVN_ERR(svn_config_get_tristate(config, &chunked_requests, SVN_CONFIG_SECTION_GLOBAL, - OPTION_HTTP_CHUNKED_REQUESTS, + SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS, "auto", svn_tristate_unknown)); - if (config) - server_group = svn_config_find_group(config, - session->session_url.hostname, - SVN_CONFIG_SECTION_GROUPS, pool); - else - server_group = NULL; +#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) + SVN_ERR(svn_config_get_int64(config, &log_components, + SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS, + SERF_LOGCOMP_NONE)); + SVN_ERR(svn_config_get_int64(config, &log_level, + SVN_CONFIG_SECTION_GLOBAL, + SVN_CONFIG_OPTION_SERF_LOG_LEVEL, + SERF_LOG_INFO)); +#endif + + server_group = svn_auth_get_parameter(session->auth_baton, + SVN_AUTH_PARAM_SERVER_GROUP); if (server_group) { @@ -259,9 +272,6 @@ load_config(svn_ra_serf__session_t *session, svn_config_get(config, &timeout_str, server_group, SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str); - svn_auth_set_parameter(session->wc_callbacks->auth_baton, - SVN_AUTH_PARAM_SERVER_GROUP, server_group); - /* Load the group proxy server settings, overriding global settings. We intentionally ignore 'http-proxy-exceptions' here because, well, if this site was an exception, why is @@ -300,12 +310,42 @@ load_config(svn_ra_serf__session_t *session, SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, session->max_connections)); - /* Should we use chunked transfer encoding. */ + /* Should we use chunked transfer encoding. */ SVN_ERR(svn_config_get_tristate(config, &chunked_requests, server_group, - OPTION_HTTP_CHUNKED_REQUESTS, + SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS, "auto", chunked_requests)); + +#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) + SVN_ERR(svn_config_get_int64(config, &log_components, + server_group, + SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS, + log_components)); + SVN_ERR(svn_config_get_int64(config, &log_level, + server_group, + SVN_CONFIG_OPTION_SERF_LOG_LEVEL, + log_level)); +#endif + } + +#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING) + if (log_components != SERF_LOGCOMP_NONE) + { + serf_log_output_t *output; + apr_status_t status; + + status = serf_logging_create_stream_output(&output, + session->context, + (apr_uint32_t)log_level, + (apr_uint32_t)log_components, + SERF_LOG_DEFAULT_LAYOUT, + stderr, + pool); + + if (!status) + serf_logging_add_output(session->context, output); } +#endif /* Don't allow the http-max-connections value to be larger than our compiled-in limit, or to be too small to operate. Broken @@ -439,8 +479,10 @@ svn_ra_serf__open(svn_ra_session_t *session, const char *session_URL, const svn_ra_callbacks2_t *callbacks, void *callback_baton, + svn_auth_baton_t *auth_baton, apr_hash_t *config, - apr_pool_t *pool) + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { apr_status_t status; svn_ra_serf__session_t *serf_sess; @@ -451,10 +493,15 @@ svn_ra_serf__open(svn_ra_session_t *session, if (corrected_url) *corrected_url = NULL; - serf_sess = apr_pcalloc(pool, sizeof(*serf_sess)); - serf_sess->pool = svn_pool_create(pool); + serf_sess = apr_pcalloc(result_pool, sizeof(*serf_sess)); + serf_sess->pool = result_pool; + if (config) + SVN_ERR(svn_config_copy_config(&serf_sess->config, config, result_pool)); + else + serf_sess->config = NULL; serf_sess->wc_callbacks = callbacks; serf_sess->wc_callback_baton = callback_baton; + serf_sess->auth_baton = auth_baton; serf_sess->progress_func = callbacks->progress_func; serf_sess->progress_baton = callbacks->progress_baton; serf_sess->cancel_func = callbacks->cancel_func; @@ -467,19 +514,8 @@ svn_ra_serf__open(svn_ra_session_t *session, serf_sess->pool)); - status = apr_uri_parse(serf_sess->pool, session_URL, &url); - if (status) - { - return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, - _("Illegal URL '%s'"), - session_URL); - } - /* Depending the version of apr-util in use, for root paths url.path - will be NULL or "", where serf requires "/". */ - if (url.path == NULL || url.path[0] == '\0') - { - url.path = apr_pstrdup(serf_sess->pool, "/"); - } + SVN_ERR(svn_ra_serf__uri_parse(&url, session_URL, serf_sess->pool)); + if (!url.port) { url.port = apr_uri_port_of_scheme(url.scheme); @@ -511,13 +547,16 @@ svn_ra_serf__open(svn_ra_session_t *session, /* create the user agent string */ if (callbacks->get_client_string) - SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool)); + SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, + scratch_pool)); if (client_string) - serf_sess->useragent = apr_pstrcat(pool, get_user_agent_string(pool), " ", - client_string, (char *)NULL); + serf_sess->useragent = apr_pstrcat(result_pool, + get_user_agent_string(scratch_pool), + " ", + client_string, SVN_VA_NULL); else - serf_sess->useragent = get_user_agent_string(pool); + serf_sess->useragent = get_user_agent_string(result_pool); /* go ahead and tell serf about the connection. */ status = @@ -538,7 +577,24 @@ svn_ra_serf__open(svn_ra_session_t *session, session->priv = serf_sess; - err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool); + /* The following code explicitly works around a bug in serf <= r2319 / 1.3.8 + where serf doesn't report the request as failed/cancelled when the + authorization request handler fails to handle the request. + + As long as we allocate the request in a subpool of the serf connection + pool, we know that the handler is always cleaned before the connection. + + Luckily our caller now passes us two pools which handle this case. + */ +#if defined(SVN_DEBUG) && !SERF_VERSION_AT_LEAST(1,4,0) + /* Currently ensured by svn_ra_open4(). + If failing causes segfault in basic_tests.py 48, "basic auth test" */ + SVN_ERR_ASSERT((serf_sess->pool != scratch_pool) + && apr_pool_is_ancestor(serf_sess->pool, scratch_pool)); +#endif + + err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, + result_pool, scratch_pool); /* serf should produce a usable error code instead of APR_EGENERAL */ if (err && err->apr_err == APR_EGENERAL) @@ -552,20 +608,188 @@ svn_ra_serf__open(svn_ra_session_t *session, problems in any proxy. */ if ((corrected_url == NULL || *corrected_url == NULL) && serf_sess->detect_chunking && !serf_sess->http10) - SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, pool)); + SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, scratch_pool)); return SVN_NO_ERROR; } -/* Implements svn_ra__vtable_t.reparent(). */ +/* Implements svn_ra__vtable_t.dup_session */ static svn_error_t * +ra_serf_dup_session(svn_ra_session_t *new_session, + svn_ra_session_t *old_session, + const char *new_session_url, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__session_t *old_sess = old_session->priv; + svn_ra_serf__session_t *new_sess; + apr_status_t status; + + new_sess = apr_pmemdup(result_pool, old_sess, sizeof(*new_sess)); + + new_sess->pool = result_pool; + + if (new_sess->config) + SVN_ERR(svn_config_copy_config(&new_sess->config, new_sess->config, + result_pool)); + + /* max_connections */ + /* using_ssl */ + /* using_compression */ + /* http10 */ + /* using_chunked_requests */ + /* detect_chunking */ + + if (new_sess->useragent) + new_sess->useragent = apr_pstrdup(result_pool, new_sess->useragent); + + if (new_sess->vcc_url) + new_sess->vcc_url = apr_pstrdup(result_pool, new_sess->vcc_url); + + new_sess->auth_state = NULL; + new_sess->auth_attempts = 0; + + /* Callback functions to get info from WC */ + /* wc_callbacks */ + /* wc_callback_baton */ + + /* progress_func */ + /* progress_baton */ + + /* cancel_func */ + /* cancel_baton */ + + /* shim_callbacks */ + + new_sess->pending_error = NULL; + + /* authn_types */ + + /* Keys and values are static */ + if (new_sess->capabilities) + new_sess->capabilities = apr_hash_copy(result_pool, new_sess->capabilities); + + if (new_sess->activity_collection_url) + { + new_sess->activity_collection_url + = apr_pstrdup(result_pool, new_sess->activity_collection_url); + } + + /* using_proxy */ + + if (new_sess->proxy_username) + { + new_sess->proxy_username + = apr_pstrdup(result_pool, new_sess->proxy_username); + } + + if (new_sess->proxy_password) + { + new_sess->proxy_username + = apr_pstrdup(result_pool, new_sess->proxy_password); + } + + new_sess->proxy_auth_attempts = 0; + + /* trust_default_ca */ + + if (new_sess->ssl_authorities) + { + new_sess->ssl_authorities = apr_pstrdup(result_pool, + new_sess->ssl_authorities); + } + + if (new_sess->uuid) + new_sess->uuid = apr_pstrdup(result_pool, new_sess->uuid); + + /* timeout */ + /* supports_deadprop_count */ + + if (new_sess->me_resource) + new_sess->me_resource = apr_pstrdup(result_pool, new_sess->me_resource); + if (new_sess->rev_stub) + new_sess->rev_stub = apr_pstrdup(result_pool, new_sess->rev_stub); + if (new_sess->txn_stub) + new_sess->txn_stub = apr_pstrdup(result_pool, new_sess->txn_stub); + if (new_sess->txn_root_stub) + new_sess->txn_root_stub = apr_pstrdup(result_pool, + new_sess->txn_root_stub); + if (new_sess->vtxn_stub) + new_sess->vtxn_stub = apr_pstrdup(result_pool, new_sess->vtxn_stub); + if (new_sess->vtxn_root_stub) + new_sess->vtxn_root_stub = apr_pstrdup(result_pool, + new_sess->vtxn_root_stub); + + /* Keys and values are static */ + if (new_sess->supported_posts) + new_sess->supported_posts = apr_hash_copy(result_pool, + new_sess->supported_posts); + + /* ### Can we copy this? */ + SVN_ERR(svn_ra_serf__blncache_create(&new_sess->blncache, + new_sess->pool)); + + if (new_sess->server_allows_bulk) + 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)); + + new_sess->session_url_str = apr_pstrdup(result_pool, new_session_url); + + SVN_ERR(svn_ra_serf__uri_parse(&new_sess->session_url, + new_sess->session_url_str, + result_pool)); + + /* svn_boolean_t supports_inline_props */ + /* supports_rev_rsrc_replay */ + + new_sess->context = serf_context_create(result_pool); + + SVN_ERR(load_config(new_sess, old_sess->config, result_pool)); + + new_sess->conns[0] = apr_pcalloc(result_pool, + sizeof(*new_sess->conns[0])); + new_sess->conns[0]->bkt_alloc = + serf_bucket_allocator_create(result_pool, NULL, NULL); + new_sess->conns[0]->session = new_sess; + new_sess->conns[0]->last_status_code = -1; + + /* go ahead and tell serf about the connection. */ + status = + serf_connection_create2(&new_sess->conns[0]->conn, + new_sess->context, + new_sess->session_url, + svn_ra_serf__conn_setup, new_sess->conns[0], + svn_ra_serf__conn_closed, new_sess->conns[0], + result_pool); + if (status) + return svn_ra_serf__wrap_err(status, NULL); + + /* Set the progress callback. */ + serf_context_set_progress_cb(new_sess->context, svn_ra_serf__progress, + new_sess); + + new_sess->num_conns = 1; + new_sess->cur_conn = 0; + + new_session->priv = new_sess; + + return SVN_NO_ERROR; +} + +/* Implements svn_ra__vtable_t.reparent(). */ +svn_error_t * svn_ra_serf__reparent(svn_ra_session_t *ra_session, const char *url, apr_pool_t *pool) { svn_ra_serf__session_t *session = ra_session->priv; apr_uri_t new_url; - apr_status_t status; /* If it's the URL we already have, wave our hands and do nothing. */ if (strcmp(session->session_url_str, url) == 0) @@ -576,7 +800,7 @@ svn_ra_serf__reparent(svn_ra_session_t *ra_session, if (!session->repos_root_str) { const char *vcc_url; - SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); + SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool)); } if (!svn_uri__is_ancestor(session->repos_root_str, url)) @@ -587,25 +811,11 @@ svn_ra_serf__reparent(svn_ra_session_t *ra_session, "URL '%s'"), url, session->repos_root_str); } - status = apr_uri_parse(pool, url, &new_url); - if (status) - { - return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, - _("Illegal repository URL '%s'"), url); - } + SVN_ERR(svn_ra_serf__uri_parse(&new_url, url, pool)); - /* Depending the version of apr-util in use, for root paths url.path - will be NULL or "", where serf requires "/". */ /* ### Maybe we should use a string buffer for these strings so we ### don't allocate memory in the session on every reparent? */ - if (new_url.path == NULL || new_url.path[0] == '\0') - { - session->session_url.path = apr_pstrdup(session->pool, "/"); - } - else - { - session->session_url.path = apr_pstrdup(session->pool, new_url.path); - } + session->session_url.path = apr_pstrdup(session->pool, new_url.path); session->session_url_str = apr_pstrdup(session->pool, url); return SVN_NO_ERROR; @@ -634,20 +844,24 @@ svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session, latest_revnum, session, pool)); } -/* Implements svn_ra__vtable_t.rev_proplist(). */ +/* Implementation of svn_ra_serf__rev_proplist(). */ static svn_error_t * -svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session, - svn_revnum_t rev, - apr_hash_t **ret_props, - apr_pool_t *pool) +serf__rev_proplist(svn_ra_session_t *ra_session, + svn_revnum_t rev, + const svn_ra_serf__dav_props_t *fetch_props, + apr_hash_t **ret_props, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { svn_ra_serf__session_t *session = ra_session->priv; apr_hash_t *props; const char *propfind_path; + svn_ra_serf__handler_t *handler; if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) { - propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev); + propfind_path = apr_psprintf(scratch_pool, "%s/%ld", session->rev_stub, + rev); /* svn_ra_serf__retrieve_props() wants to added the revision as a Label to the PROPFIND, which isn't really necessary when @@ -658,507 +872,79 @@ svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session, else { /* Use the VCC as the propfind target path. */ - SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool)); - } - - /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */ - SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0], - propfind_path, rev, "0", all_props, - pool, pool)); - - SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props, - pool, pool)); - - return SVN_NO_ERROR; -} - -/* Implements svn_ra__vtable_t.rev_prop(). */ -static svn_error_t * -svn_ra_serf__rev_prop(svn_ra_session_t *session, - svn_revnum_t rev, - const char *name, - svn_string_t **value, - apr_pool_t *pool) -{ - apr_hash_t *props; - - SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool)); - - *value = svn_hash_gets(props, name); - - return SVN_NO_ERROR; -} - -static svn_error_t * -fetch_path_props(apr_hash_t **props, - svn_ra_serf__session_t *session, - const char *session_relpath, - svn_revnum_t revision, - const svn_ra_serf__dav_props_t *desired_props, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *url; - - url = session->session_url.path; - - /* If we have a relative path, append it. */ - if (session_relpath) - url = svn_path_url_add_component2(url, session_relpath, scratch_pool); - - /* If we were given a specific revision, get a URL that refers to that - specific revision (rather than floating with HEAD). */ - if (SVN_IS_VALID_REVNUM(revision)) - { - SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, - session, NULL /* conn */, - url, revision, - scratch_pool, scratch_pool)); - } - - /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant. - Or we started with SVN_INVALID_REVNUM and URL may be floating. */ - SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0], - url, SVN_INVALID_REVNUM, - desired_props, - result_pool, scratch_pool)); - - return SVN_NO_ERROR; -} - -/* Implements svn_ra__vtable_t.check_path(). */ -static svn_error_t * -svn_ra_serf__check_path(svn_ra_session_t *ra_session, - const char *rel_path, - svn_revnum_t revision, - svn_node_kind_t *kind, - apr_pool_t *pool) -{ - svn_ra_serf__session_t *session = ra_session->priv; - apr_hash_t *props; - - svn_error_t *err = fetch_path_props(&props, session, rel_path, - revision, check_path_props, - pool, pool); - - if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) - { - svn_error_clear(err); - *kind = svn_node_none; + SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, + scratch_pool)); } - else - { - /* Any other error, raise to caller. */ - if (err) - return svn_error_trace(err); - SVN_ERR(svn_ra_serf__get_resource_type(kind, props)); - } - - return SVN_NO_ERROR; -} + props = apr_hash_make(result_pool); + SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, + propfind_path, rev, "0", + fetch_props, + svn_ra_serf__deliver_svn_props, + props, + scratch_pool)); + SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); -struct dirent_walker_baton_t { - /* Update the fields in this entry. */ - svn_dirent_t *entry; + svn_ra_serf__keep_only_regular_props(props, scratch_pool); - svn_tristate_t *supports_deadprop_count; - - /* If allocations are necessary, then use this pool. */ - apr_pool_t *result_pool; -}; - -static svn_error_t * -dirent_walker(void *baton, - const char *ns, - const char *name, - const svn_string_t *val, - apr_pool_t *scratch_pool) -{ - struct dirent_walker_baton_t *dwb = baton; - - if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) - { - dwb->entry->has_props = TRUE; - } - else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) - { - dwb->entry->has_props = TRUE; - } - else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) - { - if(strcmp(name, "deadprop-count") == 0) - { - if (*val->data) - { - apr_int64_t deadprop_count; - SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data)); - dwb->entry->has_props = deadprop_count > 0; - if (dwb->supports_deadprop_count) - *dwb->supports_deadprop_count = svn_tristate_true; - } - else if (dwb->supports_deadprop_count) - *dwb->supports_deadprop_count = svn_tristate_false; - } - } - else if (strcmp(ns, "DAV:") == 0) - { - if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) - { - dwb->entry->created_rev = SVN_STR_TO_REV(val->data); - } - else if (strcmp(name, "creator-displayname") == 0) - { - dwb->entry->last_author = val->data; - } - else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) - { - SVN_ERR(svn_time_from_cstring(&dwb->entry->time, - val->data, - dwb->result_pool)); - } - else if (strcmp(name, "getcontentlength") == 0) - { - /* 'getcontentlength' property is empty for directories. */ - if (val->len) - { - SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data)); - } - } - else if (strcmp(name, "resourcetype") == 0) - { - if (strcmp(val->data, "collection") == 0) - { - dwb->entry->kind = svn_node_dir; - } - else - { - dwb->entry->kind = svn_node_file; - } - } - } + *ret_props = props; return SVN_NO_ERROR; } -struct path_dirent_visitor_t { - apr_hash_t *full_paths; - apr_hash_t *base_paths; - const char *orig_path; - svn_tristate_t supports_deadprop_count; - apr_pool_t *result_pool; -}; - -static svn_error_t * -path_dirent_walker(void *baton, - const char *path, apr_ssize_t path_len, - const char *ns, apr_ssize_t ns_len, - const char *name, apr_ssize_t name_len, - const svn_string_t *val, - apr_pool_t *pool) -{ - struct path_dirent_visitor_t *dirents = baton; - struct dirent_walker_baton_t dwb; - svn_dirent_t *entry; - - /* Skip our original path. */ - if (strcmp(path, dirents->orig_path) == 0) - { - return SVN_NO_ERROR; - } - - entry = apr_hash_get(dirents->full_paths, path, path_len); - - if (!entry) - { - const char *base_name; - - entry = svn_dirent_create(pool); - - apr_hash_set(dirents->full_paths, path, path_len, entry); - - base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool), - pool); - - svn_hash_sets(dirents->base_paths, base_name, entry); - } - - dwb.entry = entry; - dwb.supports_deadprop_count = &dirents->supports_deadprop_count; - dwb.result_pool = dirents->result_pool; - return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool)); -} - -static const svn_ra_serf__dav_props_t * -get_dirent_props(apr_uint32_t dirent_fields, - svn_ra_serf__session_t *session, - apr_pool_t *pool) -{ - svn_ra_serf__dav_props_t *prop; - apr_array_header_t *props = apr_array_make - (pool, 7, sizeof(svn_ra_serf__dav_props_t)); - - if (session->supports_deadprop_count != svn_tristate_false - || ! (dirent_fields & SVN_DIRENT_HAS_PROPS)) - { - if (dirent_fields & SVN_DIRENT_KIND) - { - prop = apr_array_push(props); - prop->namespace = "DAV:"; - prop->name = "resourcetype"; - } - - if (dirent_fields & SVN_DIRENT_SIZE) - { - prop = apr_array_push(props); - prop->namespace = "DAV:"; - prop->name = "getcontentlength"; - } - - if (dirent_fields & SVN_DIRENT_HAS_PROPS) - { - prop = apr_array_push(props); - prop->namespace = SVN_DAV_PROP_NS_DAV; - prop->name = "deadprop-count"; - } - - if (dirent_fields & SVN_DIRENT_CREATED_REV) - { - svn_ra_serf__dav_props_t *p = apr_array_push(props); - p->namespace = "DAV:"; - p->name = SVN_DAV__VERSION_NAME; - } - - if (dirent_fields & SVN_DIRENT_TIME) - { - prop = apr_array_push(props); - prop->namespace = "DAV:"; - prop->name = SVN_DAV__CREATIONDATE; - } - - if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) - { - prop = apr_array_push(props); - prop->namespace = "DAV:"; - prop->name = "creator-displayname"; - } - } - else - { - /* We found an old subversion server that can't handle - the deadprop-count property in the way we expect. - - The neon behavior is to retrieve all properties in this case */ - prop = apr_array_push(props); - prop->namespace = "DAV:"; - prop->name = "allprop"; - } - - prop = apr_array_push(props); - prop->namespace = NULL; - prop->name = NULL; - - return (svn_ra_serf__dav_props_t *) props->elts; -} - -/* Implements svn_ra__vtable_t.stat(). */ +/* Implements svn_ra__vtable_t.rev_proplist(). */ static svn_error_t * -svn_ra_serf__stat(svn_ra_session_t *ra_session, - const char *rel_path, - svn_revnum_t revision, - svn_dirent_t **dirent, - apr_pool_t *pool) +svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session, + svn_revnum_t rev, + apr_hash_t **ret_props, + apr_pool_t *result_pool) { - svn_ra_serf__session_t *session = ra_session->priv; - apr_hash_t *props; + apr_pool_t *scratch_pool = svn_pool_create(result_pool); svn_error_t *err; - struct dirent_walker_baton_t dwb; - svn_tristate_t deadprop_count = svn_tristate_unknown; - - err = fetch_path_props(&props, - session, rel_path, revision, - get_dirent_props(SVN_DIRENT_ALL, session, pool), - pool, pool); - if (err) - { - if (err->apr_err == SVN_ERR_FS_NOT_FOUND) - { - svn_error_clear(err); - *dirent = NULL; - return SVN_NO_ERROR; - } - else - return svn_error_trace(err); - } - dwb.entry = svn_dirent_create(pool); - dwb.supports_deadprop_count = &deadprop_count; - dwb.result_pool = pool; - SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool)); + err = serf__rev_proplist(ra_session, rev, all_props, ret_props, + result_pool, scratch_pool); - if (deadprop_count == svn_tristate_false - && session->supports_deadprop_count == svn_tristate_unknown - && !dwb.entry->has_props) - { - /* We have to requery as the server didn't give us the right - information */ - session->supports_deadprop_count = svn_tristate_false; - - SVN_ERR(fetch_path_props(&props, - session, rel_path, SVN_INVALID_REVNUM, - get_dirent_props(SVN_DIRENT_ALL, session, pool), - pool, pool)); - - SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool)); - } - - if (deadprop_count != svn_tristate_unknown) - session->supports_deadprop_count = deadprop_count; - - *dirent = dwb.entry; - - return SVN_NO_ERROR; + svn_pool_destroy(scratch_pool); + return svn_error_trace(err); } -/* Reads the 'resourcetype' property from the list PROPS and checks if the - * resource at PATH@REVISION really is a directory. Returns - * SVN_ERR_FS_NOT_DIRECTORY if not. - */ -static svn_error_t * -resource_is_directory(apr_hash_t *props) -{ - svn_node_kind_t kind; - - SVN_ERR(svn_ra_serf__get_resource_type(&kind, props)); - - if (kind != svn_node_dir) - { - return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, - _("Can't get entries of non-directory")); - } - - return SVN_NO_ERROR; -} -/* Implements svn_ra__vtable_t.get_dir(). */ -static svn_error_t * -svn_ra_serf__get_dir(svn_ra_session_t *ra_session, - apr_hash_t **dirents, - svn_revnum_t *fetched_rev, - apr_hash_t **ret_props, - const char *rel_path, - svn_revnum_t revision, - apr_uint32_t dirent_fields, - apr_pool_t *pool) +/* Implements svn_ra__vtable_t.rev_prop(). */ +svn_error_t * +svn_ra_serf__rev_prop(svn_ra_session_t *session, + svn_revnum_t rev, + const char *name, + svn_string_t **value, + apr_pool_t *result_pool) { - svn_ra_serf__session_t *session = ra_session->priv; - const char *path; - - path = session->session_url.path; - - /* If we have a relative path, URI encode and append it. */ - if (rel_path) - { - path = svn_path_url_add_component2(path, rel_path, pool); - } - - /* If the user specified a peg revision other than HEAD, we have to fetch - the baseline collection url for that revision. If not, we can use the - public url. */ - if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) - { - SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev, - session, NULL /* conn */, - path, revision, - pool, pool)); - revision = SVN_INVALID_REVNUM; - } - /* REVISION is always SVN_INVALID_REVNUM */ - SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); - - /* If we're asked for children, fetch them now. */ - if (dirents) + apr_pool_t *scratch_pool = svn_pool_create(result_pool); + apr_hash_t *props; + svn_ra_serf__dav_props_t specific_props[2]; + const svn_ra_serf__dav_props_t *fetch_props = all_props; + + /* The DAV propfind doesn't allow property fetches for any property name + as there is no defined way to quote values. If we are just fetching a + "svn:property" we can safely do this. In other cases we just fetch all + revision properties and filter the right one out */ + if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX)-1) == 0 + && !strchr(name + sizeof(SVN_PROP_PREFIX)-1, ':')) { - struct path_dirent_visitor_t dirent_walk; - apr_hash_t *props; - const char *rtype; - - /* Always request node kind to check that path is really a - * directory. - */ - dirent_fields |= SVN_DIRENT_KIND; - SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0], - path, SVN_INVALID_REVNUM, "1", - get_dirent_props(dirent_fields, - session, pool), - pool, pool)); - - /* Check if the path is really a directory. */ - rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype"); - if (rtype == NULL || strcmp(rtype, "collection") != 0) - return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, - _("Can't get entries of non-directory")); - - /* We're going to create two hashes to help the walker along. - * We're going to return the 2nd one back to the caller as it - * will have the basenames it expects. - */ - dirent_walk.full_paths = apr_hash_make(pool); - dirent_walk.base_paths = apr_hash_make(pool); - dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool); - dirent_walk.supports_deadprop_count = svn_tristate_unknown; - dirent_walk.result_pool = pool; - - SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM, - path_dirent_walker, &dirent_walk, - pool)); - - if (dirent_walk.supports_deadprop_count == svn_tristate_false - && session->supports_deadprop_count == svn_tristate_unknown - && dirent_fields & SVN_DIRENT_HAS_PROPS) - { - /* We have to requery as the server didn't give us the right - information */ - session->supports_deadprop_count = svn_tristate_false; - SVN_ERR(svn_ra_serf__retrieve_props(&props, session, - session->conns[0], - path, SVN_INVALID_REVNUM, "1", - get_dirent_props(dirent_fields, - session, pool), - pool, pool)); - - apr_hash_clear(dirent_walk.full_paths); - apr_hash_clear(dirent_walk.base_paths); - - SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM, - path_dirent_walker, - &dirent_walk, pool)); - } - - *dirents = dirent_walk.base_paths; + specific_props[0].xmlns = SVN_DAV_PROP_NS_SVN; + specific_props[0].name = name + sizeof(SVN_PROP_PREFIX)-1; + specific_props[1].xmlns = NULL; + specific_props[1].name = NULL; - if (dirent_walk.supports_deadprop_count != svn_tristate_unknown) - session->supports_deadprop_count = dirent_walk.supports_deadprop_count; + fetch_props = specific_props; } - /* If we're asked for the directory properties, fetch them too. */ - if (ret_props) - { - apr_hash_t *props; - - SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0], - path, SVN_INVALID_REVNUM, - all_props, - pool, pool)); + SVN_ERR(serf__rev_proplist(session, rev, fetch_props, &props, + result_pool, scratch_pool)); - /* Check if the path is really a directory. */ - SVN_ERR(resource_is_directory(props)); + *value = svn_hash_gets(props, name); - /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props() - ### put them into POOL, so we're okay. */ - SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool)); - } + svn_pool_destroy(scratch_pool); return SVN_NO_ERROR; } @@ -1173,7 +959,7 @@ svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session, if (!session->repos_root_str) { const char *vcc_url; - SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); + SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool)); } *url = session->repos_root_str; @@ -1209,7 +995,7 @@ svn_ra_serf__get_uuid(svn_ra_session_t *ra_session, /* We're not interested in vcc_url and relative_url, but this call also stores the repository's uuid in the session. */ - SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool)); + SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool)); if (!session->uuid) { return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL, @@ -1229,6 +1015,7 @@ static const svn_ra__vtable_t serf_vtable = { ra_serf_get_description, ra_serf_get_schemes, svn_ra_serf__open, + ra_serf_dup_session, svn_ra_serf__reparent, svn_ra_serf__get_session_url, svn_ra_serf__get_latest_revnum, diff --git a/subversion/libsvn_ra_serf/stat.c b/subversion/libsvn_ra_serf/stat.c new file mode 100644 index 0000000..b6d10c5 --- /dev/null +++ b/subversion/libsvn_ra_serf/stat.c @@ -0,0 +1,615 @@ +/* + * stat.c : file and directory stat and read functions + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include <serf.h> + +#include "svn_private_config.h" +#include "svn_pools.h" +#include "svn_xml.h" +#include "../libsvn_ra/ra_loader.h" +#include "svn_config.h" +#include "svn_dirent_uri.h" +#include "svn_hash.h" +#include "svn_path.h" +#include "svn_props.h" +#include "svn_time.h" +#include "svn_version.h" + +#include "private/svn_dav_protocol.h" +#include "private/svn_dep_compat.h" +#include "private/svn_fspath.h" + +#include "ra_serf.h" + + + +/* Implements svn_ra__vtable_t.check_path(). */ +svn_error_t * +svn_ra_serf__check_path(svn_ra_session_t *ra_session, + const char *relpath, + svn_revnum_t revision, + svn_node_kind_t *kind, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + apr_hash_t *props; + svn_error_t *err; + const char *url; + + url = session->session_url.path; + + /* If we have a relative path, append it. */ + if (relpath) + url = svn_path_url_add_component2(url, relpath, scratch_pool); + + /* If we were given a specific revision, get a URL that refers to that + specific revision (rather than floating with HEAD). */ + if (SVN_IS_VALID_REVNUM(revision)) + { + SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, + session, + url, revision, + scratch_pool, scratch_pool)); + } + + /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant. + Or we started with SVN_INVALID_REVNUM and URL may be floating. */ + err = svn_ra_serf__fetch_node_props(&props, session, + url, SVN_INVALID_REVNUM, + check_path_props, + scratch_pool, scratch_pool); + + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + *kind = svn_node_none; + } + else + { + apr_hash_t *dav_props; + const char *res_type; + + /* Any other error, raise to caller. */ + SVN_ERR(err); + + dav_props = apr_hash_get(props, "DAV:", 4); + res_type = svn_prop_get_value(dav_props, "resourcetype"); + if (!res_type) + { + /* How did this happen? */ + return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, + _("The PROPFIND response did not include the " + "requested resourcetype value")); + } + + if (strcmp(res_type, "collection") == 0) + *kind = svn_node_dir; + else + *kind = svn_node_file; + } + + return SVN_NO_ERROR; +} + + +/* Baton for fill_dirent_propfunc() */ +struct fill_dirent_baton_t +{ + /* Update the fields in this entry. */ + svn_dirent_t *entry; + + svn_tristate_t *supports_deadprop_count; + + /* If allocations are necessary, then use this pool. */ + apr_pool_t *result_pool; +}; + +/* Implements svn_ra_serf__prop_func_t */ +static svn_error_t * +fill_dirent_propfunc(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *val, + apr_pool_t *scratch_pool) +{ + struct fill_dirent_baton_t *fdb = baton; + + if (strcmp(ns, "DAV:") == 0) + { + if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) + { + apr_int64_t rev; + SVN_ERR(svn_cstring_atoi64(&rev, val->data)); + + fdb->entry->created_rev = (svn_revnum_t)rev; + } + else if (strcmp(name, "creator-displayname") == 0) + { + fdb->entry->last_author = apr_pstrdup(fdb->result_pool, val->data); + } + else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) + { + SVN_ERR(svn_time_from_cstring(&fdb->entry->time, + val->data, + fdb->result_pool)); + } + else if (strcmp(name, "getcontentlength") == 0) + { + /* 'getcontentlength' property is empty for directories. */ + if (val->len) + { + SVN_ERR(svn_cstring_atoi64(&fdb->entry->size, val->data)); + } + } + else if (strcmp(name, "resourcetype") == 0) + { + if (strcmp(val->data, "collection") == 0) + { + fdb->entry->kind = svn_node_dir; + } + else + { + fdb->entry->kind = svn_node_file; + } + } + } + else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) + { + fdb->entry->has_props = TRUE; + } + else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) + { + fdb->entry->has_props = TRUE; + } + else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) + { + if(strcmp(name, "deadprop-count") == 0) + { + if (*val->data) + { + apr_int64_t deadprop_count; + SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data)); + fdb->entry->has_props = deadprop_count > 0; + if (fdb->supports_deadprop_count) + *fdb->supports_deadprop_count = svn_tristate_true; + } + else if (fdb->supports_deadprop_count) + *fdb->supports_deadprop_count = svn_tristate_false; + } + } + + return SVN_NO_ERROR; +} + +static const svn_ra_serf__dav_props_t * +get_dirent_props(apr_uint32_t dirent_fields, + svn_ra_serf__session_t *session, + apr_pool_t *pool) +{ + svn_ra_serf__dav_props_t *prop; + apr_array_header_t *props = apr_array_make + (pool, 7, sizeof(svn_ra_serf__dav_props_t)); + + if (session->supports_deadprop_count != svn_tristate_false + || ! (dirent_fields & SVN_DIRENT_HAS_PROPS)) + { + if (dirent_fields & SVN_DIRENT_KIND) + { + prop = apr_array_push(props); + prop->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"; + } + + prop = apr_array_push(props); + prop->xmlns = NULL; + prop->name = NULL; + + return (svn_ra_serf__dav_props_t *) props->elts; +} + +/* Implements svn_ra__vtable_t.stat(). */ +svn_error_t * +svn_ra_serf__stat(svn_ra_session_t *ra_session, + const char *relpath, + svn_revnum_t revision, + svn_dirent_t **dirent, + apr_pool_t *pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + svn_error_t *err; + struct fill_dirent_baton_t fdb; + svn_tristate_t deadprop_count = svn_tristate_unknown; + svn_ra_serf__handler_t *handler; + const char *url; + + url = session->session_url.path; + + /* If we have a relative path, append it. */ + if (relpath) + url = svn_path_url_add_component2(url, relpath, pool); + + /* If we were given a specific revision, get a URL that refers to that + specific revision (rather than floating with HEAD). */ + if (SVN_IS_VALID_REVNUM(revision)) + { + SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */, + session, + url, revision, + pool, pool)); + } + + fdb.entry = svn_dirent_create(pool); + fdb.supports_deadprop_count = &deadprop_count; + fdb.result_pool = pool; + + SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, url, + SVN_INVALID_REVNUM, "0", + get_dirent_props(SVN_DIRENT_ALL, + session, + pool), + fill_dirent_propfunc, &fdb, pool)); + + err = svn_ra_serf__context_run_one(handler, pool); + + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + *dirent = NULL; + return SVN_NO_ERROR; + } + else + return svn_error_trace(err); + } + + if (deadprop_count == svn_tristate_false + && session->supports_deadprop_count == svn_tristate_unknown + && !fdb.entry->has_props) + { + /* We have to requery as the server didn't give us the right + information */ + session->supports_deadprop_count = svn_tristate_false; + + /* Run the same handler again */ + SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); + } + + if (deadprop_count != svn_tristate_unknown) + session->supports_deadprop_count = deadprop_count; + + *dirent = fdb.entry; + + return SVN_NO_ERROR; +} + +/* Baton for get_dir_dirents_cb and get_dir_props_cb */ +struct get_dir_baton_t +{ + apr_pool_t *result_pool; + apr_hash_t *dirents; + apr_hash_t *ret_props; + svn_boolean_t is_directory; + svn_tristate_t supports_deadprop_count; + const char *path; +}; + +/* Implements svn_ra_serf__prop_func_t */ +static svn_error_t * +get_dir_dirents_cb(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct get_dir_baton_t *db = baton; + const char *relpath; + + relpath = svn_fspath__skip_ancestor(db->path, path); + + if (relpath && relpath[0] != '\0') + { + struct fill_dirent_baton_t fdb; + + relpath = svn_path_uri_decode(relpath, scratch_pool); + fdb.entry = svn_hash_gets(db->dirents, relpath); + + if (!fdb.entry) + { + fdb.entry = svn_dirent_create(db->result_pool); + svn_hash_sets(db->dirents, + apr_pstrdup(db->result_pool, relpath), + fdb.entry); + } + + fdb.result_pool = db->result_pool; + fdb.supports_deadprop_count = &db->supports_deadprop_count; + SVN_ERR(fill_dirent_propfunc(&fdb, path, ns, name, value, scratch_pool)); + } + else if (relpath && !db->is_directory) + { + if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) + { + if (strcmp(value->data, "collection") != 0) + { + /* Tell a lie to exit early */ + return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Can't get properties of non-directory")); + } + else + db->is_directory = TRUE; + } + } + + return SVN_NO_ERROR; +} + +/* Implements svn_ra_serf__prop_func */ +static svn_error_t * +get_dir_props_cb(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct get_dir_baton_t *db = baton; + const char *propname; + + propname = svn_ra_serf__svnname_from_wirename(ns, name, db->result_pool); + if (propname) + { + svn_hash_sets(db->ret_props, propname, + svn_string_dup(value, db->result_pool)); + return SVN_NO_ERROR; + } + + if (!db->is_directory) + { + if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) + { + if (strcmp(value->data, "collection") != 0) + { + /* Tell a lie to exit early */ + return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Can't get properties of non-directory")); + } + else + db->is_directory = TRUE; + } + } + + return SVN_NO_ERROR; +} + +/* Implements svn_ra__vtable_t.get_dir(). */ +svn_error_t * +svn_ra_serf__get_dir(svn_ra_session_t *ra_session, + apr_hash_t **dirents, + svn_revnum_t *fetched_rev, + apr_hash_t **ret_props, + const char *rel_path, + svn_revnum_t revision, + apr_uint32_t dirent_fields, + apr_pool_t *result_pool) +{ + svn_ra_serf__session_t *session = ra_session->priv; + apr_pool_t *scratch_pool = svn_pool_create(result_pool); + svn_ra_serf__handler_t *dirent_handler = NULL; + svn_ra_serf__handler_t *props_handler = NULL; + const char *path; + struct get_dir_baton_t gdb; + svn_error_t *err = SVN_NO_ERROR; + + gdb.result_pool = result_pool; + gdb.is_directory = FALSE; + gdb.supports_deadprop_count = svn_tristate_unknown; + + path = session->session_url.path; + + /* If we have a relative path, URI encode and append it. */ + if (rel_path) + { + path = svn_path_url_add_component2(path, rel_path, scratch_pool); + } + + /* If the user specified a peg revision other than HEAD, we have to fetch + the baseline collection url for that revision. If not, we can use the + public url. */ + if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) + { + SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev, + session, + path, revision, + scratch_pool, scratch_pool)); + revision = SVN_INVALID_REVNUM; + } + /* REVISION is always SVN_INVALID_REVNUM */ + SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); + + gdb.path = path; + + /* If we're asked for children, fetch them now. */ + if (dirents) + { + /* Always request node kind to check that path is really a + * directory. */ + if (!ret_props) + dirent_fields |= SVN_DIRENT_KIND; + + gdb.dirents = apr_hash_make(result_pool); + + SVN_ERR(svn_ra_serf__create_propfind_handler( + &dirent_handler, session, + path, SVN_INVALID_REVNUM, "1", + get_dirent_props(dirent_fields, + session, + scratch_pool), + get_dir_dirents_cb, &gdb, + scratch_pool)); + + svn_ra_serf__request_create(dirent_handler); + } + else + gdb.dirents = NULL; + + if (ret_props) + { + gdb.ret_props = apr_hash_make(result_pool); + SVN_ERR(svn_ra_serf__create_propfind_handler( + &props_handler, session, + path, SVN_INVALID_REVNUM, "0", + all_props, + get_dir_props_cb, &gdb, + scratch_pool)); + + svn_ra_serf__request_create(props_handler); + } + else + gdb.ret_props = NULL; + + if (dirent_handler) + { + err = svn_error_trace( + svn_ra_serf__context_run_wait(&dirent_handler->done, + session, + scratch_pool)); + + if (err) + { + svn_pool_clear(scratch_pool); /* Unregisters outstanding requests */ + return err; + } + + if (gdb.supports_deadprop_count == svn_tristate_false + && session->supports_deadprop_count == svn_tristate_unknown + && dirent_fields & SVN_DIRENT_HAS_PROPS) + { + /* We have to requery as the server didn't give us the right + information */ + session->supports_deadprop_count = svn_tristate_false; + + apr_hash_clear(gdb.dirents); + + SVN_ERR(svn_ra_serf__create_propfind_handler( + &dirent_handler, session, + path, SVN_INVALID_REVNUM, "1", + get_dirent_props(dirent_fields, + session, + scratch_pool), + get_dir_dirents_cb, &gdb, + scratch_pool)); + + svn_ra_serf__request_create(dirent_handler); + } + } + + if (props_handler) + { + err = svn_error_trace( + svn_ra_serf__context_run_wait(&props_handler->done, + session, + scratch_pool)); + } + + /* And dirent again for the case when we had to send the request again */ + if (! err && dirent_handler) + { + err = svn_error_trace( + svn_ra_serf__context_run_wait(&dirent_handler->done, + session, + scratch_pool)); + } + + if (!err && gdb.supports_deadprop_count != svn_tristate_unknown) + session->supports_deadprop_count = gdb.supports_deadprop_count; + + svn_pool_destroy(scratch_pool); /* Unregisters outstanding requests */ + + SVN_ERR(err); + + if (!gdb.is_directory) + return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL, + _("Can't get entries of non-directory")); + + if (ret_props) + *ret_props = gdb.ret_props; + + if (dirents) + *dirents = gdb.dirents; + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_ra_serf/update.c b/subversion/libsvn_ra_serf/update.c index 88488ff..8313af0 100644 --- a/subversion/libsvn_ra_serf/update.c +++ b/subversion/libsvn_ra_serf/update.c @@ -49,6 +49,7 @@ #include "ra_serf.h" #include "../libsvn_ra/ra_loader.h" + /* * This enum represents the current state of our XML parsing for a REPORT. @@ -63,23 +64,180 @@ * the tag is 'closed', the pool will be reused. */ typedef enum report_state_e { - NONE = 0, - INITIAL = 0, - UPDATE_REPORT, - TARGET_REVISION, - OPEN_DIR, - ADD_DIR, - ABSENT_DIR, - OPEN_FILE, - ADD_FILE, - ABSENT_FILE, - PROP, - IGNORE_PROP_NAME, - NEED_PROP_NAME, - TXDELTA + INITIAL = XML_STATE_INITIAL /* = 0 */, + UPDATE_REPORT, + TARGET_REVISION, + + OPEN_DIR, + ADD_DIR, + + OPEN_FILE, + ADD_FILE, + + DELETE_ENTRY, + ABSENT_DIR, + ABSENT_FILE, + + SET_PROP, + REMOVE_PROP, + + PROP, + + FETCH_FILE, + FETCH_PROPS, + TXDELTA, + + CHECKED_IN, + CHECKED_IN_HREF, + + MD5_CHECKSUM, + + VERSION_NAME, + CREATIONDATE, + CREATOR_DISPLAYNAME } report_state_e; +#define D_ "DAV:" +#define S_ SVN_XML_NAMESPACE +#define V_ SVN_DAV_PROP_NS_DAV +static const svn_ra_serf__xml_transition_t update_ttable[] = { + { INITIAL, S_, "update-report", UPDATE_REPORT, + FALSE, { "?inline-props", "?send-all", NULL }, TRUE }, + + { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION, + FALSE, { "rev", NULL }, TRUE }, + + { UPDATE_REPORT, S_, "open-directory", OPEN_DIR, + FALSE, { "rev", NULL }, TRUE }, + + { OPEN_DIR, S_, "open-directory", OPEN_DIR, + FALSE, { "rev", "name", NULL }, TRUE }, + + { ADD_DIR, S_, "open-directory", OPEN_DIR, + FALSE, { "rev", "name", NULL }, TRUE }, + + { OPEN_DIR, S_, "add-directory", ADD_DIR, + FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/ + NULL }, TRUE }, + + { ADD_DIR, S_, "add-directory", ADD_DIR, + FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", /*"?bc-url",*/ + NULL }, TRUE }, + + { OPEN_DIR, S_, "open-file", OPEN_FILE, + FALSE, { "rev", "name", NULL }, TRUE }, + + { ADD_DIR, S_, "open-file", OPEN_FILE, + FALSE, { "rev", "name", NULL }, TRUE }, + + { OPEN_DIR, S_, "add-file", ADD_FILE, + FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", + "?sha1-checksum", NULL }, TRUE }, + + { ADD_DIR, S_, "add-file", ADD_FILE, + FALSE, { "name", "?copyfrom-path", "?copyfrom-rev", + "?sha1-checksum", NULL }, TRUE }, + + { OPEN_DIR, S_, "delete-entry", DELETE_ENTRY, + FALSE, { "?rev", "name", NULL }, TRUE }, + + { ADD_DIR, S_, "delete-entry", DELETE_ENTRY, + FALSE, { "?rev", "name", NULL }, TRUE }, + + { OPEN_DIR, S_, "absent-directory", ABSENT_DIR, + FALSE, { "name", NULL }, TRUE }, + + { ADD_DIR, S_, "absent-directory", ABSENT_DIR, + FALSE, { "name", NULL }, TRUE }, + + { OPEN_DIR, S_, "absent-file", ABSENT_FILE, + FALSE, { "name", NULL }, TRUE }, + + { ADD_DIR, S_, "absent-file", ABSENT_FILE, + FALSE, { "name", NULL }, TRUE }, + + + { OPEN_DIR, D_, "checked-in", CHECKED_IN, + FALSE, { NULL }, FALSE }, + + { ADD_DIR, D_, "checked-in", CHECKED_IN, + FALSE, { NULL }, FALSE }, + + { OPEN_FILE, D_, "checked-in", CHECKED_IN, + FALSE, { NULL }, FALSE }, + + { ADD_FILE, D_, "checked-in", CHECKED_IN, + FALSE, { NULL }, FALSE }, + + + { OPEN_DIR, S_, "set-prop", SET_PROP, + TRUE, { "name", "?encoding", NULL }, TRUE }, + + { ADD_DIR, S_, "set-prop", SET_PROP, + TRUE, { "name", "?encoding", NULL }, TRUE }, + + { OPEN_FILE, S_, "set-prop", SET_PROP, + TRUE, { "name", "?encoding", NULL }, TRUE }, + + { ADD_FILE, S_, "set-prop", SET_PROP, + TRUE, { "name", "?encoding", NULL }, TRUE }, + + + { OPEN_DIR, S_, "remove-prop", REMOVE_PROP, + TRUE, { "name", NULL }, TRUE }, + + { ADD_DIR, S_, "remove-prop", REMOVE_PROP, + TRUE, { "name", NULL }, TRUE }, + + { OPEN_FILE, S_, "remove-prop", REMOVE_PROP, + TRUE, { "name", NULL }, TRUE }, + + { ADD_FILE, S_, "remove-prop", REMOVE_PROP, + TRUE, { "name", NULL }, TRUE }, + + { OPEN_FILE, S_, "prop", PROP, + FALSE, { NULL }, FALSE }, + { OPEN_DIR, S_, "prop", PROP, + FALSE, { NULL }, FALSE }, + { ADD_FILE, S_, "prop", PROP, + FALSE, { NULL }, FALSE }, + { ADD_DIR, S_, "prop", PROP, + FALSE, { NULL }, FALSE }, + + { OPEN_FILE, S_, "txdelta", TXDELTA, + FALSE, { "?base-checksum" }, TRUE }, + + { ADD_FILE, S_, "txdelta", TXDELTA, + FALSE, { "?base-checksum" }, TRUE }, + + { OPEN_FILE, S_, "fetch-file", FETCH_FILE, + FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE}, + + { ADD_FILE, S_, "fetch-file", FETCH_FILE, + FALSE, { "?base-checksum", "?sha1-checksum", NULL }, TRUE }, + + { CHECKED_IN, D_, "href", CHECKED_IN_HREF, + TRUE, { NULL }, TRUE }, + + { PROP, V_, "md5-checksum", MD5_CHECKSUM, + TRUE, { NULL }, TRUE }, + + /* These are only reported for <= 1.6.x mod_dav_svn */ + { OPEN_DIR, S_, "fetch-props", FETCH_PROPS, + FALSE, { NULL }, FALSE }, + { OPEN_FILE, S_, "fetch-props", FETCH_PROPS, + FALSE, { NULL }, FALSE }, + + { PROP, D_, "version-name", VERSION_NAME, + TRUE, { NULL }, TRUE }, + { PROP, D_, "creationdate", CREATIONDATE, + TRUE, { NULL }, TRUE }, + { PROP, D_, "creator-displayname", CREATOR_DISPLAYNAME, + TRUE, { NULL }, TRUE }, + { 0 } +}; + /* While we process the REPORT response, we will queue up GET and PROPFIND requests. For a very large checkout, it is very easy to queue requests faster than they are resolved. Thus, we need to pause the XML processing @@ -97,175 +255,120 @@ typedef enum report_state_e { #define REQUEST_COUNT_TO_PAUSE 50 #define REQUEST_COUNT_TO_RESUME 40 +#define SPILLBUF_BLOCKSIZE 4096 +#define SPILLBUF_MAXBUFFSIZE 131072 + +#define PARSE_CHUNK_SIZE 8000 /* Copied from xml.c ### Needs tuning */ /* Forward-declare our report context. */ typedef struct report_context_t report_context_t; - +typedef struct body_create_baton_t body_create_baton_t; /* * This structure represents the information for a directory. */ -typedef struct report_dir_t +typedef struct dir_baton_t { - /* Our parent directory. - * - * This value is NULL when we are the root. - */ - struct report_dir_t *parent_dir; + struct dir_baton_t *parent_dir; /* NULL when root */ - apr_pool_t *pool; + apr_pool_t *pool; /* Subpool for this directory */ /* Pointer back to our original report context. */ - report_context_t *report_context; - - /* Our name sans any parents. */ - const char *base_name; + report_context_t *ctx; - /* the expanded directory name (including all parent names) */ - const char *name; + const char *relpath; /* session relative path */ + const char *base_name; /* Name of item "" for root */ /* the canonical url for this directory after updating. (received) */ const char *url; - /* The original repos_relpath of this url (from the working copy) - or NULL if the repos_relpath can be calculated from the edit root. */ + /* The original repos_relpath of this url (via the reporter) + directly, or via an ancestor. */ const char *repos_relpath; - /* Our base revision - SVN_INVALID_REVNUM if we're adding this dir. */ - svn_revnum_t base_rev; + svn_revnum_t base_rev; /* base revision or NULL for Add */ + + const char *copyfrom_path; /* NULL for open */ + svn_revnum_t copyfrom_rev; /* SVN_INVALID_REVNUM for open */ /* controlling dir baton - this is only created in ensure_dir_opened() */ + svn_boolean_t dir_opened; void *dir_baton; - apr_pool_t *dir_baton_pool; /* How many references to this directory do we still have open? */ apr_size_t ref_count; - /* Namespace list allocated out of this ->pool. */ - svn_ra_serf__ns_t *ns_list; - - /* hashtable for all of the properties (shared within a dir) */ - apr_hash_t *props; - - /* hashtable for all to-be-removed properties (shared within a dir) */ - apr_hash_t *removed_props; - - /* The propfind request for our current directory */ + svn_boolean_t fetch_props; /* Use PROPFIND request? */ svn_ra_serf__handler_t *propfind_handler; + apr_hash_t *remove_props; - /* Has the server told us to fetch the dir props? */ - svn_boolean_t fetch_props; - - /* Have we closed the directory tag (meaning no more additions)? */ - svn_boolean_t tag_closed; - - /* The children of this directory */ - struct report_dir_t *children; - - /* The next sibling of this directory */ - struct report_dir_t *sibling; -} report_dir_t; +} dir_baton_t; /* - * This structure represents the information for a file. - * - * A directory may have a report_info_t associated with it as well. - * - * This structure is created as we parse the REPORT response and - * once the element is completed, we create a report_fetch_t structure - * to give to serf to retrieve this file. - */ -typedef struct report_info_t +* This structure represents the information for a file. +* +* This structure is created as we parse the REPORT response and +* once the element is completed, we may create a fetch_ctx_t structure +* to give to serf to retrieve this file. +*/ +typedef struct file_baton_t { - apr_pool_t *pool; - - /* The enclosing directory. - * - * If this structure refers to a directory, the dir it points to will be - * itself. - */ - report_dir_t *dir; + dir_baton_t *parent_dir; /* The parent */ + apr_pool_t *pool; /* Subpool for this file*/ - /* Our name sans any directory info. */ + const char *relpath; /* session relative path */ const char *base_name; - /* the expanded file name (including all parent directory names) */ - const char *name; - - /* the canonical url for this file. */ + /* the canonical url for this directory after updating. (received) */ const char *url; + /* The original repos_relpath of this url as reported. */ + const char *repos_relpath; + /* lock token, if we had one to start off with. */ const char *lock_token; - /* Our base revision - SVN_INVALID_REVNUM if we're adding this file. */ - svn_revnum_t base_rev; - - /* our delta base, if present (NULL if we're adding the file) */ - const char *delta_base; + svn_revnum_t base_rev; /* SVN_INVALID_REVNUM for Add */ - /* Path of original item if add with history */ - const char *copyfrom_path; + const char *copyfrom_path; /* NULL for open */ + svn_revnum_t copyfrom_rev; /* SVN_INVALID_REVNUM for open */ - /* Revision of original item if add with history */ - svn_revnum_t copyfrom_rev; + /* controlling dir baton - this is only created in ensure_file_opened() */ + svn_boolean_t file_opened; + void *file_baton; - /* The propfind request for our current file (if present) */ + svn_boolean_t fetch_props; /* Use PROPFIND request? */ svn_ra_serf__handler_t *propfind_handler; - - /* Has the server told us to fetch the file props? */ - svn_boolean_t fetch_props; + svn_boolean_t found_lock_prop; + apr_hash_t *remove_props; /* Has the server told us to go fetch - only valid if we had it already */ svn_boolean_t fetch_file; - /* The properties for this file */ - apr_hash_t *props; + /* controlling file_baton and textdelta handler */ + svn_txdelta_window_handler_t txdelta; + void *txdelta_baton; - /* pool passed to update->add_file, etc. */ - apr_pool_t *editor_pool; + svn_checksum_t *base_md5_checksum; + svn_checksum_t *final_md5_checksum; + svn_checksum_t *final_sha1_checksum; - /* controlling file_baton and textdelta handler */ - void *file_baton; - const char *base_checksum; - const char *final_sha1_checksum; - svn_txdelta_window_handler_t textdelta; - void *textdelta_baton; - svn_stream_t *svndiff_decoder; - svn_stream_t *base64_decoder; - - /* Checksum for close_file */ - const char *final_checksum; - - /* Stream containing file contents already cached in the working - copy (which may be used to avoid a GET request for the same). */ - svn_stream_t *cached_contents; - - /* temporary property for this file which is currently being parsed - * It will eventually be stored in our parent directory's property hash. - */ - const char *prop_ns; - const char *prop_name; - svn_stringbuf_t *prop_value; - const char *prop_encoding; -} report_info_t; + svn_stream_t *txdelta_stream; /* Stream that feeds windows when + written to within txdelta*/ +} file_baton_t; /* * This structure represents a single request to GET (fetch) a file with * its associated Serf session/connection. */ -typedef struct report_fetch_t { +typedef struct fetch_ctx_t { /* The handler representing this particular fetch. */ svn_ra_serf__handler_t *handler; - /* The session we should use to fetch the file. */ - svn_ra_serf__session_t *sess; - - /* The connection we should use to fetch file. */ - svn_ra_serf__connection_t *conn; + svn_boolean_t using_compression; /* Stores the information for the file we want to fetch. */ - report_info_t *info; + file_baton_t *file; /* Have we read our response headers yet? */ svn_boolean_t read_headers; @@ -279,22 +382,13 @@ typedef struct report_fetch_t { /* This is the amount of data that we have read so far. */ apr_off_t read_size; - /* If we're receiving an svndiff, this will be non-NULL. */ - svn_stream_t *delta_stream; - /* If we're writing this file to a stream, this will be non-NULL. */ - svn_stream_t *target_stream; - - /* Are we done fetching this file? */ - svn_boolean_t done; - - /* Discard the rest of the content? */ - svn_boolean_t discard; + svn_stream_t *result_stream; - svn_ra_serf__list_t **done_list; - svn_ra_serf__list_t done_item; + /* The base-rev header */ + const char *delta_base; -} report_fetch_t; +} fetch_ctx_t; /* * The master structure for a REPORT request and response. @@ -303,7 +397,6 @@ struct report_context_t { apr_pool_t *pool; svn_ra_serf__session_t *sess; - svn_ra_serf__connection_t *conn; /* Source path and destination path */ const char *source; @@ -315,6 +408,10 @@ struct report_context_t { /* What is the target revision that we want for this REPORT? */ svn_revnum_t target_rev; + /* Where are we (used while parsing) */ + dir_baton_t *cur_dir; + file_baton_t *cur_file; + /* Have we been asked to ignore ancestry or textdeltas? */ svn_boolean_t ignore_ancestry; svn_boolean_t text_deltas; @@ -332,170 +429,305 @@ struct report_context_t { /* Path -> const char *repos_relpath mapping */ apr_hash_t *switched_paths; - /* Boolean indicating whether "" is switched. - (This indicates that the we are updating a single file) */ - svn_boolean_t root_is_switched; - /* Our master update editor and baton. */ - const svn_delta_editor_t *update_editor; - void *update_baton; + 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. */ - apr_file_t *body_file; - - /* root directory object */ - report_dir_t *root_dir; + svn_stream_t *body_template; + body_create_baton_t *body; /* number of pending GET requests */ unsigned int num_active_fetches; - /* completed fetches (contains report_fetch_t) */ - svn_ra_serf__list_t *done_fetches; - /* number of pending PROPFIND requests */ unsigned int num_active_propfinds; - /* completed PROPFIND requests (contains svn_ra_serf__handler_t) */ - svn_ra_serf__list_t *done_propfinds; - svn_ra_serf__list_t *done_dir_propfinds; - - /* list of outstanding prop changes (contains report_dir_t) */ - svn_ra_serf__list_t *active_dir_propfinds; - - /* list of files that only have prop changes (contains report_info_t) */ - svn_ra_serf__list_t *file_propchanges_only; - - /* The path to the REPORT request */ - const char *path; - /* Are we done parsing the REPORT response? */ svn_boolean_t done; /* Did we receive all data from the network? */ svn_boolean_t report_received; - /* Did we get a complete (non-truncated) report? */ - svn_boolean_t report_completed; - - /* The XML parser context for the REPORT response. */ - svn_ra_serf__xml_parser_t *parser_ctx; - /* Did we close the root directory? */ svn_boolean_t closed_root; }; +/* 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; -#ifdef NOT_USED_YET + apr_pool_t *scratch_pool; -#define D_ "DAV:" -#define S_ SVN_XML_NAMESPACE -static const svn_ra_serf__xml_transition_t update_ttable[] = { - { INITIAL, S_, "update-report", UPDATE_REPORT, - FALSE, { NULL }, FALSE }, + serf_bucket_alloc_t *alloc; + serf_bucket_t *collect_bucket; - { UPDATE_REPORT, S_, "target-revision", TARGET_REVISION, - FALSE, { "rev", NULL }, TRUE }, + const void *all_data; + apr_file_t *file; +}; - { UPDATE_REPORT, S_, "open-directory", OPEN_DIR, - FALSE, { "rev", NULL }, TRUE }, - { OPEN_DIR, S_, "open-directory", OPEN_DIR, - FALSE, { "rev", "name", NULL }, TRUE }, +#define MAX_BODY_IN_RAM (256*1024) - { OPEN_DIR, S_, "add-directory", ADD_DIR, - FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, +/* 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; - { ADD_DIR, S_, "add-directory", ADD_DIR, - FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, + while (!(s = serf_bucket_read(body->collect_bucket, remaining, &data, &sz))) + { + memcpy(next, data, sz); + remaining -= sz; + next += sz; - { OPEN_DIR, S_, "open-file", OPEN_FILE, - FALSE, { "rev", "name", NULL }, TRUE }, + if (! remaining) + break; + } - { OPEN_DIR, S_, "add-file", ADD_FILE, - FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, + if (!SERF_BUCKET_READ_ERROR(s)) + { + memcpy(next, data, sz); + } - { ADD_DIR, S_, "add-file", ADD_FILE, - FALSE, { "rev", "name", "?copyfrom-path", "?copyfrom-rev", NULL }, TRUE }, + serf_bucket_destroy(body->collect_bucket); + body->collect_bucket = NULL; - { OPEN_DIR, S_, "delete-entry", OPEN_FILE, - FALSE, { "?rev", "name", NULL }, TRUE }, + return (s != APR_EOF) ? NULL : buffer; +} - { OPEN_DIR, S_, "absent-directory", ABSENT_DIR, - FALSE, { "name", NULL }, TRUE }, +/* Noop function. Make serf take care of freeing in error situations */ +static void serf_free_no_error(void *unfreed_baton, void *block) {} - { ADD_DIR, S_, "absent-directory", ABSENT_DIR, - FALSE, { "name", NULL }, TRUE }, +/* 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; - { OPEN_DIR, S_, "absent-file", ABSENT_FILE, - FALSE, { "name", NULL }, TRUE }, + if (!bcb->scratch_pool) + bcb->scratch_pool = svn_pool_create(bcb->result_pool); - { ADD_DIR, S_, "absent-file", ABSENT_FILE, - FALSE, { "name", NULL }, TRUE }, + if (bcb->file) + { + SVN_ERR(svn_io_file_write_full(bcb->file, data, *len, NULL, + bcb->scratch_pool)); + svn_pool_clear(bcb->scratch_pool); - { 0 } -}; + 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)); + } -/* Conforms to svn_ra_serf__xml_opened_t */ -static svn_error_t * -update_opened(svn_ra_serf__xml_estate_t *xes, - void *baton, - int entered_state, - const svn_ra_serf__dav_props_t *tag, - apr_pool_t *scratch_pool) -{ - report_context_t *ctx = baton; + 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; +} -/* Conforms to svn_ra_serf__xml_closed_t */ static svn_error_t * -update_closed(svn_ra_serf__xml_estate_t *xes, - void *baton, - int leaving_state, - const svn_string_t *cdata, - apr_hash_t *attrs, - apr_pool_t *scratch_pool) +create_dir_baton(dir_baton_t **new_dir, + report_context_t *ctx, + const char *name, + apr_pool_t *scratch_pool) { - report_context_t *ctx = baton; + dir_baton_t *parent = ctx->cur_dir; + apr_pool_t *dir_pool; + dir_baton_t *dir; - if (leaving_state == TARGET_REVISION) + if (parent) + dir_pool = svn_pool_create(parent->pool); + else + dir_pool = svn_pool_create(ctx->pool); + + dir = apr_pcalloc(dir_pool, sizeof(*dir)); + dir->pool = dir_pool; + dir->ctx = ctx; + + if (parent) { - const char *rev = svn_hash_gets(attrs, "rev"); + dir->parent_dir = parent; + parent->ref_count++; + } - SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton, - SVN_STR_TO_REV(rev), - ctx->sess->pool)); + dir->relpath = parent ? svn_relpath_join(parent->relpath, name, dir_pool) + : apr_pstrdup(dir_pool, name); + dir->base_name = svn_relpath_basename(dir->relpath, NULL); + + dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->relpath); + if (!dir->repos_relpath) + { + if (parent) + dir->repos_relpath = svn_relpath_join(parent->repos_relpath, name, + dir_pool); + else + dir->repos_relpath = svn_uri_skip_ancestor(ctx->sess->repos_root_str, + ctx->sess->session_url_str, + dir_pool); } + dir->base_rev = SVN_INVALID_REVNUM; + dir->copyfrom_rev = SVN_INVALID_REVNUM; + + dir->ref_count = 1; + + ctx->cur_dir = dir; + + *new_dir = dir; return SVN_NO_ERROR; } - -/* Conforms to svn_ra_serf__xml_cdata_t */ static svn_error_t * -update_cdata(svn_ra_serf__xml_estate_t *xes, - void *baton, - int current_state, - const char *data, - apr_size_t len, - apr_pool_t *scratch_pool) +create_file_baton(file_baton_t **new_file, + report_context_t *ctx, + const char *name, + apr_pool_t *scratch_pool) { - report_context_t *ctx = baton; + dir_baton_t *parent = ctx->cur_dir; + apr_pool_t *file_pool; + file_baton_t *file; + + file_pool = svn_pool_create(parent->pool); + + file = apr_pcalloc(file_pool, sizeof(*file)); + file->pool = file_pool; + + file->parent_dir = parent; + parent->ref_count++; + + file->relpath = svn_relpath_join(parent->relpath, name, file_pool); + file->base_name = svn_relpath_basename(file->relpath, NULL); + + file->repos_relpath = svn_hash_gets(ctx->switched_paths, file->relpath); + if (!file->repos_relpath) + file->repos_relpath = svn_relpath_join(parent->repos_relpath, name, + file_pool); + + /* Sane defaults */ + file->base_rev = SVN_INVALID_REVNUM; + file->copyfrom_rev = SVN_INVALID_REVNUM; + + *new_file = file; + + ctx->cur_file = file; return SVN_NO_ERROR; } -#endif /* NOT_USED_YET */ +/** Minimum nr. of outstanding requests needed before a new connection is + * opened. */ +#define REQS_PER_CONN 8 +/** This function creates a new connection for this serf session, but only + * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is + * only one main connection open. + */ +static svn_error_t * +open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs) +{ + /* For each REQS_PER_CONN outstanding requests open a new connection, with + * a minimum of 1 extra connection. */ + if (sess->num_conns == 1 || + ((num_active_reqs / REQS_PER_CONN) > sess->num_conns)) + { + int cur = sess->num_conns; + apr_status_t status; + + sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur])); + sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool, + NULL, NULL); + sess->conns[cur]->last_status_code = -1; + sess->conns[cur]->session = sess; + status = serf_connection_create2(&sess->conns[cur]->conn, + sess->context, + sess->session_url, + svn_ra_serf__conn_setup, + sess->conns[cur], + svn_ra_serf__conn_closed, + sess->conns[cur], + sess->pool); + if (status) + return svn_ra_serf__wrap_err(status, NULL); + + sess->num_conns++; + } + + return SVN_NO_ERROR; +} /* Returns best connection for fetching files/properties. */ static svn_ra_serf__connection_t * @@ -520,391 +752,199 @@ get_best_connection(report_context_t *ctx) if (ctx->report_received && (ctx->sess->max_connections > 2)) first_conn = 0; - /* Currently, we just cycle connections. In the future we could - store the number of pending requests on each connection, or - perform other heuristics, to achieve better connection usage. - (As an optimization, if there's only one available auxiliary - connection to use, don't bother doing all the cur_conn math -- - just return that one connection.) */ + /* If there's only one available auxiliary connection to use, don't bother + doing all the cur_conn math -- just return that one connection. */ if (ctx->sess->num_conns - first_conn == 1) { conn = ctx->sess->conns[first_conn]; } else { +#if SERF_VERSION_AT_LEAST(1, 4, 0) + /* Often one connection is slower than others, e.g. because the server + process/thread has to do more work for the particular set of requests. + In the worst case, when REQUEST_COUNT_TO_RESUME requests are queued + on such a slow connection, ra_serf will completely stop sending + requests. + + The method used here selects the connection with the least amount of + pending requests, thereby giving more work to lightly loaded server + processes. + */ + int i, best_conn = first_conn; + unsigned int min = INT_MAX; + for (i = first_conn; i < ctx->sess->num_conns; i++) + { + serf_connection_t *sc = ctx->sess->conns[i]->conn; + unsigned int pending = serf_connection_pending_requests(sc); + if (pending < min) + { + min = pending; + best_conn = i; + } + } + conn = ctx->sess->conns[best_conn]; +#else + /* We don't know how many requests are pending per connection, so just + cycle them. */ conn = ctx->sess->conns[ctx->sess->cur_conn]; ctx->sess->cur_conn++; if (ctx->sess->cur_conn >= ctx->sess->num_conns) ctx->sess->cur_conn = first_conn; +#endif } return conn; } - - -/** Report state management helper **/ - -static report_info_t * -push_state(svn_ra_serf__xml_parser_t *parser, - report_context_t *ctx, - report_state_e state) -{ - report_info_t *info; - apr_pool_t *info_parent_pool; - - svn_ra_serf__xml_push_state(parser, state); - - info = parser->state->private; - - /* Our private pool needs to be disjoint from the state pool. */ - if (!info) - { - info_parent_pool = ctx->pool; - } - else - { - info_parent_pool = info->pool; - } - - if (state == OPEN_DIR || state == ADD_DIR) - { - report_info_t *new_info; - - new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info)); - new_info->pool = svn_pool_create(info_parent_pool); - new_info->lock_token = NULL; - new_info->prop_value = svn_stringbuf_create_empty(new_info->pool); - - new_info->dir = apr_pcalloc(new_info->pool, sizeof(*new_info->dir)); - new_info->dir->pool = new_info->pool; - - /* Create the root property tree. */ - new_info->dir->props = apr_hash_make(new_info->pool); - new_info->props = new_info->dir->props; - new_info->dir->removed_props = apr_hash_make(new_info->pool); - - new_info->dir->report_context = ctx; - - if (info) - { - info->dir->ref_count++; - - new_info->dir->parent_dir = info->dir; - - /* Point our ns_list at our parents to try to reuse it. */ - new_info->dir->ns_list = info->dir->ns_list; - - /* Add ourselves to our parent's list */ - new_info->dir->sibling = info->dir->children; - info->dir->children = new_info->dir; - } - else - { - /* Allow us to be found later. */ - ctx->root_dir = new_info->dir; - } - - parser->state->private = new_info; - } - else if (state == OPEN_FILE || state == ADD_FILE) - { - report_info_t *new_info; - - new_info = apr_pcalloc(info_parent_pool, sizeof(*new_info)); - new_info->pool = svn_pool_create(info_parent_pool); - new_info->file_baton = NULL; - new_info->lock_token = NULL; - new_info->fetch_file = FALSE; - new_info->prop_value = svn_stringbuf_create_empty(new_info->pool); - - /* Point at our parent's directory state. */ - new_info->dir = info->dir; - info->dir->ref_count++; - - new_info->props = apr_hash_make(new_info->pool); - - parser->state->private = new_info; - } - - return parser->state->private; -} - - -/** Wrappers around our various property walkers **/ - -static svn_error_t * -set_file_props(void *baton, - const char *ns, - const char *name, - const svn_string_t *val, - apr_pool_t *scratch_pool) -{ - report_info_t *info = baton; - const svn_delta_editor_t *editor = info->dir->report_context->update_editor; - const char *prop_name; - - prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); - if (prop_name != NULL) - return svn_error_trace(editor->change_file_prop(info->file_baton, - prop_name, - val, - scratch_pool)); - return SVN_NO_ERROR; -} - - -static svn_error_t * -set_dir_props(void *baton, - const char *ns, - const char *name, - const svn_string_t *val, - apr_pool_t *scratch_pool) -{ - report_dir_t *dir = baton; - const svn_delta_editor_t *editor = dir->report_context->update_editor; - const char *prop_name; - - prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); - if (prop_name != NULL) - return svn_error_trace(editor->change_dir_prop(dir->dir_baton, - prop_name, - val, - scratch_pool)); - return SVN_NO_ERROR; -} - - -static svn_error_t * -remove_file_props(void *baton, - const char *ns, - const char *name, - const svn_string_t *val, - apr_pool_t *scratch_pool) -{ - report_info_t *info = baton; - const svn_delta_editor_t *editor = info->dir->report_context->update_editor; - const char *prop_name; - - prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); - if (prop_name != NULL) - return svn_error_trace(editor->change_file_prop(info->file_baton, - prop_name, - NULL, - scratch_pool)); - return SVN_NO_ERROR; -} - - -static svn_error_t * -remove_dir_props(void *baton, - const char *ns, - const char *name, - const svn_string_t *val, - apr_pool_t *scratch_pool) -{ - report_dir_t *dir = baton; - const svn_delta_editor_t *editor = dir->report_context->update_editor; - const char *prop_name; - - prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); - if (prop_name != NULL) - return svn_error_trace(editor->change_dir_prop(dir->dir_baton, - prop_name, - NULL, - scratch_pool)); - return SVN_NO_ERROR; -} - /** Helpers to open and close directories */ static svn_error_t* -ensure_dir_opened(report_dir_t *dir) +ensure_dir_opened(dir_baton_t *dir, + apr_pool_t *scratch_pool) { - report_context_t *ctx = dir->report_context; + report_context_t *ctx = dir->ctx; - /* if we're already open, return now */ - if (dir->dir_baton) - { - return SVN_NO_ERROR; - } + if (dir->dir_opened) + return SVN_NO_ERROR; if (dir->base_name[0] == '\0') { - dir->dir_baton_pool = svn_pool_create(dir->pool); - if (ctx->destination && ctx->sess->wc_callbacks->invalidate_wc_props) { SVN_ERR(ctx->sess->wc_callbacks->invalidate_wc_props( ctx->sess->wc_callback_baton, ctx->update_target, - SVN_RA_SERF__WC_CHECKED_IN_URL, dir->pool)); + SVN_RA_SERF__WC_CHECKED_IN_URL, scratch_pool)); } - SVN_ERR(ctx->update_editor->open_root(ctx->update_baton, dir->base_rev, - dir->dir_baton_pool, - &dir->dir_baton)); + SVN_ERR(ctx->editor->open_root(ctx->editor_baton, dir->base_rev, + dir->pool, + &dir->dir_baton)); } else { - SVN_ERR(ensure_dir_opened(dir->parent_dir)); - - dir->dir_baton_pool = svn_pool_create(dir->parent_dir->dir_baton_pool); + SVN_ERR(ensure_dir_opened(dir->parent_dir, scratch_pool)); if (SVN_IS_VALID_REVNUM(dir->base_rev)) { - SVN_ERR(ctx->update_editor->open_directory(dir->name, - dir->parent_dir->dir_baton, - dir->base_rev, - dir->dir_baton_pool, - &dir->dir_baton)); + SVN_ERR(ctx->editor->open_directory(dir->relpath, + dir->parent_dir->dir_baton, + dir->base_rev, + dir->pool, + &dir->dir_baton)); } else { - SVN_ERR(ctx->update_editor->add_directory(dir->name, - dir->parent_dir->dir_baton, - NULL, SVN_INVALID_REVNUM, - dir->dir_baton_pool, - &dir->dir_baton)); + SVN_ERR(ctx->editor->add_directory(dir->relpath, + dir->parent_dir->dir_baton, + dir->copyfrom_path, + dir->copyfrom_rev, + dir->pool, + &dir->dir_baton)); } } + dir->dir_opened = TRUE; + return SVN_NO_ERROR; } static svn_error_t * -close_dir(report_dir_t *dir) +maybe_close_dir(dir_baton_t *dir) { - report_dir_t *prev; - report_dir_t *sibling; - - /* ### is there a better pool... this is tossed at end-of-func */ - apr_pool_t *scratch_pool = dir->dir_baton_pool; + apr_pool_t *scratch_pool = dir->pool; + dir_baton_t *parent = dir->parent_dir; + report_context_t *ctx = dir->ctx; - SVN_ERR_ASSERT(! dir->ref_count); - - SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->base_name, - dir->base_rev, - set_dir_props, dir, - scratch_pool)); - - SVN_ERR(svn_ra_serf__walk_all_props(dir->removed_props, dir->base_name, - dir->base_rev, remove_dir_props, dir, - scratch_pool)); - - if (dir->fetch_props) + if (--dir->ref_count) { - SVN_ERR(svn_ra_serf__walk_all_props(dir->props, dir->url, - dir->report_context->target_rev, - set_dir_props, dir, - scratch_pool)); + return SVN_NO_ERROR; } - SVN_ERR(dir->report_context->update_editor->close_directory( - dir->dir_baton, scratch_pool)); + SVN_ERR(ensure_dir_opened(dir, dir->pool)); - /* remove us from our parent's children list */ - if (dir->parent_dir) + if (dir->remove_props) { - prev = NULL; - sibling = dir->parent_dir->children; + apr_hash_index_t *hi; - while (sibling != dir) + for (hi = apr_hash_first(scratch_pool, dir->remove_props); + hi; + hi = apr_hash_next(hi)) { - prev = sibling; - sibling = sibling->sibling; - if (!sibling) - SVN_ERR_MALFUNCTION(); - } - - if (!prev) - { - dir->parent_dir->children = dir->sibling; - } - else - { - prev->sibling = dir->sibling; + SVN_ERR(ctx->editor->change_file_prop(dir->dir_baton, + apr_hash_this_key(hi), + NULL /* value */, + scratch_pool)); } } - svn_pool_destroy(dir->dir_baton_pool); - svn_pool_destroy(dir->pool); + SVN_ERR(dir->ctx->editor->close_directory(dir->dir_baton, scratch_pool)); - return SVN_NO_ERROR; -} + svn_pool_destroy(dir->pool /* scratch_pool */); -static svn_error_t *close_all_dirs(report_dir_t *dir) -{ - while (dir->children) - { - SVN_ERR(close_all_dirs(dir->children)); - dir->ref_count--; - } - - SVN_ERR_ASSERT(! dir->ref_count); - - SVN_ERR(ensure_dir_opened(dir)); - - return close_dir(dir); + if (parent) + return svn_error_trace(maybe_close_dir(parent)); + else + return SVN_NO_ERROR; } - -/** Routines called when we are fetching a file */ - -/* This function works around a bug in some older versions of - * mod_dav_svn in that it will not send remove-prop in the update - * report when a lock property disappears when send-all is false. - * - * Therefore, we'll try to look at our properties and see if there's - * an active lock. If not, then we'll assume there isn't a lock - * anymore. - */ -static void -check_lock(report_info_t *info) +static svn_error_t * +ensure_file_opened(file_baton_t *file, + apr_pool_t *scratch_pool) { - const char *lock_val; + const svn_delta_editor_t *editor = file->parent_dir->ctx->editor; - lock_val = svn_ra_serf__get_ver_prop(info->props, info->url, - info->dir->report_context->target_rev, - "DAV:", "lockdiscovery"); + if (file->file_opened) + return SVN_NO_ERROR; - if (lock_val) + /* Ensure our parent is open. */ + SVN_ERR(ensure_dir_opened(file->parent_dir, scratch_pool)); + + /* Open (or add) the file. */ + if (SVN_IS_VALID_REVNUM(file->base_rev)) { - char *new_lock; - new_lock = apr_pstrdup(info->editor_pool, lock_val); - apr_collapse_spaces(new_lock, new_lock); - lock_val = new_lock; + SVN_ERR(editor->open_file(file->relpath, + file->parent_dir->dir_baton, + file->base_rev, + file->pool, + &file->file_baton)); } - - if (!lock_val || lock_val[0] == '\0') + else { - svn_string_t *str; + SVN_ERR(editor->add_file(file->relpath, + file->parent_dir->dir_baton, + file->copyfrom_path, + file->copyfrom_rev, + file->pool, + &file->file_baton)); + } - str = svn_string_ncreate("", 1, info->editor_pool); + file->file_opened = TRUE; - svn_ra_serf__set_ver_prop(info->dir->removed_props, info->base_name, - info->base_rev, "DAV:", "lock-token", - str, info->dir->pool); - } + return SVN_NO_ERROR; } + +/** Routines called when we are fetching a file */ + static svn_error_t * headers_fetch(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { - report_fetch_t *fetch_ctx = baton; + fetch_ctx_t *fetch_ctx = baton; /* note that we have old VC URL */ - if (SVN_IS_VALID_REVNUM(fetch_ctx->info->base_rev) && - fetch_ctx->info->delta_base) + if (fetch_ctx->delta_base) { serf_bucket_headers_setn(headers, SVN_DAV_DELTA_BASE_HEADER, - fetch_ctx->info->delta_base); + fetch_ctx->delta_base); serf_bucket_headers_setn(headers, "Accept-Encoding", "svndiff1;q=0.9,svndiff;q=0.8"); } - else if (fetch_ctx->sess->using_compression) + else if (fetch_ctx->using_compression) { serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip"); } @@ -918,7 +958,7 @@ cancel_fetch(serf_request_t *request, int status_code, void *baton) { - report_fetch_t *fetch_ctx = baton; + fetch_ctx_t *fetch_ctx = baton; /* Uh-oh. Our connection died on us. * @@ -948,30 +988,6 @@ cancel_fetch(serf_request_t *request, SVN_ERR_MALFUNCTION(); } -static svn_error_t * -error_fetch(serf_request_t *request, - report_fetch_t *fetch_ctx, - svn_error_t *err) -{ - fetch_ctx->done = TRUE; - - fetch_ctx->done_item.data = fetch_ctx; - fetch_ctx->done_item.next = *fetch_ctx->done_list; - *fetch_ctx->done_list = &fetch_ctx->done_item; - - /* Discard the rest of this request - (This makes sure it doesn't error when the request is aborted later) */ - serf_request_set_handler(request, - svn_ra_serf__response_discard_handler, NULL); - - /* Some errors would be handled by serf; make sure they really make - the update fail by wrapping it in a different error. */ - if (!SERF_BUCKET_READ_ERROR(err->apr_err)) - return svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - - return err; -} - /* Wield the editor referenced by INFO to open (or add) the file file also associated with INFO, setting properties on the file and calling the editor's apply_textdelta() function on it if necessary @@ -980,95 +996,92 @@ error_fetch(serf_request_t *request, Callers will probably want to also see the function that serves the opposite purpose of this one, close_updated_file(). */ static svn_error_t * -open_updated_file(report_info_t *info, - svn_boolean_t force_apply_textdelta, +open_file_txdelta(file_baton_t *file, apr_pool_t *scratch_pool) { - report_context_t *ctx = info->dir->report_context; - const svn_delta_editor_t *update_editor = ctx->update_editor; + const svn_delta_editor_t *editor = file->parent_dir->ctx->editor; - /* Ensure our parent is open. */ - SVN_ERR(ensure_dir_opened(info->dir)); - info->editor_pool = svn_pool_create(info->dir->dir_baton_pool); + SVN_ERR_ASSERT(file->txdelta == NULL); - /* Expand our full name now if we haven't done so yet. */ - if (!info->name) - { - info->name = svn_relpath_join(info->dir->name, info->base_name, - info->editor_pool); - } - - /* Open (or add) the file. */ - if (SVN_IS_VALID_REVNUM(info->base_rev)) - { - SVN_ERR(update_editor->open_file(info->name, - info->dir->dir_baton, - info->base_rev, - info->editor_pool, - &info->file_baton)); - } - else - { - SVN_ERR(update_editor->add_file(info->name, - info->dir->dir_baton, - info->copyfrom_path, - info->copyfrom_rev, - info->editor_pool, - &info->file_baton)); - } - - /* Check for lock information. */ - if (info->lock_token) - check_lock(info); + SVN_ERR(ensure_file_opened(file, scratch_pool)); /* Get (maybe) a textdelta window handler for transmitting file content changes. */ - if (info->fetch_file || force_apply_textdelta) - { - SVN_ERR(update_editor->apply_textdelta(info->file_baton, - info->base_checksum, - info->editor_pool, - &info->textdelta, - &info->textdelta_baton)); - } + SVN_ERR(editor->apply_textdelta(file->file_baton, + svn_checksum_to_cstring( + file->base_md5_checksum, + scratch_pool), + file->pool, + &file->txdelta, + &file->txdelta_baton)); return SVN_NO_ERROR; } -/* Close the file associated with INFO->file_baton, and cleanup other - bits of that structure managed by open_updated_file(). */ +/* Close the file, handling loose ends and cleanup */ static svn_error_t * -close_updated_file(report_info_t *info, - apr_pool_t *scratch_pool) +close_file(file_baton_t *file, + apr_pool_t *scratch_pool) { - report_context_t *ctx = info->dir->report_context; + dir_baton_t *parent_dir = file->parent_dir; + report_context_t *ctx = parent_dir->ctx; + + SVN_ERR(ensure_file_opened(file, scratch_pool)); /* Set all of the properties we received */ - SVN_ERR(svn_ra_serf__walk_all_props(info->props, - info->base_name, - info->base_rev, - set_file_props, info, - scratch_pool)); - SVN_ERR(svn_ra_serf__walk_all_props(info->dir->removed_props, - info->base_name, - info->base_rev, - remove_file_props, info, - scratch_pool)); - if (info->fetch_props) + if (file->remove_props) + { + apr_hash_index_t *hi; + + for (hi = apr_hash_first(scratch_pool, file->remove_props); + hi; + hi = apr_hash_next(hi)) + { + SVN_ERR(ctx->editor->change_file_prop(file->file_baton, + apr_hash_this_key(hi), + NULL /* value */, + scratch_pool)); + } + } + + /* Check for lock information. */ + + /* This works around a bug in some older versions of mod_dav_svn in that it + * will not send remove-prop in the update report when a lock property + * disappears when send-all is false. + + ### Given that we only fetch props on additions, is this really necessary? + Or is it covering up old local copy bugs where we copied locks to other + paths? */ + if (!ctx->add_props_included + && file->lock_token && !file->found_lock_prop + && SVN_IS_VALID_REVNUM(file->base_rev) /* file_is_added */) + { + SVN_ERR(ctx->editor->change_file_prop(file->file_baton, + SVN_PROP_ENTRY_LOCK_TOKEN, + NULL, + scratch_pool)); + } + + if (file->url) { - SVN_ERR(svn_ra_serf__walk_all_props(info->props, - info->url, - ctx->target_rev, - set_file_props, info, - scratch_pool)); + SVN_ERR(ctx->editor->change_file_prop(file->file_baton, + SVN_RA_SERF__WC_CHECKED_IN_URL, + svn_string_create(file->url, + scratch_pool), + scratch_pool)); } /* Close the file via the editor. */ - SVN_ERR(info->dir->report_context->update_editor->close_file( - info->file_baton, info->final_checksum, scratch_pool)); + SVN_ERR(ctx->editor->close_file(file->file_baton, + svn_checksum_to_cstring( + file->final_md5_checksum, + scratch_pool), + scratch_pool)); + + svn_pool_destroy(file->pool); - /* We're done with our editor pool. */ - svn_pool_destroy(info->editor_pool); + SVN_ERR(maybe_close_dir(parent_dir)); /* Remove reference */ return SVN_NO_ERROR; } @@ -1083,8 +1096,8 @@ handle_fetch(serf_request_t *request, const char *data; apr_size_t len; apr_status_t status; - report_fetch_t *fetch_ctx = handler_baton; - svn_error_t *err; + fetch_ctx_t *fetch_ctx = handler_baton; + file_baton_t *file = fetch_ctx->file; /* ### new field. make sure we didn't miss some initialization. */ SVN_ERR_ASSERT(fetch_ctx->handler != NULL); @@ -1093,50 +1106,53 @@ handle_fetch(serf_request_t *request, { serf_bucket_t *hdrs; const char *val; - report_info_t *info; + + /* If the error code wasn't 200, something went wrong. Don't use the + * returned data as its probably an error message. Just bail out instead. + */ + if (fetch_ctx->handler->sline.code != 200) + { + fetch_ctx->handler->discard_body = TRUE; + return SVN_NO_ERROR; /* Will return an error in the DONE handler */ + } hdrs = serf_bucket_response_get_headers(response); val = serf_bucket_headers_get(hdrs, "Content-Type"); - info = fetch_ctx->info; if (val && svn_cstring_casecmp(val, SVN_SVNDIFF_MIME_TYPE) == 0) { - fetch_ctx->delta_stream = - svn_txdelta_parse_svndiff(info->textdelta, - info->textdelta_baton, - TRUE, info->editor_pool); + fetch_ctx->result_stream = + svn_txdelta_parse_svndiff(file->txdelta, + file->txdelta_baton, + TRUE, file->pool); /* Validate the delta base claimed by the server matches what we asked for! */ val = serf_bucket_headers_get(hdrs, SVN_DAV_DELTA_BASE_HEADER); - if (val && (strcmp(val, info->delta_base) != 0)) + if (val && fetch_ctx->delta_base == NULL) + { + /* We recieved response with delta base header while we didn't + requested it -- report it as error. */ + return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("GET request returned unexpected " + "delta base: %s"), val); + } + else if (val && (strcmp(val, fetch_ctx->delta_base) != 0)) { - err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, - _("GET request returned unexpected " - "delta base: %s"), val); - return error_fetch(request, fetch_ctx, err); + return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("GET request returned unexpected " + "delta base: %s"), val); } } else { - fetch_ctx->delta_stream = NULL; + fetch_ctx->result_stream = NULL; } fetch_ctx->read_headers = TRUE; } - /* If the error code wasn't 200, something went wrong. Don't use the returned - data as its probably an error message. Just bail out instead. */ - if (fetch_ctx->handler->sline.code != 200) - { - err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, - _("GET request failed: %d %s"), - fetch_ctx->handler->sline.code, - fetch_ctx->handler->sline.reason); - return error_fetch(request, fetch_ctx, err); - } - - while (1) + while (TRUE) { svn_txdelta_window_t delta_window = { 0 }; svn_txdelta_op_t delta_op; @@ -1163,10 +1179,9 @@ handle_fetch(serf_request_t *request, } /* Skip on to the next iteration of this loop. */ - if (APR_STATUS_IS_EAGAIN(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } + if (status /* includes EAGAIN */) + return svn_ra_serf__wrap_err(status, NULL); + continue; } @@ -1176,17 +1191,12 @@ handle_fetch(serf_request_t *request, /* Update data and len to just provide the new data. */ skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size); data += skip; - len -= skip; + len -= (apr_size_t)skip; } - if (fetch_ctx->delta_stream) - { - err = svn_stream_write(fetch_ctx->delta_stream, data, &len); - if (err) - { - return error_fetch(request, fetch_ctx, err); - } - } + if (fetch_ctx->result_stream) + SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, &len)); + /* otherwise, manually construct the text delta window. */ else if (len) { @@ -1203,378 +1213,271 @@ handle_fetch(serf_request_t *request, delta_window.new_data = &window_data; /* write to the file located in the info. */ - err = fetch_ctx->info->textdelta(&delta_window, - fetch_ctx->info->textdelta_baton); - if (err) - { - return error_fetch(request, fetch_ctx, err); - } + SVN_ERR(file->txdelta(&delta_window, file->txdelta_baton)); } if (APR_STATUS_IS_EOF(status)) { - report_info_t *info = fetch_ctx->info; - - if (fetch_ctx->delta_stream) - err = svn_error_trace(svn_stream_close(fetch_ctx->delta_stream)); + if (fetch_ctx->result_stream) + SVN_ERR(svn_stream_close(fetch_ctx->result_stream)); else - err = svn_error_trace(info->textdelta(NULL, - info->textdelta_baton)); - if (err) - { - return error_fetch(request, fetch_ctx, err); - } - - err = close_updated_file(info, info->pool); - if (err) - { - return svn_error_trace(error_fetch(request, fetch_ctx, err)); - } - - fetch_ctx->done = TRUE; - - fetch_ctx->done_item.data = fetch_ctx; - fetch_ctx->done_item.next = *fetch_ctx->done_list; - *fetch_ctx->done_list = &fetch_ctx->done_item; - - /* We're done with our pool. */ - svn_pool_destroy(info->pool); - - if (status) - return svn_ra_serf__wrap_err(status, NULL); - } - if (APR_STATUS_IS_EAGAIN(status)) - { - return svn_ra_serf__wrap_err(status, NULL); + SVN_ERR(file->txdelta(NULL, file->txdelta_baton)); } + + /* Report EOF, EEAGAIN and other special errors to serf */ + if (status) + return svn_ra_serf__wrap_err(status, NULL); } - /* not reached */ } -/* Implements svn_ra_serf__response_handler_t */ -static svn_error_t * -handle_stream(serf_request_t *request, - serf_bucket_t *response, - void *handler_baton, - apr_pool_t *pool) -{ - report_fetch_t *fetch_ctx = handler_baton; - svn_error_t *err; - apr_status_t status; - - /* ### new field. make sure we didn't miss some initialization. */ - SVN_ERR_ASSERT(fetch_ctx->handler != NULL); +/* --------------------------------------------------------- */ - err = svn_ra_serf__error_on_status(fetch_ctx->handler->sline, - fetch_ctx->info->name, - fetch_ctx->handler->location); - if (err) - { - fetch_ctx->handler->done = TRUE; +/** Wrappers around our various property walkers **/ - err = svn_error_compose_create( - err, - svn_ra_serf__handle_discard_body(request, response, NULL, pool)); +/* Implements svn_ra_serf__prop_func */ +static svn_error_t * +set_file_props(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *val, + apr_pool_t *scratch_pool) +{ + file_baton_t *file = baton; + report_context_t *ctx = file->parent_dir->ctx; + const char *prop_name; - return svn_error_trace(err); - } + prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); - while (1) + if (!prop_name) { - const char *data; - apr_size_t len; - - status = serf_bucket_read(response, 8000, &data, &len); - if (SERF_BUCKET_READ_ERROR(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } - - fetch_ctx->read_size += len; - - if (fetch_ctx->aborted_read) + /* This works around a bug in some older versions of + * mod_dav_svn in that it will not send remove-prop in the update + * report when a lock property disappears when send-all is false. + * + * Therefore, we'll try to look at our properties and see if there's + * an active lock. If not, then we'll assume there isn't a lock + * anymore. + */ + /* assert(!ctx->add_props_included); // Or we wouldn't be here */ + if (file->lock_token + && !file->found_lock_prop + && val + && strcmp(ns, "DAV:") == 0 + && strcmp(name, "lockdiscovery") == 0) { - /* We haven't caught up to where we were before. */ - if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) - { - /* Eek. What did the file shrink or something? */ - if (APR_STATUS_IS_EOF(status)) - { - SVN_ERR_MALFUNCTION(); - } - - /* Skip on to the next iteration of this loop. */ - if (APR_STATUS_IS_EAGAIN(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } - continue; - } + char *new_lock; + new_lock = apr_pstrdup(scratch_pool, val->data); + apr_collapse_spaces(new_lock, new_lock); - /* Woo-hoo. We're back. */ - fetch_ctx->aborted_read = FALSE; - - /* Increment data and len by the difference. */ - data += fetch_ctx->read_size - fetch_ctx->aborted_read_size; - len += fetch_ctx->read_size - fetch_ctx->aborted_read_size; + if (new_lock[0] != '\0') + file->found_lock_prop = TRUE; } - if (len) - { - apr_size_t written_len; - - written_len = len; + return SVN_NO_ERROR; + } - SVN_ERR(svn_stream_write(fetch_ctx->target_stream, data, - &written_len)); - } + SVN_ERR(ensure_file_opened(file, scratch_pool)); - if (APR_STATUS_IS_EOF(status)) - { - fetch_ctx->done = TRUE; - } + SVN_ERR(ctx->editor->change_file_prop(file->file_baton, + prop_name, val, + scratch_pool)); - if (status) - { - return svn_ra_serf__wrap_err(status, NULL); - } - } - /* not reached */ + return SVN_NO_ERROR; } -/* Close the directory represented by DIR -- and any suitable parents - thereof -- if we are able to do so. This is the case whenever: - - - there are no remaining open items within the directory, and - - the directory's XML close tag has been processed (so we know - there are no more children to worry about in the future), and - - either: - - we aren't fetching properties for this directory, or - - we've already finished fetching those properties. -*/ +/* Implements svn_ra_serf__response_done_delegate_t */ static svn_error_t * -maybe_close_dir_chain(report_dir_t *dir) +file_props_done(serf_request_t *request, + void *baton, + apr_pool_t *scratch_pool) { - report_dir_t *cur_dir = dir; + file_baton_t *file = baton; + svn_ra_serf__handler_t *handler = file->propfind_handler; - SVN_ERR(ensure_dir_opened(cur_dir)); + if (handler->server_error) + return svn_error_trace(svn_ra_serf__server_error_create(handler, + scratch_pool)); - while (cur_dir - && !cur_dir->ref_count - && cur_dir->tag_closed - && (!cur_dir->fetch_props || cur_dir->propfind_handler->done)) - { - report_dir_t *parent = cur_dir->parent_dir; - report_context_t *report_context = cur_dir->report_context; - svn_boolean_t propfind_in_done_list = FALSE; - svn_ra_serf__list_t *done_list; - - /* Make sure there are no references to this dir in the - active_dir_propfinds list. If there are, don't close the - directory -- which would delete the pool from which the - relevant active_dir_propfinds list item is allocated -- and - of course don't crawl upward to check the parents for - a closure opportunity, either. */ - done_list = report_context->active_dir_propfinds; - while (done_list) - { - if (done_list->data == cur_dir) - { - propfind_in_done_list = TRUE; - break; - } - done_list = done_list->next; - } - if (propfind_in_done_list) - break; + if (handler->sline.code != 207) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); - SVN_ERR(close_dir(cur_dir)); - if (parent) - { - parent->ref_count--; - } - else - { - report_context->closed_root = TRUE; - } - cur_dir = parent; - } - - return SVN_NO_ERROR; -} - -/* Open the file associated with INFO for editing, pass along any - propchanges we've recorded for it, and then close the file. */ -static svn_error_t * -handle_propchange_only(report_info_t *info, - apr_pool_t *scratch_pool) -{ - SVN_ERR(open_updated_file(info, FALSE, scratch_pool)); - SVN_ERR(close_updated_file(info, scratch_pool)); + file->parent_dir->ctx->num_active_propfinds--; - /* We're done with our pool. */ - svn_pool_destroy(info->pool); + file->fetch_props = FALSE; - info->dir->ref_count--; + if (file->fetch_file) + return SVN_NO_ERROR; /* Still processing file request */ - /* See if the parent directory of this file (and perhaps even - parents of that) can be closed now. */ - SVN_ERR(maybe_close_dir_chain(info->dir)); + /* Closing the file will automatically deliver the propfind props. + * + * Note that closing the directory may dispose the pool containing the + * handler, which is only a valid operation in this callback, as only + * after this callback our serf plumbing assumes the request is done. */ - return SVN_NO_ERROR; + return svn_error_trace(close_file(file, scratch_pool)); } -/* "Fetch" a file whose contents were made available via the - get_wc_contents() callback (as opposed to requiring a GET to the - server), and feed the information through the associated update - editor. In editor-speak, this will add/open the file, transmit any - property changes, handle the contents, and then close the file. */ static svn_error_t * -handle_local_content(report_info_t *info, - apr_pool_t *scratch_pool) +file_fetch_done(serf_request_t *request, + void *baton, + apr_pool_t *scratch_pool) { - SVN_ERR(svn_txdelta_send_stream(info->cached_contents, info->textdelta, - info->textdelta_baton, NULL, scratch_pool)); - SVN_ERR(svn_stream_close(info->cached_contents)); - info->cached_contents = NULL; - SVN_ERR(close_updated_file(info, scratch_pool)); + fetch_ctx_t *fetch_ctx = baton; + file_baton_t *file = fetch_ctx->file; + svn_ra_serf__handler_t *handler = fetch_ctx->handler; - /* We're done with our pool. */ - svn_pool_destroy(info->pool); + if (handler->server_error) + return svn_error_trace(svn_ra_serf__server_error_create(handler, + scratch_pool)); - info->dir->ref_count--; + if (handler->sline.code != 200) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); - /* See if the parent directory of this fetched item (and - perhaps even parents of that) can be closed now. */ - SVN_ERR(maybe_close_dir_chain(info->dir)); + file->parent_dir->ctx->num_active_fetches--; - return SVN_NO_ERROR; -} + file->fetch_file = FALSE; -/* --------------------------------------------------------- */ + if (file->fetch_props) + return SVN_NO_ERROR; /* Still processing PROPFIND request */ + /* Closing the file will automatically deliver the propfind props. + * + * Note that closing the directory may dispose the pool containing the + * handler, fetch_ctx, etc. which is only a valid operation in this + * callback, as only after this callback our serf plumbing assumes the + * request is done. */ + return svn_error_trace(close_file(file, scratch_pool)); +} + +/* Initiates additional requests needed for a file when not in "send-all" mode. + */ static svn_error_t * -fetch_file(report_context_t *ctx, report_info_t *info) +fetch_for_file(file_baton_t *file, + apr_pool_t *scratch_pool) { + report_context_t *ctx = file->parent_dir->ctx; svn_ra_serf__connection_t *conn; svn_ra_serf__handler_t *handler; + /* Open extra connections if we have enough requests to send. */ + if (ctx->sess->num_conns < ctx->sess->max_connections) + SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches + + ctx->num_active_propfinds)); + /* What connection should we go on? */ conn = get_best_connection(ctx); - /* If needed, create the PROPFIND to retrieve the file's properties. */ - info->propfind_handler = NULL; - if (info->fetch_props) - { - SVN_ERR(svn_ra_serf__deliver_props(&info->propfind_handler, info->props, - ctx->sess, conn, info->url, - ctx->target_rev, "0", all_props, - &ctx->done_propfinds, - info->dir->pool)); - SVN_ERR_ASSERT(info->propfind_handler); - - /* Create a serf request for the PROPFIND. */ - svn_ra_serf__request_create(info->propfind_handler); - - ctx->num_active_propfinds++; - } + /* Note that we (still) use conn for both requests.. Should we send + them out on different connections? */ - /* If we've been asked to fetch the file or it's an add, do so. - * Otherwise, handle the case where only the properties changed. - */ - if (info->fetch_file && ctx->text_deltas) + if (file->fetch_file) { - svn_stream_t *contents = NULL; + SVN_ERR(open_file_txdelta(file, scratch_pool)); - /* Open the file for editing. */ - SVN_ERR(open_updated_file(info, FALSE, info->pool)); - - if (info->textdelta == svn_delta_noop_window_handler) + if (!ctx->text_deltas + || file->txdelta == svn_delta_noop_window_handler) { - /* There is nobody looking for an actual stream. - - Just report an empty stream instead of fetching - to be ingored data */ - info->cached_contents = svn_stream_empty(info->pool); + SVN_ERR(file->txdelta(NULL, file->txdelta_baton)); + file->fetch_file = FALSE; } - else if (ctx->sess->wc_callbacks->get_wc_contents - && info->final_sha1_checksum) - { - svn_error_t *err = NULL; - svn_checksum_t *checksum = NULL; - - /* Parse the optional SHA1 checksum (1.7+) */ - err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1, - info->final_sha1_checksum, - info->pool); - /* Okay so far? Let's try to get a stream on some readily - available matching content. */ - if (!err && checksum) - { - err = ctx->sess->wc_callbacks->get_wc_contents( - ctx->sess->wc_callback_baton, &contents, - checksum, info->pool); + if (file->fetch_file + && file->final_sha1_checksum + && ctx->sess->wc_callbacks->get_wc_contents) + { + svn_error_t *err; + svn_stream_t *cached_contents = NULL; - if (! err) - info->cached_contents = contents; - } + err = ctx->sess->wc_callbacks->get_wc_contents( + ctx->sess->wc_callback_baton, + &cached_contents, + file->final_sha1_checksum, + scratch_pool); - if (err) + if (err || !cached_contents) + svn_error_clear(err); /* ### Can we return some/most errors? */ + else { - /* Meh. Maybe we'll care one day why we're in an - errorful state, but this codepath is optional. */ - svn_error_clear(err); + /* ### For debugging purposes we could validate the md5 here, + but our implementations in libsvn_client already do that + for us... */ + SVN_ERR(svn_txdelta_send_stream(cached_contents, + file->txdelta, + file->txdelta_baton, + NULL, scratch_pool)); + SVN_ERR(svn_stream_close(cached_contents)); + file->fetch_file = FALSE; } } - /* If the working copy can provide cached contents for this - file, we don't have to fetch them from the server. */ - if (info->cached_contents) + if (file->fetch_file) { - /* If we'll be doing a PROPFIND for this file... */ - if (info->propfind_handler) + fetch_ctx_t *fetch_ctx; + + /* Let's fetch the file with a GET request... */ + SVN_ERR_ASSERT(file->url && file->repos_relpath); + + /* Otherwise, we use a GET request for the file's contents. */ + + fetch_ctx = apr_pcalloc(file->pool, sizeof(*fetch_ctx)); + fetch_ctx->file = file; + fetch_ctx->using_compression = ctx->sess->using_compression; + + /* Can we somehow get away with just obtaining a DIFF? */ + if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess)) { - /* ... then we'll just leave ourselves a little "todo" - about that fact (and we'll deal with the file content - stuff later, after we've handled that PROPFIND - response. */ - svn_ra_serf__list_t *list_item; - - list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item)); - list_item->data = info; - list_item->next = ctx->file_propchanges_only; - ctx->file_propchanges_only = list_item; + /* If this file is switched vs the editor root we should provide + its real url instead of the one calculated from the session root. + */ + if (SVN_IS_VALID_REVNUM(file->base_rev)) + { + fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s", + ctx->sess->rev_root_stub, + file->base_rev, + svn_path_uri_encode( + file->repos_relpath, + scratch_pool)); + } + else if (file->copyfrom_path) + { + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(file->copyfrom_rev)); + + fetch_ctx->delta_base = apr_psprintf(file->pool, "%s/%ld/%s", + ctx->sess->rev_root_stub, + file->copyfrom_rev, + svn_path_uri_encode( + file->copyfrom_path+1, + scratch_pool)); + } } - else + else if (ctx->sess->wc_callbacks->get_wc_prop) { - /* Otherwise, if we've no PROPFIND to do, we might as - well take care of those locally accessible file - contents now. */ - SVN_ERR(handle_local_content(info, info->pool)); + /* If we have a WC, we might be able to dive all the way into the WC + * to get the previous URL so we can do a differential GET with the + * base URL. + */ + const svn_string_t *value = NULL; + SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop( + ctx->sess->wc_callback_baton, + file->relpath, + SVN_RA_SERF__WC_CHECKED_IN_URL, + &value, scratch_pool)); + + fetch_ctx->delta_base = value + ? apr_pstrdup(file->pool, value->data) + : NULL; } - } - else - { - /* Otherwise, we use a GET request for the file's contents. */ - report_fetch_t *fetch_ctx; - fetch_ctx = apr_pcalloc(info->dir->pool, sizeof(*fetch_ctx)); - fetch_ctx->info = info; - fetch_ctx->done_list = &ctx->done_fetches; - fetch_ctx->sess = ctx->sess; - fetch_ctx->conn = conn; + handler = svn_ra_serf__create_handler(ctx->sess, file->pool); - handler = apr_pcalloc(info->dir->pool, sizeof(*handler)); - - handler->handler_pool = info->dir->pool; handler->method = "GET"; - handler->path = fetch_ctx->info->url; + handler->path = file->url; - handler->conn = conn; - handler->session = ctx->sess; + handler->conn = conn; /* Explicit scheduling */ handler->custom_accept_encoding = TRUE; + handler->no_dav_headers = TRUE; handler->header_delegate = headers_fetch; handler->header_delegate_baton = fetch_ctx; @@ -1584,6 +1487,9 @@ fetch_file(report_context_t *ctx, report_info_t *info) handler->response_error = cancel_fetch; handler->response_error_baton = fetch_ctx; + handler->done_delegate = file_fetch_done; + handler->done_delegate_baton = fetch_ctx; + fetch_ctx->handler = handler; svn_ra_serf__request_create(handler); @@ -1591,944 +1497,691 @@ fetch_file(report_context_t *ctx, report_info_t *info) ctx->num_active_fetches++; } } - else if (info->propfind_handler) - { - svn_ra_serf__list_t *list_item; - list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item)); - list_item->data = info; - list_item->next = ctx->file_propchanges_only; - ctx->file_propchanges_only = list_item; - } - else + /* If needed, create the PROPFIND to retrieve the file's properties. */ + if (file->fetch_props) { - /* No propfind or GET request. Just handle the prop changes now. */ - SVN_ERR(handle_propchange_only(info, info->pool)); + SVN_ERR(svn_ra_serf__create_propfind_handler(&file->propfind_handler, + ctx->sess, file->url, + ctx->target_rev, "0", + all_props, + set_file_props, file, + file->pool)); + file->propfind_handler->conn = conn; /* Explicit scheduling */ + + file->propfind_handler->done_delegate = file_props_done; + file->propfind_handler->done_delegate_baton = file; + + /* Create a serf request for the PROPFIND. */ + svn_ra_serf__request_create(file->propfind_handler); + + ctx->num_active_propfinds++; } - if (ctx->num_active_fetches + ctx->num_active_propfinds - > REQUEST_COUNT_TO_PAUSE) - ctx->parser_ctx->paused = TRUE; + if (file->fetch_props || file->fetch_file) + return SVN_NO_ERROR; - return SVN_NO_ERROR; -} - -/** XML callbacks for our update-report response parsing */ + /* Somehow we are done; probably via the local cache. + Close the file and release memory, etc. */ + return svn_error_trace(close_file(file, scratch_pool)); +} + +/* Implements svn_ra_serf__prop_func */ static svn_error_t * -start_report(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - const char **attrs, +set_dir_prop(void *baton, + const char *path, + const char *ns, + const char *name, + const svn_string_t *val, apr_pool_t *scratch_pool) { - report_context_t *ctx = parser->user_data; - report_state_e state; + dir_baton_t *dir = baton; + report_context_t *ctx = dir->ctx; + const char *prop_name; - state = parser->state->current_state; + prop_name = svn_ra_serf__svnname_from_wirename(ns, name, scratch_pool); + if (prop_name == NULL) + return SVN_NO_ERROR; - if (state == NONE && strcmp(name.name, "update-report") == 0) - { - const char *val; + SVN_ERR(ensure_dir_opened(dir, scratch_pool)); - val = svn_xml_get_attr_value("inline-props", attrs); - if (val && (strcmp(val, "true") == 0)) - ctx->add_props_included = TRUE; + SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton, + prop_name, val, + scratch_pool)); + return SVN_NO_ERROR; +} - val = svn_xml_get_attr_value("send-all", attrs); - if (val && (strcmp(val, "true") == 0)) - { - ctx->send_all_mode = TRUE; +/* Implements svn_ra_serf__response_done_delegate_t */ +static svn_error_t * +dir_props_done(serf_request_t *request, + void *baton, + apr_pool_t *scratch_pool) +{ + dir_baton_t *dir = baton; + svn_ra_serf__handler_t *handler = dir->propfind_handler; - /* All properties are included in send-all mode. */ - ctx->add_props_included = TRUE; - } - } - else if (state == NONE && strcmp(name.name, "target-revision") == 0) - { - const char *rev; + if (handler->server_error) + return svn_ra_serf__server_error_create(handler, scratch_pool); - rev = svn_xml_get_attr_value("rev", attrs); + if (handler->sline.code != 207) + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); - if (!rev) - { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing revision attr in target-revision element")); - } + dir->ctx->num_active_propfinds--; - SVN_ERR(ctx->update_editor->set_target_revision(ctx->update_baton, - SVN_STR_TO_REV(rev), - ctx->sess->pool)); - } - else if (state == NONE && strcmp(name.name, "open-directory") == 0) - { - const char *rev; - report_info_t *info; + /* Closing the directory will automatically deliver the propfind props. + * + * Note that closing the directory may dispose the pool containing the + * handler, which is only a valid operation in this callback, as after + * this callback serf assumes the request is done. */ - rev = svn_xml_get_attr_value("rev", attrs); + return svn_error_trace(maybe_close_dir(dir)); +} - if (!rev) - { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing revision attr in open-directory element")); - } +/* Initiates additional requests needed for a directory when not in "send-all" + * mode */ +static svn_error_t * +fetch_for_dir(dir_baton_t *dir, + apr_pool_t *scratch) +{ + report_context_t *ctx = dir->ctx; + svn_ra_serf__connection_t *conn; - info = push_state(parser, ctx, OPEN_DIR); + /* Open extra connections if we have enough requests to send. */ + if (ctx->sess->num_conns < ctx->sess->max_connections) + SVN_ERR(open_connection_if_needed(ctx->sess, ctx->num_active_fetches + + ctx->num_active_propfinds)); - info->base_rev = SVN_STR_TO_REV(rev); - info->dir->base_rev = info->base_rev; - info->fetch_props = TRUE; + /* What connection should we go on? */ + conn = get_best_connection(ctx); - info->dir->base_name = ""; - info->dir->name = ""; + /* If needed, create the PROPFIND to retrieve the file's properties. */ + if (dir->fetch_props) + { + SVN_ERR(svn_ra_serf__create_propfind_handler(&dir->propfind_handler, + ctx->sess, dir->url, + ctx->target_rev, "0", + all_props, + set_dir_prop, dir, + dir->pool)); - info->base_name = info->dir->base_name; - info->name = info->dir->name; + dir->propfind_handler->conn = conn; + dir->propfind_handler->done_delegate = dir_props_done; + dir->propfind_handler->done_delegate_baton = dir; - info->dir->repos_relpath = svn_hash_gets(ctx->switched_paths, ""); + /* Create a serf request for the PROPFIND. */ + svn_ra_serf__request_create(dir->propfind_handler); - if (!info->dir->repos_relpath) - SVN_ERR(svn_ra_serf__get_relative_path(&info->dir->repos_relpath, - ctx->sess->session_url.path, - ctx->sess, ctx->conn, - info->dir->pool)); - } - else if (state == NONE) - { - /* do nothing as we haven't seen our valid start tag yet. */ + ctx->num_active_propfinds++; } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "open-directory") == 0) - { - const char *rev, *dirname; - report_dir_t *dir; - report_info_t *info; + else + SVN_ERR_MALFUNCTION(); - rev = svn_xml_get_attr_value("rev", attrs); + return SVN_NO_ERROR; +} - if (!rev) - { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing revision attr in open-directory element")); - } + +/** XML callbacks for our update-report response parsing */ - dirname = svn_xml_get_attr_value("name", attrs); +/* Conforms to svn_ra_serf__xml_opened_t */ +static svn_error_t * +update_opened(svn_ra_serf__xml_estate_t *xes, + void *baton, + int entered_state, + const svn_ra_serf__dav_props_t *tag, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = baton; + apr_hash_t *attrs; - if (!dirname) + switch (entered_state) + { + case UPDATE_REPORT: { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in open-directory element")); - } - - info = push_state(parser, ctx, OPEN_DIR); - - dir = info->dir; + const char *val; - info->base_rev = SVN_STR_TO_REV(rev); - dir->base_rev = info->base_rev; + attrs = svn_ra_serf__xml_gather_since(xes, UPDATE_REPORT); + val = svn_hash_gets(attrs, "inline-props"); - info->fetch_props = FALSE; + if (val && (strcmp(val, "true") == 0)) + ctx->add_props_included = TRUE; - dir->base_name = apr_pstrdup(dir->pool, dirname); - info->base_name = dir->base_name; + val = svn_hash_gets(attrs, "send-all"); - /* Expand our name. */ - dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name, - dir->pool); - info->name = dir->name; - - dir->repos_relpath = svn_hash_gets(ctx->switched_paths, dir->name); + if (val && (strcmp(val, "true") == 0)) + { + ctx->send_all_mode = TRUE; - if (!dir->repos_relpath) - dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath, - dir->base_name, dir->pool); - } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "add-directory") == 0) - { - const char *dir_name, *cf, *cr; - report_dir_t *dir; - report_info_t *info; + /* All properties are included in send-all mode. */ + ctx->add_props_included = TRUE; + } + } + break; - dir_name = svn_xml_get_attr_value("name", attrs); - if (!dir_name) + case OPEN_DIR: + case ADD_DIR: { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in add-directory element")); - } - cf = svn_xml_get_attr_value("copyfrom-path", attrs); - cr = svn_xml_get_attr_value("copyfrom-rev", attrs); + dir_baton_t *dir; + const char *name; + attrs = svn_ra_serf__xml_gather_since(xes, entered_state); - info = push_state(parser, ctx, ADD_DIR); + name = svn_hash_gets(attrs, "name"); + if (!name) + name = ""; - dir = info->dir; + SVN_ERR(create_dir_baton(&dir, ctx, name, scratch_pool)); - dir->base_name = apr_pstrdup(dir->pool, dir_name); - info->base_name = dir->base_name; + if (entered_state == OPEN_DIR) + { + apr_int64_t base_rev; - /* Expand our name. */ - dir->name = svn_relpath_join(dir->parent_dir->name, dir->base_name, - dir->pool); - info->name = dir->name; + SVN_ERR(svn_cstring_atoi64(&base_rev, + svn_hash_gets(attrs, "rev"))); + dir->base_rev = (svn_revnum_t)base_rev; + } + else + { + dir->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path"); - info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL; - info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM; + if (dir->copyfrom_path) + { + apr_int64_t copyfrom_rev; + const char *copyfrom_rev_str; + dir->copyfrom_path = svn_fspath__canonicalize( + dir->copyfrom_path, + dir->pool); - /* Mark that we don't have a base. */ - info->base_rev = SVN_INVALID_REVNUM; - dir->base_rev = info->base_rev; + copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev"); - /* If the server isn't included properties for added items, - we'll need to fetch them ourselves. */ - if (! ctx->add_props_included) - dir->fetch_props = TRUE; + if (!copyfrom_rev_str) + return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND, + NULL, + _("Missing '%s' attribute"), + "copyfrom-rev"); - dir->repos_relpath = svn_relpath_join(dir->parent_dir->repos_relpath, - dir->base_name, dir->pool); - } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "open-file") == 0) - { - const char *file_name, *rev; - report_info_t *info; + SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str)); - file_name = svn_xml_get_attr_value("name", attrs); + dir->copyfrom_rev = (svn_revnum_t)copyfrom_rev; + } - if (!file_name) - { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in open-file element")); + if (! ctx->add_props_included) + dir->fetch_props = TRUE; + } } - - rev = svn_xml_get_attr_value("rev", attrs); - - if (!rev) + break; + case OPEN_FILE: + case ADD_FILE: { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing revision attr in open-file element")); - } + file_baton_t *file; - info = push_state(parser, ctx, OPEN_FILE); + attrs = svn_ra_serf__xml_gather_since(xes, entered_state); - info->base_rev = SVN_STR_TO_REV(rev); - info->fetch_props = FALSE; + SVN_ERR(create_file_baton(&file, ctx, svn_hash_gets(attrs, "name"), + scratch_pool)); - info->base_name = apr_pstrdup(info->pool, file_name); - info->name = NULL; - } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "add-file") == 0) - { - const char *file_name, *cf, *cr; - report_info_t *info; - - file_name = svn_xml_get_attr_value("name", attrs); - cf = svn_xml_get_attr_value("copyfrom-path", attrs); - cr = svn_xml_get_attr_value("copyfrom-rev", attrs); + if (entered_state == OPEN_FILE) + { + apr_int64_t base_rev; - if (!file_name) - { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in add-file element")); - } + SVN_ERR(svn_cstring_atoi64(&base_rev, + svn_hash_gets(attrs, "rev"))); + file->base_rev = (svn_revnum_t)base_rev; + } + else + { + const char *sha1_checksum; + file->copyfrom_path = svn_hash_gets(attrs, "copyfrom-path"); - info = push_state(parser, ctx, ADD_FILE); + if (file->copyfrom_path) + { + apr_int64_t copyfrom_rev; + const char *copyfrom_rev_str; - info->base_rev = SVN_INVALID_REVNUM; + file->copyfrom_path = svn_fspath__canonicalize( + file->copyfrom_path, + file->pool); - /* If the server isn't in "send-all" mode, we should expect to - fetch contents for added files. */ - if (! ctx->send_all_mode) - info->fetch_file = TRUE; + copyfrom_rev_str = svn_hash_gets(attrs, "copyfrom-rev"); - /* If the server isn't included properties for added items, - we'll need to fetch them ourselves. */ - if (! ctx->add_props_included) - info->fetch_props = TRUE; + if (!copyfrom_rev_str) + return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND, + NULL, + _("Missing '%s' attribute"), + "copyfrom-rev"); - info->base_name = apr_pstrdup(info->pool, file_name); - info->name = NULL; + SVN_ERR(svn_cstring_atoi64(©from_rev, copyfrom_rev_str)); - info->copyfrom_path = cf ? apr_pstrdup(info->pool, cf) : NULL; - info->copyfrom_rev = cr ? SVN_STR_TO_REV(cr) : SVN_INVALID_REVNUM; + file->copyfrom_rev = (svn_revnum_t)copyfrom_rev; + } - info->final_sha1_checksum = - svn_xml_get_attr_value("sha1-checksum", attrs); - if (info->final_sha1_checksum) - info->final_sha1_checksum = apr_pstrdup(info->pool, - info->final_sha1_checksum); - } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "delete-entry") == 0) - { - const char *file_name; - const char *rev_str; - report_info_t *info; - apr_pool_t *tmppool; - const char *full_path; - svn_revnum_t delete_rev = SVN_INVALID_REVNUM; + sha1_checksum = svn_hash_gets(attrs, "sha1-checksum"); + if (sha1_checksum) + { + SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum, + svn_checksum_sha1, + sha1_checksum, + file->pool)); + } - file_name = svn_xml_get_attr_value("name", attrs); + /* If the server isn't in "send-all" mode, we should expect to + fetch contents for added files. */ + if (! ctx->send_all_mode) + file->fetch_file = TRUE; - if (!file_name) - { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in delete-entry element")); + /* If the server isn't included properties for added items, + we'll need to fetch them ourselves. */ + if (! ctx->add_props_included) + file->fetch_props = TRUE; + } } + break; - rev_str = svn_xml_get_attr_value("rev", attrs); - if (rev_str) /* Not available on older repositories! */ - delete_rev = SVN_STR_TO_REV(rev_str); + case TXDELTA: + { + file_baton_t *file = ctx->cur_file; + const char *base_checksum; - info = parser->state->private; + /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in + addition to <fetch-file>s and such) when *not* in + "send-all" mode. As a client, we're smart enough to know + that's wrong, so we'll just ignore these tags. */ + if (! ctx->send_all_mode) + break; - SVN_ERR(ensure_dir_opened(info->dir)); + file->fetch_file = FALSE; - tmppool = svn_pool_create(info->dir->dir_baton_pool); + attrs = svn_ra_serf__xml_gather_since(xes, entered_state); + base_checksum = svn_hash_gets(attrs, "base-checksum"); - full_path = svn_relpath_join(info->dir->name, file_name, tmppool); + if (base_checksum) + SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum, + svn_checksum_md5, base_checksum, + file->pool)); - SVN_ERR(ctx->update_editor->delete_entry(full_path, - delete_rev, - info->dir->dir_baton, - tmppool)); + SVN_ERR(open_file_txdelta(ctx->cur_file, scratch_pool)); - svn_pool_destroy(tmppool); - } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "absent-directory") == 0) - { - const char *file_name; - report_info_t *info; + if (ctx->cur_file->txdelta != svn_delta_noop_window_handler) + { + svn_stream_t *decoder; - file_name = svn_xml_get_attr_value("name", attrs); + decoder = svn_txdelta_parse_svndiff(file->txdelta, + file->txdelta_baton, + TRUE /* error early close*/, + file->pool); - if (!file_name) - { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in absent-directory element")); + file->txdelta_stream = svn_base64_decode(decoder, file->pool); + } } + break; - info = parser->state->private; + case FETCH_PROPS: + { + /* Subversion <= 1.6 servers will return a fetch-props element on + open-file and open-dir when non entry props were changed in + !send-all mode. In turn we fetch the full set of properties + and send all of those as *changes* to the editor. So these + editors have to be aware that they receive-non property changes. + (In case of incomplete directories they have to be aware anyway) - SVN_ERR(ensure_dir_opened(info->dir)); + In r1063337 this behavior was changed in mod_dav_svn to always + send property changes inline in these cases. (See issue #3657) - SVN_ERR(ctx->update_editor->absent_directory( - svn_relpath_join(info->name, file_name, - info->dir->pool), - info->dir->dir_baton, - info->dir->pool)); + Note that before that change the property changes to the last_* + entry props were already inlined via specific xml elements. */ + if (ctx->cur_file) + ctx->cur_file->fetch_props = TRUE; + else if (ctx->cur_dir) + ctx->cur_dir->fetch_props = TRUE; + } + break; } - else if ((state == OPEN_DIR || state == ADD_DIR) && - strcmp(name.name, "absent-file") == 0) - { - const char *file_name; - report_info_t *info; - file_name = svn_xml_get_attr_value("name", attrs); + return SVN_NO_ERROR; +} - if (!file_name) - { - return svn_error_create( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in absent-file element")); - } - info = parser->state->private; - SVN_ERR(ensure_dir_opened(info->dir)); +/* Conforms to svn_ra_serf__xml_closed_t */ +static svn_error_t * +update_closed(svn_ra_serf__xml_estate_t *xes, + void *baton, + int leaving_state, + const svn_string_t *cdata, + apr_hash_t *attrs, + apr_pool_t *scratch_pool) +{ + report_context_t *ctx = baton; - SVN_ERR(ctx->update_editor->absent_file( - svn_relpath_join(info->name, file_name, - info->dir->pool), - info->dir->dir_baton, - info->dir->pool)); - } - else if (state == OPEN_DIR || state == ADD_DIR) + switch (leaving_state) { - report_info_t *info; - - if (strcmp(name.name, "checked-in") == 0) - { - info = push_state(parser, ctx, IGNORE_PROP_NAME); - info->prop_ns = name.namespace; - info->prop_name = apr_pstrdup(parser->state->pool, name.name); - info->prop_encoding = NULL; - svn_stringbuf_setempty(info->prop_value); - } - else if (strcmp(name.name, "set-prop") == 0 || - strcmp(name.name, "remove-prop") == 0) + case UPDATE_REPORT: + ctx->done = TRUE; + break; + case TARGET_REVISION: { - const char *full_prop_name; - const char *colon; - - info = push_state(parser, ctx, PROP); - - full_prop_name = svn_xml_get_attr_value("name", attrs); - if (!full_prop_name) - { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in %s element"), - name.name); - } - - colon = strchr(full_prop_name, ':'); - - if (colon) - colon++; - else - colon = full_prop_name; + const char *revstr = svn_hash_gets(attrs, "rev"); + apr_int64_t rev; - info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name, - colon - full_prop_name); - info->prop_name = apr_pstrdup(parser->state->pool, colon); - info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); - svn_stringbuf_setempty(info->prop_value); - } - else if (strcmp(name.name, "prop") == 0) - { - /* need to fetch it. */ - push_state(parser, ctx, NEED_PROP_NAME); - } - else if (strcmp(name.name, "fetch-props") == 0) - { - info = parser->state->private; + SVN_ERR(svn_cstring_atoi64(&rev, revstr)); - info->dir->fetch_props = TRUE; - } - else - { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Unknown tag '%s' while at state %d"), - name.name, state); + SVN_ERR(ctx->editor->set_target_revision(ctx->editor_baton, + (svn_revnum_t)rev, + scratch_pool)); } + break; - } - else if (state == OPEN_FILE || state == ADD_FILE) - { - report_info_t *info; + case CHECKED_IN_HREF: + if (ctx->cur_file) + ctx->cur_file->url = apr_pstrdup(ctx->cur_file->pool, cdata->data); + else + ctx->cur_dir->url = apr_pstrdup(ctx->cur_dir->pool, cdata->data); + break; - if (strcmp(name.name, "checked-in") == 0) - { - info = push_state(parser, ctx, IGNORE_PROP_NAME); - info->prop_ns = name.namespace; - info->prop_name = apr_pstrdup(parser->state->pool, name.name); - info->prop_encoding = NULL; - svn_stringbuf_setempty(info->prop_value); - } - else if (strcmp(name.name, "prop") == 0) - { - /* need to fetch it. */ - push_state(parser, ctx, NEED_PROP_NAME); - } - else if (strcmp(name.name, "fetch-props") == 0) + case SET_PROP: + case REMOVE_PROP: { - info = parser->state->private; + const char *name = svn_hash_gets(attrs, "name"); + const char *encoding; + const svn_string_t *value; - info->fetch_props = TRUE; - } - else if (strcmp(name.name, "fetch-file") == 0) - { - info = parser->state->private; - info->base_checksum = svn_xml_get_attr_value("base-checksum", attrs); + if (leaving_state == REMOVE_PROP) + value = NULL; + else if ((encoding = svn_hash_gets(attrs, "encoding"))) + { + if (strcmp(encoding, "base64") != 0) + return svn_error_createf(SVN_ERR_XML_UNKNOWN_ENCODING, NULL, + _("Got unrecognized encoding '%s'"), + encoding); - if (info->base_checksum) - info->base_checksum = apr_pstrdup(info->pool, info->base_checksum); + value = svn_base64_decode_string(cdata, scratch_pool); + } + else + value = cdata; - info->final_sha1_checksum = - svn_xml_get_attr_value("sha1-checksum", attrs); - if (info->final_sha1_checksum) - info->final_sha1_checksum = apr_pstrdup(info->pool, - info->final_sha1_checksum); + if (ctx->cur_file) + { + file_baton_t *file = ctx->cur_file; - info->fetch_file = TRUE; - } - else if (strcmp(name.name, "set-prop") == 0 || - strcmp(name.name, "remove-prop") == 0) - { - const char *full_prop_name; - const char *colon; + if (value + || ctx->add_props_included + || SVN_IS_VALID_REVNUM(file->base_rev)) + { + SVN_ERR(ensure_file_opened(file, scratch_pool)); - info = push_state(parser, ctx, PROP); + SVN_ERR(ctx->editor->change_file_prop(file->file_baton, + name, + value, + scratch_pool)); + } + else + { + if (!file->remove_props) + file->remove_props = apr_hash_make(file->pool); - full_prop_name = svn_xml_get_attr_value("name", attrs); - if (!full_prop_name) - { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Missing name attr in %s element"), - name.name); + svn_hash_sets(file->remove_props, + apr_pstrdup(file->pool, name), + ""); + } } - colon = strchr(full_prop_name, ':'); - - if (colon) - colon++; else - colon = full_prop_name; - - info->prop_ns = apr_pstrmemdup(info->dir->pool, full_prop_name, - colon - full_prop_name); - info->prop_name = apr_pstrdup(parser->state->pool, colon); - info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); - svn_stringbuf_setempty(info->prop_value); - } - else if (strcmp(name.name, "txdelta") == 0) - { - /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in - addition to <fetch-file>s and such) when *not* in - "send-all" mode. As a client, we're smart enough to know - that's wrong, so we'll just ignore these tags. */ - if (ctx->send_all_mode) { - const svn_delta_editor_t *update_editor = ctx->update_editor; - - info = push_state(parser, ctx, TXDELTA); + dir_baton_t *dir = ctx->cur_dir; - if (! info->file_baton) + if (value + || ctx->add_props_included + || SVN_IS_VALID_REVNUM(dir->base_rev)) { - SVN_ERR(open_updated_file(info, FALSE, info->pool)); + SVN_ERR(ensure_dir_opened(dir, scratch_pool)); + + SVN_ERR(ctx->editor->change_dir_prop(dir->dir_baton, + name, + value, + scratch_pool)); } + else + { + if (!dir->remove_props) + dir->remove_props = apr_hash_make(dir->pool); - info->base_checksum = svn_xml_get_attr_value("base-checksum", - attrs); - SVN_ERR(update_editor->apply_textdelta(info->file_baton, - info->base_checksum, - info->editor_pool, - &info->textdelta, - &info->textdelta_baton)); - info->svndiff_decoder = svn_txdelta_parse_svndiff( - info->textdelta, - info->textdelta_baton, - TRUE, info->pool); - info->base64_decoder = svn_base64_decode(info->svndiff_decoder, - info->pool); + svn_hash_sets(dir->remove_props, + apr_pstrdup(dir->pool, name), + ""); + } } } - else - { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Unknown tag '%s' while at state %d"), - name.name, state); - } - } - else if (state == IGNORE_PROP_NAME) - { - report_info_t *info = push_state(parser, ctx, PROP); - info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); - } - else if (state == NEED_PROP_NAME) - { - report_info_t *info; - - info = push_state(parser, ctx, PROP); - - info->prop_ns = name.namespace; - info->prop_name = apr_pstrdup(parser->state->pool, name.name); - info->prop_encoding = svn_xml_get_attr_value("encoding", attrs); - svn_stringbuf_setempty(info->prop_value); - } - - return SVN_NO_ERROR; -} + break; -static svn_error_t * -end_report(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - apr_pool_t *scratch_pool) -{ - report_context_t *ctx = parser->user_data; - report_state_e state; + case OPEN_DIR: + case ADD_DIR: + { + dir_baton_t *dir = ctx->cur_dir; + ctx->cur_dir = ctx->cur_dir->parent_dir; - state = parser->state->current_state; + if (dir->fetch_props && ! dir->url) + { + return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("The REPORT response did not " + "include the requested checked-in " + "value")); + } - if (state == NONE) - { - if (strcmp(name.name, "update-report") == 0) - { - ctx->report_completed = TRUE; - } - else - { - /* nothing to close yet. */ - return SVN_NO_ERROR; + if (!dir->fetch_props) + { + SVN_ERR(maybe_close_dir(dir)); + break; /* dir potentially no longer valid */ + } + else + { + /* Otherwise, if the server is *not* in "send-all" mode, we + are at a point where we can queue up the PROPFIND request */ + SVN_ERR(fetch_for_dir(dir, scratch_pool)); + } } - } + break; - if (((state == OPEN_DIR && (strcmp(name.name, "open-directory") == 0)) || - (state == ADD_DIR && (strcmp(name.name, "add-directory") == 0)))) - { - const char *checked_in_url; - report_info_t *info = parser->state->private; + case OPEN_FILE: + case ADD_FILE: + { + file_baton_t *file = ctx->cur_file; - /* We've now closed this directory; note it. */ - info->dir->tag_closed = TRUE; + ctx->cur_file = NULL; + /* go fetch info->name from DAV:checked-in */ - /* go fetch info->file_name from DAV:checked-in */ - checked_in_url = - svn_ra_serf__get_ver_prop(info->dir->props, info->base_name, - info->base_rev, "DAV:", "checked-in"); + if ((file->fetch_file || file->fetch_props) && ! file->url) + { + return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("The REPORT response did not " + "include the requested checked-in " + "value")); + } - /* If we were expecting to have the properties and we aren't able to - * get it, bail. - */ - if (!checked_in_url && - (!SVN_IS_VALID_REVNUM(info->dir->base_rev) || info->dir->fetch_props)) - { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("The REPORT or PROPFIND response did not " - "include the requested checked-in value")); + /* If the server is in "send-all" mode or didn't get further work, + we can now close the file */ + if (! file->fetch_file && ! file->fetch_props) + { + SVN_ERR(close_file(file, scratch_pool)); + break; /* file is no longer valid */ + } + else + { + /* Otherwise, if the server is *not* in "send-all" mode, we + should be at a point where we can queue up any auxiliary + content-fetching requests. */ + SVN_ERR(fetch_for_file(file, scratch_pool)); + } } + break; - info->dir->url = checked_in_url; + case MD5_CHECKSUM: + SVN_ERR(svn_checksum_parse_hex(&ctx->cur_file->final_md5_checksum, + svn_checksum_md5, + cdata->data, + ctx->cur_file->pool)); + break; - /* At this point, we should have the checked-in href. - * If needed, create the PROPFIND to retrieve the dir's properties. - */ - if (info->dir->fetch_props) - { - svn_ra_serf__list_t *list_item; - - SVN_ERR(svn_ra_serf__deliver_props(&info->dir->propfind_handler, - info->dir->props, ctx->sess, - get_best_connection(ctx), - info->dir->url, - ctx->target_rev, "0", - all_props, - &ctx->done_dir_propfinds, - info->dir->pool)); - SVN_ERR_ASSERT(info->dir->propfind_handler); - - /* Create a serf request for the PROPFIND. */ - svn_ra_serf__request_create(info->dir->propfind_handler); - - ctx->num_active_propfinds++; - - list_item = apr_pcalloc(info->dir->pool, sizeof(*list_item)); - list_item->data = info->dir; - list_item->next = ctx->active_dir_propfinds; - ctx->active_dir_propfinds = list_item; - - if (ctx->num_active_fetches + ctx->num_active_propfinds - > REQUEST_COUNT_TO_PAUSE) - ctx->parser_ctx->paused = TRUE; - } - else + case FETCH_FILE: { - info->dir->propfind_handler = NULL; - } + file_baton_t *file = ctx->cur_file; + const char *base_checksum = svn_hash_gets(attrs, "base-checksum"); + const char *sha1_checksum = svn_hash_gets(attrs, "sha1-checksum"); - /* See if this directory (and perhaps even parents of that) can - be closed now. This is likely to be the case only if we - didn't need to contact the server for supplemental - information required to handle any of this directory's - children. */ - SVN_ERR(maybe_close_dir_chain(info->dir)); - svn_ra_serf__xml_pop_state(parser); - } - else if (state == OPEN_FILE && strcmp(name.name, "open-file") == 0) - { - report_info_t *info = parser->state->private; + if (base_checksum) + SVN_ERR(svn_checksum_parse_hex(&file->base_md5_checksum, + svn_checksum_md5, base_checksum, + file->pool)); - /* Expand our full name now if we haven't done so yet. */ - if (!info->name) - { - info->name = svn_relpath_join(info->dir->name, info->base_name, - info->pool); - } + /* Property is duplicated between add-file and fetch-file */ + if (sha1_checksum && !file->final_sha1_checksum) + SVN_ERR(svn_checksum_parse_hex(&file->final_sha1_checksum, + svn_checksum_sha1, + sha1_checksum, + file->pool)); - if (info->lock_token && !info->fetch_props) - info->fetch_props = TRUE; + /* Some 0.3x mod_dav_svn wrote both txdelta and fetch-file + elements in send-all mode. (See neon for history) */ + if (! ctx->send_all_mode) + file->fetch_file = TRUE; + } + break; - /* If possible, we'd like to fetch only a delta against a - * version of the file we already have in our working copy, - * rather than fetching a fulltext. - * - * In HTTP v2, we can simply construct the URL we need given the - * repos_relpath and base revision number. - */ - if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(ctx->sess)) + case DELETE_ENTRY: { - const char *repos_relpath; + const char *name = svn_hash_gets(attrs, "name"); + const char *revstr; + apr_int64_t delete_rev; - /* If this file is switched vs the editor root we should provide - its real url instead of the one calculated from the session root. - */ - repos_relpath = svn_hash_gets(ctx->switched_paths, info->name); + SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool)); - if (!repos_relpath) - { - if (ctx->root_is_switched) - { - /* We are updating a direct target (most likely a file) - that is switched vs its parent url */ - SVN_ERR_ASSERT(*svn_relpath_dirname(info->name, info->pool) - == '\0'); + revstr = svn_hash_gets(attrs, "rev"); - repos_relpath = svn_hash_gets(ctx->switched_paths, ""); - } - else - repos_relpath = svn_relpath_join(info->dir->repos_relpath, - info->base_name, info->pool); - } - - info->delta_base = apr_psprintf(info->pool, "%s/%ld/%s", - ctx->sess->rev_root_stub, - info->base_rev, - svn_path_uri_encode(repos_relpath, - info->pool)); - } - else if (ctx->sess->wc_callbacks->get_wc_prop) - { - /* If we have a WC, we might be able to dive all the way into the WC - * to get the previous URL so we can do a differential GET with the - * base URL. - */ - const svn_string_t *value = NULL; - SVN_ERR(ctx->sess->wc_callbacks->get_wc_prop( - ctx->sess->wc_callback_baton, info->name, - SVN_RA_SERF__WC_CHECKED_IN_URL, &value, info->pool)); + if (revstr) + SVN_ERR(svn_cstring_atoi64(&delete_rev, revstr)); + else + delete_rev = SVN_INVALID_REVNUM; - info->delta_base = value ? value->data : NULL; + SVN_ERR(ctx->editor->delete_entry( + svn_relpath_join(ctx->cur_dir->relpath, + name, + scratch_pool), + (svn_revnum_t)delete_rev, + ctx->cur_dir->dir_baton, + scratch_pool)); } + break; - /* go fetch info->name from DAV:checked-in */ - info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name, - info->base_rev, "DAV:", "checked-in"); - if (!info->url) + case ABSENT_DIR: { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("The REPORT or PROPFIND response did not " - "include the requested checked-in value")); - } + const char *name = svn_hash_gets(attrs, "name"); - /* If the server is in "send-all" mode, we might have opened the - file when we started seeing content for it. If we didn't get - any content for it, we still need to open the file. But in - any case, we can then immediately close it. */ - if (ctx->send_all_mode) - { - if (! info->file_baton) - { - SVN_ERR(open_updated_file(info, FALSE, info->pool)); - } - SVN_ERR(close_updated_file(info, info->pool)); - info->dir->ref_count--; - } - /* Otherwise, if the server is *not* in "send-all" mode, we - should be at a point where we can queue up any auxiliary - content-fetching requests. */ - else - { - SVN_ERR(fetch_file(ctx, info)); - } - - svn_ra_serf__xml_pop_state(parser); - } - else if (state == ADD_FILE && strcmp(name.name, "add-file") == 0) - { - report_info_t *info = parser->state->private; - - /* go fetch info->name from DAV:checked-in */ - info->url = svn_ra_serf__get_ver_prop(info->props, info->base_name, - info->base_rev, "DAV:", "checked-in"); - if (!info->url) - { - return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("The REPORT or PROPFIND response did not " - "include the requested checked-in value")); - } + SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool)); - /* If the server is in "send-all" mode, we might have opened the - file when we started seeing content for it. If we didn't get - any content for it, we still need to open the file. But in - any case, we can then immediately close it. */ - if (ctx->send_all_mode) - { - if (! info->file_baton) - { - SVN_ERR(open_updated_file(info, FALSE, info->pool)); - } - SVN_ERR(close_updated_file(info, info->pool)); - info->dir->ref_count--; + SVN_ERR(ctx->editor->absent_directory( + svn_relpath_join(ctx->cur_dir->relpath, + name, scratch_pool), + ctx->cur_dir->dir_baton, + scratch_pool)); } - /* Otherwise, if the server is *not* in "send-all" mode, we - should be at a point where we can queue up any auxiliary - content-fetching requests. */ - else + break; + case ABSENT_FILE: { - SVN_ERR(fetch_file(ctx, info)); - } + const char *name = svn_hash_gets(attrs, "name"); - svn_ra_serf__xml_pop_state(parser); - } - else if (state == TXDELTA && strcmp(name.name, "txdelta") == 0) - { - report_info_t *info = parser->state->private; + SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool)); - /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to - <fetch-file>s and such) when *not* in "send-all" mode. As a - client, we're smart enough to know that's wrong, so when not - in "receiving-all" mode, we'll ignore these tags. */ - if (ctx->send_all_mode) - { - SVN_ERR(svn_stream_close(info->base64_decoder)); + SVN_ERR(ctx->editor->absent_file( + svn_relpath_join(ctx->cur_dir->relpath, + name, scratch_pool), + ctx->cur_dir->dir_baton, + scratch_pool)); } + break; - svn_ra_serf__xml_pop_state(parser); - } - else if (state == PROP) - { - /* We need to move the prop_ns, prop_name, and prop_value into the - * same lifetime as the dir->pool. - */ - svn_ra_serf__ns_t *ns, *ns_name_match; - svn_boolean_t found = FALSE; - report_info_t *info; - report_dir_t *dir; - apr_hash_t *props; - const svn_string_t *set_val_str; - apr_pool_t *pool; - - info = parser->state->private; - dir = info->dir; - - /* We're going to be slightly tricky. We don't care what the ->url - * field is here at this point. So, we're going to stick a single - * copy of the property name inside of the ->url field. - */ - ns_name_match = NULL; - for (ns = dir->ns_list; ns; ns = ns->next) + case TXDELTA: { - if (strcmp(ns->namespace, info->prop_ns) == 0) - { - ns_name_match = ns; - if (strcmp(ns->url, info->prop_name) == 0) - { - found = TRUE; - break; - } - } - } + file_baton_t *file = ctx->cur_file; - if (!found) - { - ns = apr_palloc(dir->pool, sizeof(*ns)); - if (!ns_name_match) + if (file->txdelta_stream) { - ns->namespace = apr_pstrdup(dir->pool, info->prop_ns); + SVN_ERR(svn_stream_close(file->txdelta_stream)); + file->txdelta_stream = NULL; } - else - { - ns->namespace = ns_name_match->namespace; - } - ns->url = apr_pstrdup(dir->pool, info->prop_name); - - ns->next = dir->ns_list; - dir->ns_list = ns; } + break; - if (strcmp(name.name, "remove-prop") != 0) + case VERSION_NAME: + case CREATIONDATE: + case CREATOR_DISPLAYNAME: { - props = info->props; - pool = info->pool; - } - else - { - props = dir->removed_props; - pool = dir->pool; - svn_stringbuf_setempty(info->prop_value); - } + /* Subversion <= 1.6 servers would return a fetch-props element on + open-file and open-dir when non entry props were changed in + !send-all mode. In turn we fetch the full set of properties and + send those as *changes* to the editor. So these editors have to + be aware that they receive non property changes. + (In case of incomplete directories they have to be aware anyway) - if (info->prop_encoding) - { - if (strcmp(info->prop_encoding, "base64") == 0) - { - svn_string_t tmp; + In that case the last_* entry props are posted as 3 specific xml + elements, which we handle here. - /* Don't use morph_info_string cuz we need prop_value to - remain usable. */ - tmp.data = info->prop_value->data; - tmp.len = info->prop_value->len; + In r1063337 this behavior was changed in mod_dav_svn to always + send property changes inline in these cases. (See issue #3657) + */ - set_val_str = svn_base64_decode_string(&tmp, pool); - } + const char *propname; + + if (ctx->cur_file) + SVN_ERR(ensure_file_opened(ctx->cur_file, scratch_pool)); + else if (ctx->cur_dir) + SVN_ERR(ensure_dir_opened(ctx->cur_dir, scratch_pool)); else + break; + + switch (leaving_state) { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, - NULL, - _("Got unrecognized encoding '%s'"), - info->prop_encoding); + case VERSION_NAME: + propname = SVN_PROP_ENTRY_COMMITTED_REV; + break; + case CREATIONDATE: + propname = SVN_PROP_ENTRY_COMMITTED_DATE; + break; + case CREATOR_DISPLAYNAME: + propname = SVN_PROP_ENTRY_LAST_AUTHOR; + break; + default: + SVN_ERR_MALFUNCTION(); /* Impossible to reach */ } - } - else - { - set_val_str = svn_string_create_from_buf(info->prop_value, pool); - } - - svn_ra_serf__set_ver_prop(props, info->base_name, info->base_rev, - ns->namespace, ns->url, set_val_str, pool); - /* Advance handling: if we spotted the md5-checksum property on - the wire, remember it's value. */ - if (strcmp(ns->url, "md5-checksum") == 0 - && strcmp(ns->namespace, SVN_DAV_PROP_NS_DAV) == 0) - info->final_checksum = apr_pstrdup(info->pool, set_val_str->data); - - svn_ra_serf__xml_pop_state(parser); - } - else if (state == IGNORE_PROP_NAME || state == NEED_PROP_NAME) - { - svn_ra_serf__xml_pop_state(parser); + if (ctx->cur_file) + SVN_ERR(ctx->editor->change_file_prop(ctx->cur_file->file_baton, + propname, cdata, + scratch_pool)); + else + SVN_ERR(ctx->editor->change_dir_prop(ctx->cur_dir->dir_baton, + propname, cdata, + scratch_pool)); + } + break; } return SVN_NO_ERROR; } + +/* Conforms to svn_ra_serf__xml_cdata_t */ static svn_error_t * -cdata_report(svn_ra_serf__xml_parser_t *parser, +update_cdata(svn_ra_serf__xml_estate_t *xes, + void *baton, + int current_state, const char *data, apr_size_t len, apr_pool_t *scratch_pool) { - report_context_t *ctx = parser->user_data; - - UNUSED_CTX(ctx); - - if (parser->state->current_state == PROP) - { - report_info_t *info = parser->state->private; + report_context_t *ctx = baton; - svn_stringbuf_appendbytes(info->prop_value, data, len); - } - else if (parser->state->current_state == TXDELTA) + if (current_state == TXDELTA && ctx->cur_file + && ctx->cur_file->txdelta_stream) { - /* Pre 1.2, mod_dav_svn was using <txdelta> tags (in addition to - <fetch-file>s and such) when *not* in "send-all" mode. As a - client, we're smart enough to know that's wrong, so when not - in "receiving-all" mode, we'll ignore these tags. */ - if (ctx->send_all_mode) - { - apr_size_t nlen = len; - report_info_t *info = parser->state->private; - - SVN_ERR(svn_stream_write(info->base64_decoder, data, &nlen)); - if (nlen != len) - { - /* Short write without associated error? "Can't happen." */ - return svn_error_createf(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, - _("Error writing to '%s': unexpected EOF"), - info->name); - } - } + SVN_ERR(svn_stream_write(ctx->cur_file->txdelta_stream, data, &len)); } return SVN_NO_ERROR; @@ -2544,7 +2197,8 @@ make_simple_xml_tag(svn_stringbuf_t **buf_p, const char *cdata, apr_pool_t *pool) { - svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, NULL); + svn_xml_make_open_tag(buf_p, pool, svn_xml_protect_pcdata, tagname, + SVN_VA_NULL); svn_xml_escape_cdata_cstring(buf_p, cdata, pool); svn_xml_make_close_tag(buf_p, pool, tagname); } @@ -2566,12 +2220,11 @@ set_path(void *report_baton, "lock-token", lock_token, "depth", svn_depth_to_word(depth), "start-empty", start_empty ? "true" : NULL, - NULL); + SVN_VA_NULL); svn_xml_escape_cdata_cstring(&buf, path, pool); svn_xml_make_close_tag(&buf, pool, "S:entry"); - SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, - NULL, pool)); + SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); return SVN_NO_ERROR; } @@ -2586,8 +2239,7 @@ delete_path(void *report_baton, make_simple_xml_tag(&buf, "S:missing", path, pool); - SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, - NULL, pool)); + SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); return SVN_NO_ERROR; } @@ -2619,12 +2271,10 @@ link_path(void *report_baton, _("Unable to parse URL '%s'"), url); } - SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess, - NULL, pool)); - SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, - NULL, pool)); + SVN_ERR(svn_ra_serf__report_resource(&report_target, report->sess, pool)); + SVN_ERR(svn_ra_serf__get_relative_path(&link, uri.path, report->sess, pool)); - link = apr_pstrcat(pool, "/", link, (char *)NULL); + link = apr_pstrcat(pool, "/", link, SVN_VA_NULL); svn_xml_make_open_tag(&buf, pool, svn_xml_protect_pcdata, "S:entry", "rev", apr_ltoa(pool, revision), @@ -2632,80 +2282,55 @@ link_path(void *report_baton, "depth", svn_depth_to_word(depth), "linkpath", link, "start-empty", start_empty ? "true" : NULL, - NULL); + SVN_VA_NULL); svn_xml_escape_cdata_cstring(&buf, path, pool); svn_xml_make_close_tag(&buf, pool, "S:entry"); - SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, - NULL, pool)); + SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); /* Store the switch roots to allow generating repos_relpaths from just the working copy paths. (Needed for HTTPv2) */ path = apr_pstrdup(report->pool, path); - svn_hash_sets(report->switched_paths, - path, apr_pstrdup(report->pool, link + 1)); + link = apr_pstrdup(report->pool, link + 1); + svn_hash_sets(report->switched_paths, path, link); - if (!*path) - report->root_is_switched = TRUE; - - return APR_SUCCESS; -} - -/** Minimum nr. of outstanding requests needed before a new connection is - * opened. */ -#define REQS_PER_CONN 8 - -/** This function creates a new connection for this serf session, but only - * if the number of NUM_ACTIVE_REQS > REQS_PER_CONN or if there currently is - * only one main connection open. - */ -static svn_error_t * -open_connection_if_needed(svn_ra_serf__session_t *sess, int num_active_reqs) -{ - /* For each REQS_PER_CONN outstanding requests open a new connection, with - * a minimum of 1 extra connection. */ - if (sess->num_conns == 1 || - ((num_active_reqs / REQS_PER_CONN) > sess->num_conns)) + if (!path[0] && report->update_target[0]) { - int cur = sess->num_conns; - apr_status_t status; - - sess->conns[cur] = apr_pcalloc(sess->pool, sizeof(*sess->conns[cur])); - sess->conns[cur]->bkt_alloc = serf_bucket_allocator_create(sess->pool, - NULL, NULL); - sess->conns[cur]->last_status_code = -1; - sess->conns[cur]->session = sess; - status = serf_connection_create2(&sess->conns[cur]->conn, - sess->context, - sess->session_url, - svn_ra_serf__conn_setup, - sess->conns[cur], - svn_ra_serf__conn_closed, - sess->conns[cur], - sess->pool); - if (status) - return svn_ra_serf__wrap_err(status, NULL); - - sess->num_conns++; + /* The update root is switched. Make sure we store it the way + we expect it to find */ + svn_hash_sets(report->switched_paths, report->update_target, link); } - return SVN_NO_ERROR; + return APR_SUCCESS; } -/* Serf callback to create update request body bucket. */ +/* 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) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { report_context_t *report = baton; - apr_off_t offset; + body_create_baton_t *body = report->body; - offset = 0; - apr_file_seek(report->body_file, APR_SET, &offset); + 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); + *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; } @@ -2714,7 +2339,8 @@ create_update_report_body(serf_bucket_t **body_bkt, static svn_error_t * setup_update_report_headers(serf_bucket_t *headers, void *baton, - apr_pool_t *pool) + apr_pool_t *pool /* request pool */, + apr_pool_t *scratch_pool) { report_context_t *report = baton; @@ -2732,368 +2358,347 @@ setup_update_report_headers(serf_bucket_t *headers, return SVN_NO_ERROR; } +/* Baton for update_delay_handler */ +typedef struct update_delay_baton_t +{ + report_context_t *report; + svn_spillbuf_t *spillbuf; + svn_ra_serf__response_handler_t inner_handler; + void *inner_handler_baton; +} update_delay_baton_t; + +/* Helper for update_delay_handler() and process_pending() to + call UDB->INNER_HANDLER with buffer pointed by DATA. */ static svn_error_t * -finish_report(void *report_baton, - apr_pool_t *pool) +process_buffer(update_delay_baton_t *udb, + serf_request_t *request, + const void *data, + apr_size_t len, + svn_boolean_t at_eof, + serf_bucket_alloc_t *alloc, + apr_pool_t *pool) { - report_context_t *report = report_baton; - svn_ra_serf__session_t *sess = report->sess; - svn_ra_serf__handler_t *handler; - svn_ra_serf__xml_parser_t *parser_ctx; - const char *report_target; - svn_stringbuf_t *buf = NULL; - apr_pool_t *iterpool = svn_pool_create(pool); + serf_bucket_t *tmp_bucket; svn_error_t *err; - apr_interval_time_t waittime_left = sess->timeout; - svn_xml_make_close_tag(&buf, iterpool, "S:update-report"); - SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, - NULL, iterpool)); + /* ### This code (and the eagain bucket code) can probably be + ### simplified by using a bit of aggregate bucket magic. + ### See mail from Ivan to dev@s.a.o. */ + if (at_eof) + { + tmp_bucket = serf_bucket_simple_create(data, len, NULL, NULL, + alloc); + } + else + { + tmp_bucket = svn_ra_serf__create_bucket_with_eagain(data, len, + alloc); + } - /* We need to flush the file, make it unbuffered (so that it can be - * zero-copied via mmap), and reset the position before attempting to - * deliver the file. - * - * N.B. If we have APR 1.3+, we can unbuffer the file to let us use mmap - * and zero-copy the PUT body. However, on older APR versions, we can't - * check the buffer status; but serf will fall through and create a file - * bucket for us on the buffered svndiff handle. - */ - apr_file_flush(report->body_file); -#if APR_VERSION_AT_LEAST(1, 3, 0) - apr_file_buffer_set(report->body_file, NULL, 0); -#endif + /* If not at EOF create a bucket that finishes with EAGAIN, otherwise + use a standard bucket with default EOF handling */ + err = udb->inner_handler(request, tmp_bucket, + udb->inner_handler_baton, pool); - SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, NULL, pool)); + /* And free the bucket explicitly to avoid growing request allocator + storage (in a loop) */ + serf_bucket_destroy(tmp_bucket); - /* create and deliver request */ - report->path = report_target; + return svn_error_trace(err); +} - handler = apr_pcalloc(pool, sizeof(*handler)); - handler->handler_pool = pool; - handler->method = "REPORT"; - handler->path = report->path; - handler->body_delegate = create_update_report_body; - handler->body_delegate_baton = report; - handler->body_type = "text/xml"; - handler->custom_accept_encoding = TRUE; - handler->header_delegate = setup_update_report_headers; - handler->header_delegate_baton = report; - handler->conn = sess->conns[0]; - handler->session = sess; +/* Delaying wrapping reponse handler, to avoid creating too many + requests to deliver efficiently */ +static svn_error_t * +update_delay_handler(serf_request_t *request, + serf_bucket_t *response, + void *handler_baton, + apr_pool_t *scratch_pool) +{ + update_delay_baton_t *udb = handler_baton; + apr_status_t status; + apr_pool_t *iterpool = NULL; - parser_ctx = apr_pcalloc(pool, sizeof(*parser_ctx)); + if (! udb->spillbuf) + { + if (udb->report->send_all_mode) + { + /* Easy out... We only have one request, so avoid everything and just + call the inner handler. - parser_ctx->pool = pool; - parser_ctx->response_type = "update-report"; - parser_ctx->user_data = report; - parser_ctx->start = start_report; - parser_ctx->end = end_report; - parser_ctx->cdata = cdata_report; - parser_ctx->done = &report->done; + We will always get in the loop (below) on the first chunk, as only + the server can get us in true send-all mode */ - handler->response_handler = svn_ra_serf__handle_xml_parser; - handler->response_baton = parser_ctx; + return svn_error_trace(udb->inner_handler(request, response, + udb->inner_handler_baton, + scratch_pool)); + } - report->parser_ctx = parser_ctx; + while ((udb->report->num_active_fetches + udb->report->num_active_propfinds) + < REQUEST_COUNT_TO_RESUME) + { + const char *data; + apr_size_t len; + svn_boolean_t at_eof = FALSE; + svn_error_t *err; - svn_ra_serf__request_create(handler); + status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); + if (SERF_BUCKET_READ_ERROR(status)) + return svn_ra_serf__wrap_err(status, NULL); + else if (APR_STATUS_IS_EOF(status)) + udb->report->report_received = at_eof = TRUE; - /* Open the first extra connection. */ - SVN_ERR(open_connection_if_needed(sess, 0)); + if (!iterpool) + iterpool = svn_pool_create(scratch_pool); + else + svn_pool_clear(iterpool); - sess->cur_conn = 1; + if (len == 0 && !at_eof) + return svn_ra_serf__wrap_err(status, NULL); - /* Note that we may have no active GET or PROPFIND requests, yet the - processing has not been completed. This could be from a delay on the - network or because we've spooled the entire response into our "pending" - content of the XML parser. The DONE flag will get set when all the - XML content has been received *and* parsed. */ - while (!report->done - || report->num_active_fetches - || report->num_active_propfinds) + err = process_buffer(udb, request, data, len, at_eof, + serf_request_get_alloc(request), + iterpool); + + if (err && SERF_BUCKET_READ_ERROR(err->apr_err)) + return svn_error_trace(err); + else if (err && APR_STATUS_IS_EAGAIN(err->apr_err)) + { + svn_error_clear(err); /* Throttling is working ok */ + } + else if (err && (APR_STATUS_IS_EOF(err->apr_err))) + { + svn_pool_destroy(iterpool); + return svn_error_trace(err); /* No buffering was necessary */ + } + else + { + /* SERF_ERROR_WAIT_CONN should be impossible? */ + return svn_error_trace(err); + } + } + + /* Let's start using the spill infrastructure */ + udb->spillbuf = svn_spillbuf__create(SPILLBUF_BLOCKSIZE, + SPILLBUF_MAXBUFFSIZE, + udb->report->pool); + } + + /* Read everything we can to a spillbuffer */ + do { - apr_pool_t *iterpool_inner; - svn_ra_serf__list_t *done_list; - int i; - apr_status_t status; + const char *data; + apr_size_t len; - /* Note: this throws out the old ITERPOOL_INNER. */ - svn_pool_clear(iterpool); + /* ### What blocksize should we pass? */ + status = serf_bucket_read(response, 8*PARSE_CHUNK_SIZE, &data, &len); - if (sess->cancel_func) - SVN_ERR(sess->cancel_func(sess->cancel_baton)); + if (!SERF_BUCKET_READ_ERROR(status)) + SVN_ERR(svn_spillbuf__write(udb->spillbuf, data, len, scratch_pool)); + } + while (status == APR_SUCCESS); - /* We need to be careful between the outer and inner ITERPOOLs, - and what items are allocated within. */ - iterpool_inner = svn_pool_create(iterpool); + if (APR_STATUS_IS_EOF(status)) + udb->report->report_received = TRUE; - status = serf_context_run(sess->context, - SVN_RA_SERF__CONTEXT_RUN_DURATION, - iterpool_inner); + /* We handle feeding the data from the main context loop, which will be right + after processing the pending data */ - err = sess->pending_error; - sess->pending_error = SVN_NO_ERROR; + if (status) + return svn_ra_serf__wrap_err(status, NULL); + else + return SVN_NO_ERROR; +} + +/* Process pending data from the update report, if any */ +static svn_error_t * +process_pending(update_delay_baton_t *udb, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = NULL; + serf_bucket_alloc_t *alloc = NULL; + + while ((udb->report->num_active_fetches + udb->report->num_active_propfinds) + < REQUEST_COUNT_TO_RESUME) + { + const char *data; + apr_size_t len; + svn_boolean_t at_eof; + svn_error_t *err; - if (!err && handler->done && handler->server_error) + if (!iterpool) { - err = handler->server_error->error; + iterpool = svn_pool_create(scratch_pool); + alloc = serf_bucket_allocator_create(scratch_pool, NULL, NULL); } + else + svn_pool_clear(iterpool); - /* If the context duration timeout is up, we'll subtract that - duration from the total time alloted for such things. If - there's no time left, we fail with a message indicating that - the connection timed out. */ - if (APR_STATUS_IS_TIMEUP(status)) - { - svn_error_clear(err); - err = SVN_NO_ERROR; - status = 0; + SVN_ERR(svn_spillbuf__read(&data, &len, udb->spillbuf, iterpool)); - if (sess->timeout) - { - if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) - { - waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; - } - else - { - return svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, - _("Connection timed out")); - } - } - } + if (data == NULL && !udb->report->report_received) + break; + else if (data == NULL) + at_eof = TRUE; else - { - waittime_left = sess->timeout; - } + at_eof = FALSE; + + err = process_buffer(udb, NULL /* allowed? */, data, len, + at_eof, alloc, iterpool); - if (status && handler->sline.code != 200) + if (err && APR_STATUS_IS_EAGAIN(err->apr_err)) { - return svn_error_trace( - svn_error_compose_create( - svn_ra_serf__error_on_status(handler->sline, - handler->path, - handler->location), - err)); + svn_error_clear(err); /* Throttling is working */ } - SVN_ERR(err); - if (status) + else if (err && APR_STATUS_IS_EOF(err->apr_err)) { - return svn_ra_serf__wrap_err(status, _("Error retrieving REPORT")); + svn_error_clear(err); + + svn_pool_destroy(iterpool); + udb->spillbuf = NULL; + return SVN_NO_ERROR; } + else if (err) + return svn_error_trace(err); + } - /* Open extra connections if we have enough requests to send. */ - if (sess->num_conns < sess->max_connections) - SVN_ERR(open_connection_if_needed(sess, report->num_active_fetches + - report->num_active_propfinds)); + if (iterpool) + svn_pool_destroy(iterpool); - /* Prune completed file PROPFINDs. */ - done_list = report->done_propfinds; - while (done_list) - { - svn_ra_serf__list_t *next_done = done_list->next; + return SVN_NO_ERROR; +} - svn_pool_clear(iterpool_inner); +/* Process the 'update' editor report */ +static svn_error_t * +process_editor_report(report_context_t *ctx, + svn_ra_serf__handler_t *handler, + apr_pool_t *scratch_pool) +{ + svn_ra_serf__session_t *sess = ctx->sess; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + apr_interval_time_t waittime_left = sess->timeout; + update_delay_baton_t *ud; - report->num_active_propfinds--; + /* Now wrap the response handler with delay support to avoid sending + out too many requests at once */ + ud = apr_pcalloc(scratch_pool, sizeof(*ud)); + ud->report = ctx; - /* If we have some files that we won't be fetching the content - * for, ensure that we update the file with any altered props. - */ - if (report->file_propchanges_only) - { - svn_ra_serf__list_t *cur, *prev; + ud->inner_handler = handler->response_handler; + ud->inner_handler_baton = handler->response_baton; - prev = NULL; - cur = report->file_propchanges_only; + handler->response_handler = update_delay_handler; + handler->response_baton = ud; - while (cur) - { - report_info_t *item = cur->data; + /* Open the first extra connection. */ + SVN_ERR(open_connection_if_needed(sess, 0)); - if (item->propfind_handler == done_list->data) - { - break; - } + sess->cur_conn = 1; - prev = cur; - cur = cur->next; - } + /* Note that we may have no active GET or PROPFIND requests, yet the + processing has not been completed. This could be from a delay on the + network or because we've spooled the entire response into our "pending" + content of the XML parser. The DONE flag will get set when all the + XML content has been received *and* parsed. */ + while (!handler->done + || ctx->num_active_fetches + || ctx->num_active_propfinds + || !ctx->done) + { + svn_error_t *err; + int i; - /* If we found a match, set the new props and remove this - * propchange from our list. - */ - if (cur) - { - report_info_t *info = cur->data; - - if (!prev) - { - report->file_propchanges_only = cur->next; - } - else - { - prev->next = cur->next; - } - - /* If we've got cached file content for this file, - take care of the locally collected properties and - file content at once. Otherwise, just deal with - the collected properties. - - NOTE: These functions below could delete - info->dir->pool (via maybe_close_dir_chain()), - from which is allocated the list item in - report->file_propchanges_only. - */ - if (info->cached_contents) - { - SVN_ERR(handle_local_content(info, iterpool_inner)); - } - else - { - SVN_ERR(handle_propchange_only(info, iterpool_inner)); - } - } - } + svn_pool_clear(iterpool); - done_list = next_done; - } - report->done_propfinds = NULL; + err = svn_ra_serf__context_run(sess, &waittime_left, iterpool); - /* Prune completed fetches from our list. */ - done_list = report->done_fetches; - while (done_list) + if (handler->done && handler->server_error) { - report_fetch_t *done_fetch = done_list->data; - svn_ra_serf__list_t *next_done = done_list->next; - report_dir_t *cur_dir; - - /* Decrease the refcount in the parent directory of the file - whose fetch has completed. */ - cur_dir = done_fetch->info->dir; - cur_dir->ref_count--; + svn_error_clear(err); + err = svn_ra_serf__server_error_create(handler, iterpool); - /* Decrement our active fetch count. */ - report->num_active_fetches--; + SVN_ERR_ASSERT(err != NULL); + } - /* See if the parent directory of this fetched item (and - perhaps even parents of that) can be closed now. + SVN_ERR(err); - NOTE: This could delete cur_dir->pool, from which is - allocated the list item in report->done_fetches. - */ - SVN_ERR(maybe_close_dir_chain(cur_dir)); + /* If there is pending REPORT data, process it now. */ + if (ud->spillbuf) + SVN_ERR(process_pending(ud, iterpool)); - done_list = next_done; + /* Debugging purposes only! */ + for (i = 0; i < sess->num_conns; i++) + { + serf_debug__closed_conn(sess->conns[i]->bkt_alloc); } - report->done_fetches = NULL; + } - /* Prune completed directory PROPFINDs. */ - done_list = report->done_dir_propfinds; - while (done_list) - { - svn_ra_serf__list_t *next_done = done_list->next; + svn_pool_clear(iterpool); - report->num_active_propfinds--; + /* If we got a complete report, close the edit. Otherwise, abort it. */ + if (ctx->done) + SVN_ERR(ctx->editor->close_edit(ctx->editor_baton, iterpool)); + else + return svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + _("Missing update-report close tag")); - if (report->active_dir_propfinds) - { - svn_ra_serf__list_t *cur, *prev; + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} - prev = NULL; - cur = report->active_dir_propfinds; +static svn_error_t * +finish_report(void *report_baton, + apr_pool_t *pool) +{ + report_context_t *report = report_baton; + svn_ra_serf__session_t *sess = report->sess; + svn_ra_serf__handler_t *handler; + svn_ra_serf__xml_context_t *xmlctx; + const char *report_target; + svn_stringbuf_t *buf = NULL; + apr_pool_t *scratch_pool = svn_pool_create(pool); + svn_error_t *err; - while (cur) - { - report_dir_t *item = cur->data; + svn_xml_make_close_tag(&buf, scratch_pool, "S:update-report"); + SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); + SVN_ERR(svn_stream_close(report->body_template)); - if (item->propfind_handler == done_list->data) - { - break; - } + SVN_ERR(svn_ra_serf__report_resource(&report_target, sess, scratch_pool)); - prev = cur; - cur = cur->next; - } - SVN_ERR_ASSERT(cur); /* we expect to find a matching propfind! */ + xmlctx = svn_ra_serf__xml_context_create(update_ttable, + update_opened, update_closed, + update_cdata, + report, + scratch_pool); + handler = svn_ra_serf__create_expat_handler(sess, xmlctx, NULL, + scratch_pool); - /* If we found a match, set the new props and remove this - * propchange from our list. - */ - if (cur) - { - report_dir_t *cur_dir = cur->data; - - if (!prev) - { - report->active_dir_propfinds = cur->next; - } - else - { - prev->next = cur->next; - } - - /* See if this directory (and perhaps even parents of that) - can be closed now. - - NOTE: This could delete cur_dir->pool, from which is - allocated the list item in report->active_dir_propfinds. - */ - SVN_ERR(maybe_close_dir_chain(cur_dir)); - } - } + 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; + handler->header_delegate_baton = report; - done_list = next_done; - } - report->done_dir_propfinds = NULL; - - /* If the parser is paused, and the number of active requests has - dropped far enough, then resume parsing. */ - if (parser_ctx->paused - && (report->num_active_fetches + report->num_active_propfinds - < REQUEST_COUNT_TO_RESUME)) - parser_ctx->paused = FALSE; - - /* If we have not paused the parser and it looks like data MAY be - present (we can't know for sure because of the private structure), - then go process the pending content. */ - if (!parser_ctx->paused && parser_ctx->pending != NULL) - SVN_ERR(svn_ra_serf__process_pending(parser_ctx, - &report->report_received, - iterpool_inner)); + svn_ra_serf__request_create(handler); - /* Debugging purposes only! */ - for (i = 0; i < sess->num_conns; i++) - { - serf_debug__closed_conn(sess->conns[i]->bkt_alloc); - } - } + err = process_editor_report(report, handler, scratch_pool); - /* If we got a complete report, close the edit. Otherwise, abort it. */ - if (report->report_completed) + if (err) { - /* Ensure that we opened and closed our root dir and that we closed - * all of our children. */ - if (!report->closed_root && report->root_dir != NULL) - { - SVN_ERR(close_all_dirs(report->root_dir)); - } - - err = report->update_editor->close_edit(report->update_baton, iterpool); + err = svn_error_trace(err); + err = svn_error_compose_create( + err, + svn_error_trace( + report->editor->abort_edit(report->editor_baton, + scratch_pool))); } - else - { - /* Tell the editor that something failed */ - err = report->update_editor->abort_edit(report->update_baton, iterpool); - err = svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA, err, - _("Missing update-report close tag")); - } + svn_pool_destroy(scratch_pool); - svn_pool_destroy(iterpool); return svn_error_trace(err); } @@ -3162,7 +2767,7 @@ make_update_reporter(svn_ra_session_t *ra_session, update_editor, update_baton, depth, has_target, - sess->pool)); + result_pool)); update_editor = filter_editor; update_baton = filter_baton; } @@ -3170,7 +2775,6 @@ make_update_reporter(svn_ra_session_t *ra_session, report = apr_pcalloc(result_pool, sizeof(*report)); report->pool = result_pool; report->sess = sess; - report->conn = report->sess->conns[0]; report->target_rev = revision; report->ignore_ancestry = ignore_ancestry; report->send_copyfrom_args = send_copyfrom_args; @@ -3181,16 +2785,18 @@ make_update_reporter(svn_ra_session_t *ra_session, report->destination = dest_path; report->update_target = update_target; - report->update_editor = update_editor; - report->update_baton = update_baton; + report->editor = update_editor; + report->editor_baton = update_baton; report->done = FALSE; *reporter = &ra_serf_reporter; *report_baton = report; - SVN_ERR(svn_io_open_unique_file3(&report->body_file, NULL, NULL, - svn_io_file_del_on_pool_cleanup, - report->pool, scratch_pool)); + 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); if (sess->bulk_updates == svn_tristate_true) { @@ -3231,6 +2837,14 @@ make_update_reporter(svn_ra_session_t *ra_session, supports inlining properties in update editor report. */ if (sess->supports_inline_props) { + /* NOTE: both inlined properties and server->allows_bulk_update + (flag SVN_DAV_ALLOW_BULK_UPDATES) were added in 1.8.0, so + this code is never reached with a released version of + mod_dav_svn. + + Basically by default a 1.8.0 client connecting to a 1.7.x or + older server will always use bulk updates. */ + /* Inline props supported: do not use bulk updates. */ use_bulk_updates = FALSE; } @@ -3248,14 +2862,14 @@ make_update_reporter(svn_ra_session_t *ra_session, svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, "S:update-report", "xmlns:S", SVN_XML_NAMESPACE, "send-all", "true", - NULL); + SVN_VA_NULL); } else { svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, "S:update-report", "xmlns:S", SVN_XML_NAMESPACE, - NULL); + SVN_VA_NULL); /* Subversion 1.8+ servers can be told to send properties for newly added items inline even when doing a skelta response. */ make_simple_xml_tag(&buf, "S:include-props", "yes", scratch_pool); @@ -3315,8 +2929,7 @@ make_update_reporter(svn_ra_session_t *ra_session, make_simple_xml_tag(&buf, "S:depth", svn_depth_to_word(depth), scratch_pool); - SVN_ERR(svn_io_file_write_full(report->body_file, buf->data, buf->len, - NULL, scratch_pool)); + SVN_ERR(svn_stream_write(report->body_template, buf->data, &buf->len)); return SVN_NO_ERROR; } @@ -3367,7 +2980,8 @@ svn_ra_serf__do_diff(svn_ra_session_t *ra_session, SVN_ERR(make_update_reporter(ra_session, reporter, report_baton, revision, session->session_url.path, versus_url, diff_target, - depth, ignore_ancestry, text_deltas, FALSE, + depth, ignore_ancestry, text_deltas, + FALSE /* send_copyfrom */, diff_editor, diff_baton, pool, scratch_pool)); svn_pool_destroy(scratch_pool); @@ -3426,195 +3040,3 @@ svn_ra_serf__do_switch(svn_ra_session_t *ra_session, switch_editor, switch_baton, result_pool, scratch_pool); } - -/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents - * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is - * present in PROPS. - * - * Sets *FOUND_P to TRUE if file contents was successfuly fetched. - * - * Performs all temporary allocations in POOL. - */ -static svn_error_t * -try_get_wc_contents(svn_boolean_t *found_p, - svn_ra_serf__session_t *session, - apr_hash_t *props, - svn_stream_t *dst_stream, - apr_pool_t *pool) -{ - apr_hash_t *svn_props; - const char *sha1_checksum_prop; - svn_checksum_t *checksum; - svn_stream_t *wc_stream; - svn_error_t *err; - - /* No contents found by default. */ - *found_p = FALSE; - - if (!session->wc_callbacks->get_wc_contents) - { - /* No callback, nothing to do. */ - return SVN_NO_ERROR; - } - - - svn_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV); - if (!svn_props) - { - /* No properties -- therefore no checksum property -- in response. */ - return SVN_NO_ERROR; - } - - sha1_checksum_prop = svn_prop_get_value(svn_props, "sha1-checksum"); - if (sha1_checksum_prop == NULL) - { - /* No checksum property in response. */ - return SVN_NO_ERROR; - } - - SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, - sha1_checksum_prop, pool)); - - err = session->wc_callbacks->get_wc_contents( - session->wc_callback_baton, &wc_stream, checksum, pool); - - if (err) - { - svn_error_clear(err); - - /* Ignore errors for now. */ - return SVN_NO_ERROR; - } - - if (wc_stream) - { - SVN_ERR(svn_stream_copy3(wc_stream, - svn_stream_disown(dst_stream, pool), - NULL, NULL, pool)); - *found_p = TRUE; - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_ra_serf__get_file(svn_ra_session_t *ra_session, - const char *path, - svn_revnum_t revision, - svn_stream_t *stream, - svn_revnum_t *fetched_rev, - apr_hash_t **props, - apr_pool_t *pool) -{ - svn_ra_serf__session_t *session = ra_session->priv; - svn_ra_serf__connection_t *conn; - const char *fetch_url; - apr_hash_t *fetch_props; - svn_node_kind_t res_kind; - const svn_ra_serf__dav_props_t *which_props; - - /* What connection should we go on? */ - conn = session->conns[session->cur_conn]; - - /* Fetch properties. */ - - fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool); - - /* The simple case is if we want HEAD - then a GET on the fetch_url is fine. - * - * Otherwise, we need to get the baseline version for this particular - * revision and then fetch that file. - */ - if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) - { - SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev, - session, conn, - fetch_url, revision, - pool, pool)); - revision = SVN_INVALID_REVNUM; - } - /* REVISION is always SVN_INVALID_REVNUM */ - SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); - - if (props) - { - which_props = all_props; - } - else if (stream && session->wc_callbacks->get_wc_contents) - { - which_props = type_and_checksum_props; - } - else - { - which_props = check_path_props; - } - - SVN_ERR(svn_ra_serf__fetch_node_props(&fetch_props, conn, fetch_url, - SVN_INVALID_REVNUM, - which_props, - pool, pool)); - - /* Verify that resource type is not collection. */ - SVN_ERR(svn_ra_serf__get_resource_type(&res_kind, fetch_props)); - if (res_kind != svn_node_file) - { - return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, - _("Can't get text contents of a directory")); - } - - /* TODO Filter out all of our props into a usable format. */ - if (props) - { - /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props() - ### put them into POOL, so we're okay. */ - SVN_ERR(svn_ra_serf__flatten_props(props, fetch_props, - pool, pool)); - } - - if (stream) - { - svn_boolean_t found; - SVN_ERR(try_get_wc_contents(&found, session, fetch_props, stream, pool)); - - /* No contents found in the WC, let's fetch from server. */ - if (!found) - { - report_fetch_t *stream_ctx; - svn_ra_serf__handler_t *handler; - - /* Create the fetch context. */ - stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx)); - stream_ctx->target_stream = stream; - stream_ctx->sess = session; - stream_ctx->conn = conn; - stream_ctx->info = apr_pcalloc(pool, sizeof(*stream_ctx->info)); - stream_ctx->info->name = fetch_url; - - handler = apr_pcalloc(pool, sizeof(*handler)); - - handler->handler_pool = pool; - handler->method = "GET"; - handler->path = fetch_url; - handler->conn = conn; - handler->session = session; - - handler->custom_accept_encoding = TRUE; - handler->header_delegate = headers_fetch; - handler->header_delegate_baton = stream_ctx; - - handler->response_handler = handle_stream; - handler->response_baton = stream_ctx; - - handler->response_error = cancel_fetch; - handler->response_error_baton = stream_ctx; - - stream_ctx->handler = handler; - - svn_ra_serf__request_create(handler); - - SVN_ERR(svn_ra_serf__context_run_wait(&stream_ctx->done, session, pool)); - } - } - - return SVN_NO_ERROR; -} diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c index 8f6c1bb..5490dde 100644 --- a/subversion/libsvn_ra_serf/util.c +++ b/subversion/libsvn_ra_serf/util.c @@ -32,83 +32,22 @@ #include <serf.h> #include <serf_bucket_types.h> -#include <expat.h> - #include "svn_hash.h" #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_private_config.h" #include "svn_string.h" -#include "svn_xml.h" #include "svn_props.h" #include "svn_dirent_uri.h" #include "../libsvn_ra/ra_loader.h" #include "private/svn_dep_compat.h" #include "private/svn_fspath.h" -#include "private/svn_subr_private.h" #include "private/svn_auth_private.h" #include "private/svn_cert.h" #include "ra_serf.h" - -/* Fix for older expat 1.95.x's that do not define - * XML_STATUS_OK/XML_STATUS_ERROR - */ -#ifndef XML_STATUS_OK -#define XML_STATUS_OK 1 -#define XML_STATUS_ERROR 0 -#endif - -#ifndef XML_VERSION_AT_LEAST -#define XML_VERSION_AT_LEAST(major,minor,patch) \ -(((major) < XML_MAJOR_VERSION) \ - || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \ - || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \ - (patch) <= XML_MICRO_VERSION)) -#endif /* APR_VERSION_AT_LEAST */ - -#if XML_VERSION_AT_LEAST(1, 95, 8) -#define EXPAT_HAS_STOPPARSER -#endif - -/* Read/write chunks of this size into the spillbuf. */ -#define PARSE_CHUNK_SIZE 8000 - -/* We will store one megabyte in memory, before switching to store content - into a temporary file. */ -#define SPILL_SIZE 1000000 - - -/* This structure records pending data for the parser in memory blocks, - and possibly into a temporary file if "too much" content arrives. */ -struct svn_ra_serf__pending_t { - /* The spillbuf where we record the pending data. */ - svn_spillbuf_t *buf; - - /* This flag is set when the network has reached EOF. The PENDING - processing can then properly detect when parsing has completed. */ - svn_boolean_t network_eof; -}; - -#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \ - && svn_spillbuf__get_size((p)->buf) != 0) - - -struct expat_ctx_t { - svn_ra_serf__xml_context_t *xmlctx; - XML_Parser parser; - svn_ra_serf__handler_t *handler; - - svn_error_t *inner_error; - - /* Do not use this pool for allocation. It is merely recorded for running - the cleanup handler. */ - apr_pool_t *cleanup_pool; -}; - - static const apr_uint32_t serf_failure_map[][2] = { { SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID }, @@ -192,6 +131,7 @@ construct_realm(svn_ra_serf__session_t *session, static char * convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool) { + const char *cn = svn_hash_gets(org, "CN"); const char *org_unit = svn_hash_gets(org, "OU"); const char *org_name = svn_hash_gets(org, "O"); const char *locality = svn_hash_gets(org, "L"); @@ -200,6 +140,12 @@ convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool) const char *email = svn_hash_gets(org, "E"); svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool); + if (cn) + { + svn_stringbuf_appendcstr(buf, cn); + svn_stringbuf_appendcstr(buf, ", "); + } + if (org_unit) { svn_stringbuf_appendcstr(buf, org_unit); @@ -285,7 +231,6 @@ ssl_server_cert(void *baton, int failures, ### This should really be handled by serf, which should pass an error for this case, but that has backwards compatibility issues. */ apr_array_header_t *san; - svn_boolean_t found_san_entry = FALSE; svn_boolean_t found_matching_hostname = FALSE; svn_string_t *actual_hostname = svn_string_create(conn->session->session_url.hostname, scratch_pool); @@ -293,11 +238,16 @@ ssl_server_cert(void *baton, int failures, serf_cert = serf_ssl_cert_certificate(cert, scratch_pool); san = svn_hash_gets(serf_cert, "subjectAltName"); - /* Try to find matching server name via subjectAltName first... */ - if (san) + /* Match server certificate CN with the hostname of the server iff + * we didn't find any subjectAltName fields and try to match them. + * Per RFC 2818 they are authoritative if present and CommonName + * should be ignored. NOTE: This isn't 100% correct since serf + * only loads the subjectAltName hash with dNSNames, technically + * we should ignore the CommonName if any subjectAltName entry + * exists even if it is one we don't support. */ + if (san && san->nelts > 0) { int i; - found_san_entry = san->nelts > 0; for (i = 0; i < san->nelts; i++) { const char *s = APR_ARRAY_IDX(san, i, const char*); @@ -310,12 +260,7 @@ ssl_server_cert(void *baton, int failures, } } } - - /* Match server certificate CN with the hostname of the server iff - * we didn't find any subjectAltName fields and try to match them. - * Per RFC 2818 they are authoritative if present and CommonName - * should be ignored. */ - if (!found_matching_hostname && !found_san_entry) + else { const char *hostname = NULL; @@ -368,11 +313,11 @@ ssl_server_cert(void *baton, int failures, { svn_error_t *err; - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, &cert_info); - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_FAILURES, &svn_failures); @@ -382,13 +327,13 @@ ssl_server_cert(void *baton, int failures, err = svn_auth_first_credentials(&creds, &state, SVN_AUTH_CRED_SSL_SERVER_AUTHORITY, realmstring, - conn->session->wc_callbacks->auth_baton, + conn->session->auth_baton, scratch_pool); - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL); - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL); if (err) @@ -415,11 +360,11 @@ ssl_server_cert(void *baton, int failures, return APR_SUCCESS; } - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_FAILURES, &svn_failures); - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, &cert_info); @@ -428,7 +373,7 @@ ssl_server_cert(void *baton, int failures, SVN_ERR(svn_auth_first_credentials(&creds, &state, SVN_AUTH_CRED_SSL_SERVER_TRUST, realmstring, - conn->session->wc_callbacks->auth_baton, + conn->session->auth_baton, scratch_pool)); if (creds) { @@ -449,7 +394,7 @@ ssl_server_cert(void *baton, int failures, } } - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL); /* Are there non accepted failures left? */ @@ -622,6 +567,7 @@ accept_response(serf_request_t *request, void *acceptor_baton, apr_pool_t *pool) { + /* svn_ra_serf__handler_t *handler = acceptor_baton; */ serf_bucket_t *c; serf_bucket_alloc_t *bkt_alloc; @@ -639,6 +585,7 @@ accept_head(serf_request_t *request, void *acceptor_baton, apr_pool_t *pool) { + /* svn_ra_serf__handler_t *handler = acceptor_baton; */ serf_bucket_t *response; response = accept_response(request, stream, acceptor_baton, pool); @@ -656,7 +603,7 @@ connection_closed(svn_ra_serf__connection_t *conn, { if (why) { - return svn_error_wrap_apr(why, NULL); + return svn_ra_serf__wrap_err(why, NULL); } if (conn->session->using_ssl) @@ -701,7 +648,7 @@ handle_client_cert(void *data, &conn->ssl_client_auth_state, SVN_AUTH_CRED_SSL_CLIENT_CERT, realm, - session->wc_callbacks->auth_baton, + session->auth_baton, pool)); } else @@ -753,7 +700,7 @@ handle_client_cert_pw(void *data, &conn->ssl_client_pw_auth_state, SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, cert_path, - session->wc_callbacks->auth_baton, + session->auth_baton, pool)); } else @@ -804,6 +751,9 @@ apr_status_t svn_ra_serf__handle_client_cert_pw(void *data, * * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header. * + * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers + * to request. + * * REQUEST_POOL should live for the duration of the request. Serf will * construct this and provide it to the request_setup callback, so we * should just use that one. @@ -816,6 +766,7 @@ setup_serf_req(serf_request_t *request, const char *method, const char *url, serf_bucket_t *body_bkt, const char *content_type, const char *accept_encoding, + svn_boolean_t dav_headers, apr_pool_t *request_pool, apr_pool_t *scratch_pool) { @@ -882,12 +833,86 @@ setup_serf_req(serf_request_t *request, serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding); } - /* These headers need to be sent with every request; see issue #3255 - ("mod_dav_svn does not pass client capabilities to start-commit - hooks") for why. */ - serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH); - serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO); - serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS); + /* These headers need to be sent with every request that might need + capability processing (e.g. during commit, reports, etc.), see + issue #3255 ("mod_dav_svn does not pass client capabilities to + start-commit hooks") for why. + + Some request types like GET/HEAD/PROPFIND are unaware of capability + handling; and in some cases the responses can even be cached by + proxies, so we don't have to send these hearders there. */ + if (dav_headers) + { + serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH); + serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO); + serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__context_run(svn_ra_serf__session_t *sess, + apr_interval_time_t *waittime_left, + apr_pool_t *scratch_pool) +{ + apr_status_t status; + svn_error_t *err; + assert(sess->pending_error == SVN_NO_ERROR); + + if (sess->cancel_func) + SVN_ERR(sess->cancel_func(sess->cancel_baton)); + + status = serf_context_run(sess->context, + SVN_RA_SERF__CONTEXT_RUN_DURATION, + scratch_pool); + + err = sess->pending_error; + sess->pending_error = SVN_NO_ERROR; + + /* If the context duration timeout is up, we'll subtract that + duration from the total time alloted for such things. If + there's no time left, we fail with a message indicating that + the connection timed out. */ + if (APR_STATUS_IS_TIMEUP(status)) + { + status = 0; + + if (sess->timeout) + { + if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) + { + *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; + } + else + { + return + svn_error_compose_create( + err, + svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, + _("Connection timed out"))); + } + } + } + else + { + *waittime_left = sess->timeout; + } + + SVN_ERR(err); + if (status) + { + /* ### This omits SVN_WARNING, and possibly relies on the fact that + ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */ + if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST) + { + /* apr can't translate subversion errors to text */ + SVN_ERR_W(svn_error_create(status, NULL, NULL), + _("Error running context")); + } + + return svn_ra_serf__wrap_err(status, _("Error running context")); + } return SVN_NO_ERROR; } @@ -905,63 +930,11 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done, iterpool = svn_pool_create(scratch_pool); while (!*done) { - apr_status_t status; - svn_error_t *err; int i; svn_pool_clear(iterpool); - if (sess->cancel_func) - SVN_ERR((*sess->cancel_func)(sess->cancel_baton)); - - status = serf_context_run(sess->context, - SVN_RA_SERF__CONTEXT_RUN_DURATION, - iterpool); - - err = sess->pending_error; - sess->pending_error = SVN_NO_ERROR; - - /* If the context duration timeout is up, we'll subtract that - duration from the total time alloted for such things. If - there's no time left, we fail with a message indicating that - the connection timed out. */ - if (APR_STATUS_IS_TIMEUP(status)) - { - status = 0; - - if (sess->timeout) - { - if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) - { - waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; - } - else - { - return - svn_error_compose_create( - err, - svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, - _("Connection timed out"))); - } - } - } - else - { - waittime_left = sess->timeout; - } - - SVN_ERR(err); - if (status) - { - if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST) - { - /* apr can't translate subversion errors to text */ - SVN_ERR_W(svn_error_create(status, NULL, NULL), - _("Error running context")); - } - - return svn_ra_serf__wrap_err(status, _("Error running context")); - } + SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool)); /* Debugging purposes only! */ for (i = 0; i < sess->num_conns; i++) @@ -974,6 +947,22 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done, return SVN_NO_ERROR; } +/* Ensure that a handler is no longer scheduled on the connection. + + Eventually serf will have a reliable way to cancel existing requests, + but currently it doesn't even have a way to relyable identify a request + after rescheduling, for auth reasons. + + So the only thing we can do today is reset the connection, which + will cancel all outstanding requests and prepare the connection + for re-use. +*/ +static void +svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler) +{ + serf_connection_reset(handler->conn->conn); + handler->scheduled = FALSE; +} svn_error_t * svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler, @@ -988,130 +977,18 @@ svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler, err = svn_ra_serf__context_run_wait(&handler->done, handler->session, scratch_pool); - /* A callback invocation has been canceled. In this simple case of - context_run_one, we can keep the ra-session operational by resetting - the connection. - - If we don't do this, the next context run will notice that the connection - is still in the error state and will just return SVN_ERR_CEASE_INVOCATION - (=the last error for the connection) again */ - if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) - { - apr_status_t status = serf_connection_reset(handler->conn->conn); - - if (status) - err = svn_error_compose_create(err, - svn_ra_serf__wrap_err(status, NULL)); - } - - if (handler->server_error) + if (handler->scheduled) { - err = svn_error_compose_create(err, handler->server_error->error); - handler->server_error = NULL; + /* We reset the connection (breaking pipelining, etc.), as + if we didn't the next data would still be handled by this handler, + which is done as far as our caller is concerned. */ + svn_ra_serf__unschedule_handler(handler); } return svn_error_trace(err); } -/* - * Expat callback invoked on a start element tag for an error response. - */ -static svn_error_t * -start_error(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - const char **attrs, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (!ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "error") == 0) - { - ctx->in_error = TRUE; - } - else if (ctx->in_error && strcmp(name.name, "human-readable") == 0) - { - const char *err_code; - - err_code = svn_xml_get_attr_value("errcode", attrs); - if (err_code) - { - apr_int64_t val; - - SVN_ERR(svn_cstring_atoi64(&val, err_code)); - ctx->error->apr_err = (apr_status_t)val; - } - - /* If there's no error code provided, or if the provided code is - 0 (which can happen sometimes depending on how the error is - constructed on the server-side), just pick a generic error - code to run with. */ - if (! ctx->error->apr_err) - { - ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; - } - - /* Start collecting cdata. */ - svn_stringbuf_setempty(ctx->cdata); - ctx->collect_cdata = TRUE; - } - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on an end element tag for a PROPFIND response. - */ -static svn_error_t * -end_error(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "error") == 0) - { - ctx->in_error = FALSE; - } - if (ctx->in_error && strcmp(name.name, "human-readable") == 0) - { - /* On the server dav_error_response_tag() will add a leading - and trailing newline if DEBUG_CR is defined in mod_dav.h, - so remove any such characters here. */ - svn_stringbuf_strip_whitespace(ctx->cdata); - - ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data, - ctx->cdata->len); - ctx->collect_cdata = FALSE; - } - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on CDATA elements in an error response. - * - * This callback can be called multiple times. - */ -static svn_error_t * -cdata_error(svn_ra_serf__xml_parser_t *parser, - const char *data, - apr_size_t len, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (ctx->collect_cdata) - { - svn_stringbuf_appendbytes(ctx->cdata, data, len); - } - - return SVN_NO_ERROR; -} static apr_status_t @@ -1131,28 +1008,7 @@ drain_bucket(serf_bucket_t *bucket) } -static svn_ra_serf__server_error_t * -begin_error_parsing(svn_ra_serf__xml_start_element_t start, - svn_ra_serf__xml_end_element_t end, - svn_ra_serf__xml_cdata_chunk_handler_t cdata, - apr_pool_t *result_pool) -{ - svn_ra_serf__server_error_t *server_err; - - server_err = apr_pcalloc(result_pool, sizeof(*server_err)); - server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL); - server_err->contains_precondition_error = FALSE; - server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool); - server_err->collect_cdata = FALSE; - server_err->parser.pool = server_err->error->pool; - server_err->parser.user_data = server_err; - server_err->parser.start = start; - server_err->parser.end = end; - server_err->parser.cdata = cdata; - server_err->parser.ignore_errors = TRUE; - - return server_err; -} + /* Implements svn_ra_serf__response_handler_t */ svn_error_t * @@ -1241,7 +1097,7 @@ svn_ra_serf__expect_empty_body(serf_request_t *request, const char *val; /* This function is just like handle_multistatus_only() except for the - XML parsing callbacks. We want to look for the human-readable element. */ + XML parsing callbacks. We want to look for the -readable element. */ /* We should see this just once, in order to initialize SERVER_ERROR. At that point, the core error processing will take over. If we choose @@ -1251,21 +1107,22 @@ svn_ra_serf__expect_empty_body(serf_request_t *request, hdrs = serf_bucket_response_get_headers(response); val = serf_bucket_headers_get(hdrs, "Content-Type"); - if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) + if (val + && (handler->sline.code < 200 || handler->sline.code >= 300) + && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) { svn_ra_serf__server_error_t *server_err; - server_err = begin_error_parsing(start_error, end_error, cdata_error, - handler->handler_pool); - - /* Get the parser to set our DONE flag. */ - server_err->parser.done = &handler->done; + SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler, + FALSE, + handler->handler_pool, + handler->handler_pool)); handler->server_error = server_err; } else { - /* The body was not text/xml, so we don't know what to do with it. + /* The body was not text/xml, or we got a success code. Toss anything that arrives. */ handler->discard_body = TRUE; } @@ -1277,631 +1134,6 @@ svn_ra_serf__expect_empty_body(serf_request_t *request, } -/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric - status code into *STATUS_CODE_OUT. Ignores leading whitespace. */ -static svn_error_t * -parse_dav_status(int *status_code_out, svn_stringbuf_t *buf, - apr_pool_t *scratch_pool) -{ - svn_error_t *err; - const char *token; - char *tok_status; - svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool); - - svn_stringbuf_strip_whitespace(temp_buf); - token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status); - if (token) - token = apr_strtok(NULL, " \t\r\n", &tok_status); - if (!token) - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Malformed DAV:status CDATA '%s'"), - buf->data); - err = svn_cstring_atoi(status_code_out, token); - if (err) - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err, - _("Malformed DAV:status CDATA '%s'"), - buf->data); - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on a start element tag for a 207 response. - */ -static svn_error_t * -start_207(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - const char **attrs, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (!ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "multistatus") == 0) - { - ctx->in_error = TRUE; - } - else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0) - { - /* Start collecting cdata. */ - svn_stringbuf_setempty(ctx->cdata); - ctx->collect_cdata = TRUE; - } - else if (ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "status") == 0) - { - /* Start collecting cdata. */ - svn_stringbuf_setempty(ctx->cdata); - ctx->collect_cdata = TRUE; - } - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on an end element tag for a 207 response. - */ -static svn_error_t * -end_207(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "multistatus") == 0) - { - ctx->in_error = FALSE; - } - if (ctx->in_error && strcmp(name.name, "responsedescription") == 0) - { - /* Remove leading newline added by DEBUG_CR on server */ - svn_stringbuf_strip_whitespace(ctx->cdata); - - ctx->collect_cdata = FALSE; - ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data, - ctx->cdata->len); - if (ctx->contains_precondition_error) - ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH; - else - ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; - } - else if (ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "status") == 0) - { - int status_code; - - ctx->collect_cdata = FALSE; - - SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool)); - if (status_code == 412) - ctx->contains_precondition_error = TRUE; - } - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on CDATA elements in a 207 response. - * - * This callback can be called multiple times. - */ -static svn_error_t * -cdata_207(svn_ra_serf__xml_parser_t *parser, - const char *data, - apr_size_t len, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (ctx->collect_cdata) - { - svn_stringbuf_appendbytes(ctx->cdata, data, len); - } - - return SVN_NO_ERROR; -} - -/* Implements svn_ra_serf__response_handler_t */ -svn_error_t * -svn_ra_serf__handle_multistatus_only(serf_request_t *request, - serf_bucket_t *response, - void *baton, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__handler_t *handler = baton; - - /* This function is just like expect_empty_body() except for the - XML parsing callbacks. We are looking for very limited pieces of - the multistatus response. */ - - /* We should see this just once, in order to initialize SERVER_ERROR. - At that point, the core error processing will take over. If we choose - not to parse an error, then we'll never return here (because we - change the response handler). */ - SVN_ERR_ASSERT(handler->server_error == NULL); - - { - serf_bucket_t *hdrs; - const char *val; - - hdrs = serf_bucket_response_get_headers(response); - val = serf_bucket_headers_get(hdrs, "Content-Type"); - if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) - { - svn_ra_serf__server_error_t *server_err; - - server_err = begin_error_parsing(start_207, end_207, cdata_207, - handler->handler_pool); - - /* Get the parser to set our DONE flag. */ - server_err->parser.done = &handler->done; - - handler->server_error = server_err; - } - else - { - /* The body was not text/xml, so we don't know what to do with it. - Toss anything that arrives. */ - handler->discard_body = TRUE; - } - } - - /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it - to call the response handler again. That will start up the XML parsing, - or it will be dropped on the floor (per the decision above). */ - return SVN_NO_ERROR; -} - - -/* Conforms to Expat's XML_StartElementHandler */ -static void -start_xml(void *userData, const char *raw_name, const char **attrs) -{ - svn_ra_serf__xml_parser_t *parser = userData; - svn_ra_serf__dav_props_t name; - apr_pool_t *scratch_pool; - svn_error_t *err; - - if (parser->error) - return; - - if (!parser->state) - svn_ra_serf__xml_push_state(parser, 0); - - /* ### get a real scratch_pool */ - scratch_pool = parser->state->pool; - - svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool); - - svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name); - - err = parser->start(parser, name, attrs, scratch_pool); - if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) - err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - - parser->error = err; -} - - -/* Conforms to Expat's XML_EndElementHandler */ -static void -end_xml(void *userData, const char *raw_name) -{ - svn_ra_serf__xml_parser_t *parser = userData; - svn_ra_serf__dav_props_t name; - svn_error_t *err; - apr_pool_t *scratch_pool; - - if (parser->error) - return; - - /* ### get a real scratch_pool */ - scratch_pool = parser->state->pool; - - svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name); - - err = parser->end(parser, name, scratch_pool); - if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) - err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - - parser->error = err; -} - - -/* Conforms to Expat's XML_CharacterDataHandler */ -static void -cdata_xml(void *userData, const char *data, int len) -{ - svn_ra_serf__xml_parser_t *parser = userData; - svn_error_t *err; - apr_pool_t *scratch_pool; - - if (parser->error) - return; - - if (!parser->state) - svn_ra_serf__xml_push_state(parser, 0); - - /* ### get a real scratch_pool */ - scratch_pool = parser->state->pool; - - err = parser->cdata(parser, data, len, scratch_pool); - if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) - err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - - parser->error = err; -} - -/* Flip the requisite bits in CTX to indicate that processing of the - response is complete, adding the current "done item" to the list of - completed items. */ -static void -add_done_item(svn_ra_serf__xml_parser_t *ctx) -{ - /* Make sure we don't add to DONE_LIST twice. */ - if (!*ctx->done) - { - *ctx->done = TRUE; - if (ctx->done_list) - { - ctx->done_item->data = ctx->user_data; - ctx->done_item->next = *ctx->done_list; - *ctx->done_list = ctx->done_item; - } - } -} - - -static svn_error_t * -write_to_pending(svn_ra_serf__xml_parser_t *ctx, - const char *data, - apr_size_t len, - apr_pool_t *scratch_pool) -{ - if (ctx->pending == NULL) - { - ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending)); - ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE, - SPILL_SIZE, - ctx->pool); - } - - /* Copy the data into one or more chunks in the spill buffer. */ - return svn_error_trace(svn_spillbuf__write(ctx->pending->buf, - data, len, - scratch_pool)); -} - - -static svn_error_t * -inject_to_parser(svn_ra_serf__xml_parser_t *ctx, - const char *data, - apr_size_t len, - const serf_status_line *sl) -{ - int xml_status; - - xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0); - - if (! ctx->ignore_errors) - { - SVN_ERR(ctx->error); - - if (xml_status != XML_STATUS_OK) - { - if (sl == NULL) - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("XML parsing failed")); - - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("XML parsing failed: (%d %s)"), - sl->code, sl->reason); - } - } - - return SVN_NO_ERROR; -} - -/* Apr pool cleanup handler to release an XML_Parser in success and error - conditions */ -static apr_status_t -xml_parser_cleanup(void *baton) -{ - XML_Parser *xmlp = baton; - - if (*xmlp) - { - (void) XML_ParserFree(*xmlp); - *xmlp = NULL; - } - - return APR_SUCCESS; -} - -/* Limit the amount of pending content to parse at once to < 100KB per - iteration. This number is chosen somewhat arbitrarely. Making it lower - will have a drastical negative impact on performance, whereas increasing it - increases the risk for connection timeouts. - */ -#define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5 - -svn_error_t * -svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser, - svn_boolean_t *network_eof, - apr_pool_t *scratch_pool) -{ - svn_boolean_t pending_empty = FALSE; - apr_size_t cur_read = 0; - - /* Fast path exit: already paused, nothing to do, or already done. */ - if (parser->paused || parser->pending == NULL || *parser->done) - { - *network_eof = parser->pending ? parser->pending->network_eof : FALSE; - return SVN_NO_ERROR; - } - - /* Parsing the pending conten in the spillbuf will result in many disc i/o - operations. This can be so slow that we don't run the network event - processing loop often enough, resulting in timed out connections. - - So we limit the amounts of bytes parsed per iteration. - */ - while (cur_read < PENDING_TO_PARSE) - { - const char *data; - apr_size_t len; - - /* Get a block of content, stopping the loop when we run out. */ - SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf, - scratch_pool)); - if (data) - { - /* Inject the content into the XML parser. */ - SVN_ERR(inject_to_parser(parser, data, len, NULL)); - - /* If the XML parsing callbacks paused us, then we're done for now. */ - if (parser->paused) - break; - - cur_read += len; - } - else - { - /* The buffer is empty. */ - pending_empty = TRUE; - break; - } - } - - /* If the PENDING structures are empty *and* we consumed all content from - the network, then we're completely done with the parsing. */ - if (pending_empty && - parser->pending->network_eof) - { - int xml_status; - SVN_ERR_ASSERT(parser->xmlp != NULL); - - /* Tell the parser that no more content will be parsed. */ - xml_status = XML_Parse(parser->xmlp, NULL, 0, 1); - - apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup); - parser->xmlp = NULL; - - if (! parser->ignore_errors) - { - SVN_ERR(parser->error); - - if (xml_status != XML_STATUS_OK) - { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("XML parsing failed")); - } - } - - add_done_item(parser); - } - - *network_eof = parser->pending ? parser->pending->network_eof : FALSE; - - return SVN_NO_ERROR; -} -#undef PENDING_TO_PARSE - - -/* ### this is still broken conceptually. just shifting incrementally... */ -static svn_error_t * -handle_server_error(serf_request_t *request, - serf_bucket_t *response, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t server_err = { 0 }; - serf_bucket_t *hdrs; - const char *val; - apr_status_t err; - - hdrs = serf_bucket_response_get_headers(response); - val = serf_bucket_headers_get(hdrs, "Content-Type"); - if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) - { - /* ### we should figure out how to reuse begin_error_parsing */ - - server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL); - server_err.contains_precondition_error = FALSE; - server_err.cdata = svn_stringbuf_create_empty(scratch_pool); - server_err.collect_cdata = FALSE; - server_err.parser.pool = server_err.error->pool; - server_err.parser.user_data = &server_err; - server_err.parser.start = start_error; - server_err.parser.end = end_error; - server_err.parser.cdata = cdata_error; - server_err.parser.done = &server_err.done; - server_err.parser.ignore_errors = TRUE; - - /* We don't care about any errors except for SERVER_ERR.ERROR */ - svn_error_clear(svn_ra_serf__handle_xml_parser(request, - response, - &server_err.parser, - scratch_pool)); - - /* ### checking DONE is silly. the above only parses whatever has - ### been received at the network interface. totally wrong. but - ### it is what we have for now (maintaining historical code), - ### until we fully migrate. */ - if (server_err.done && server_err.error->apr_err == APR_SUCCESS) - { - svn_error_clear(server_err.error); - server_err.error = SVN_NO_ERROR; - } - - return svn_error_trace(server_err.error); - } - - /* The only error that we will return is from the XML response body. - Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to - surface. */ - err = drain_bucket(response); - if (err && !SERF_BUCKET_READ_ERROR(err)) - return svn_ra_serf__wrap_err(err, NULL); - - return SVN_NO_ERROR; -} - - -/* Implements svn_ra_serf__response_handler_t */ -svn_error_t * -svn_ra_serf__handle_xml_parser(serf_request_t *request, - serf_bucket_t *response, - void *baton, - apr_pool_t *pool) -{ - serf_status_line sl; - apr_status_t status; - svn_ra_serf__xml_parser_t *ctx = baton; - svn_error_t *err; - - /* ### get the HANDLER rather than fetching this. */ - status = serf_bucket_response_status(response, &sl); - if (SERF_BUCKET_READ_ERROR(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } - - /* Woo-hoo. Nothing here to see. */ - if (sl.code == 404 && !ctx->ignore_errors) - { - err = handle_server_error(request, response, pool); - - if (err && APR_STATUS_IS_EOF(err->apr_err)) - add_done_item(ctx); - - return svn_error_trace(err); - } - - if (!ctx->xmlp) - { - ctx->xmlp = XML_ParserCreate(NULL); - apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup, - apr_pool_cleanup_null); - XML_SetUserData(ctx->xmlp, ctx); - XML_SetElementHandler(ctx->xmlp, start_xml, end_xml); - if (ctx->cdata) - { - XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml); - } - } - - while (1) - { - const char *data; - apr_size_t len; - - status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); - if (SERF_BUCKET_READ_ERROR(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } - - /* Note: once the callbacks invoked by inject_to_parser() sets the - PAUSED flag, then it will not be cleared. write_to_pending() will - only save the content. Logic outside of serf_context_run() will - clear that flag, as appropriate, along with processing the - content that we have placed into the PENDING buffer. - - We want to save arriving content into the PENDING structures if - the parser has been paused, or we already have data in there (so - the arriving data is appended, rather than injected out of order) */ - if (ctx->paused || HAS_PENDING_DATA(ctx->pending)) - { - err = write_to_pending(ctx, data, len, pool); - } - else - { - err = inject_to_parser(ctx, data, len, &sl); - if (err) - { - /* Should have no errors if IGNORE_ERRORS is set. */ - SVN_ERR_ASSERT(!ctx->ignore_errors); - } - } - if (err) - { - SVN_ERR_ASSERT(ctx->xmlp != NULL); - - apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup); - add_done_item(ctx); - return svn_error_trace(err); - } - - if (APR_STATUS_IS_EAGAIN(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } - - if (APR_STATUS_IS_EOF(status)) - { - if (ctx->pending != NULL) - ctx->pending->network_eof = TRUE; - - /* We just hit the end of the network content. If we have nothing - in the PENDING structures, then we're completely done. */ - if (!HAS_PENDING_DATA(ctx->pending)) - { - int xml_status; - SVN_ERR_ASSERT(ctx->xmlp != NULL); - - xml_status = XML_Parse(ctx->xmlp, NULL, 0, 1); - - apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup); - - if (! ctx->ignore_errors) - { - SVN_ERR(ctx->error); - - if (xml_status != XML_STATUS_OK) - { - return svn_error_create( - SVN_ERR_XML_MALFORMED, NULL, - _("The XML response contains invalid XML")); - } - } - - add_done_item(ctx); - } - - return svn_ra_serf__wrap_err(status, NULL); - } - - /* feed me! */ - } - /* not reached */ -} - - apr_status_t svn_ra_serf__credentials_callback(char **username, char **password, serf_request_t *request, void *baton, @@ -1927,7 +1159,7 @@ svn_ra_serf__credentials_callback(char **username, char **password, &session->auth_state, SVN_AUTH_CRED_SIMPLE, realm, - session->wc_callbacks->auth_baton, + session->auth_baton, session->pool); } else @@ -2008,6 +1240,8 @@ handle_response(serf_request_t *request, if (!response) { /* Uh-oh. Our connection died. */ + handler->scheduled = FALSE; + if (handler->response_error) { /* Give a handler chance to prevent request requeue. */ @@ -2126,10 +1360,7 @@ handle_response(serf_request_t *request, } handler->conn->last_status_code = handler->sline.code; - if (handler->sline.code == 405 - || handler->sline.code == 408 - || handler->sline.code == 409 - || handler->sline.code >= 500) + if (handler->sline.code >= 400) { /* 405 Method Not allowed. 408 Request Timeout @@ -2144,35 +1375,22 @@ handle_response(serf_request_t *request, { svn_ra_serf__server_error_t *server_err; - server_err = begin_error_parsing(start_error, end_error, cdata_error, - handler->handler_pool); - /* Get the parser to set our DONE flag. */ - server_err->parser.done = &handler->done; + SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler, + FALSE, + handler->handler_pool, + handler->handler_pool)); handler->server_error = server_err; } else { handler->discard_body = TRUE; - - if (!handler->session->pending_error) - { - apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; - - /* 405 == Method Not Allowed (Occurs when trying to lock a working - copy path which no longer exists at HEAD in the repository. */ - if (handler->sline.code == 405 - && strcmp(handler->method, "LOCK") == 0) - apr_err = SVN_ERR_FS_OUT_OF_DATE; - - handler->session->pending_error = - svn_error_createf(apr_err, NULL, - _("%s request on '%s' failed: %d %s"), - handler->method, handler->path, - handler->sline.code, handler->sline.reason); - } } } + else if (handler->sline.code <= 199) + { + handler->discard_body = TRUE; + } /* Stop processing the above, on every packet arrival. */ handler->reading_body = TRUE; @@ -2184,13 +1402,6 @@ handle_response(serf_request_t *request, { *serf_status = drain_bucket(response); - /* If the handler hasn't set done (which it shouldn't have) and - we now have the EOF, go ahead and set it so that we can stop - our context loops. - */ - if (!handler->done && APR_STATUS_IS_EOF(*serf_status)) - handler->done = TRUE; - return SVN_NO_ERROR; } @@ -2198,50 +1409,12 @@ handle_response(serf_request_t *request, that now. */ if (handler->server_error != NULL) { - err = svn_ra_serf__handle_xml_parser(request, response, - &handler->server_error->parser, - scratch_pool); - - /* If we do not receive an error or it is a non-transient error, return - immediately. - - APR_EOF will be returned when parsing is complete. - - APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through - parsing and the network has no more data right now. If we receive that, - clear the error and return - allowing serf to wait for more data. - */ - if (!err || SERF_BUCKET_READ_ERROR(err->apr_err)) - return svn_error_trace(err); - - if (!APR_STATUS_IS_EOF(err->apr_err)) - { - *serf_status = err->apr_err; - svn_error_clear(err); - return SVN_NO_ERROR; - } - - /* Clear the EOF. We don't need it. */ - svn_error_clear(err); - - /* If the parsing is done, and we did not extract an error, then - simply toss everything, and anything else that might arrive. - The higher-level code will need to investigate HANDLER->SLINE, - as we have no further information for them. */ - if (handler->done - && handler->server_error->error->apr_err == APR_SUCCESS) - { - svn_error_clear(handler->server_error->error); - - /* Stop parsing for a server error. */ - handler->server_error = NULL; - - /* If anything arrives after this, then just discard it. */ - handler->discard_body = TRUE; - } - - *serf_status = APR_EOF; - return SVN_NO_ERROR; + return svn_error_trace( + svn_ra_serf__handle_server_error(handler->server_error, + handler, + request, response, + serf_status, + scratch_pool)); } /* Pass the body along to the registered response handler. */ @@ -2271,12 +1444,13 @@ static apr_status_t handle_response_cb(serf_request_t *request, serf_bucket_t *response, void *baton, - apr_pool_t *scratch_pool) + apr_pool_t *response_pool) { svn_ra_serf__handler_t *handler = baton; svn_error_t *err; apr_status_t inner_status; apr_status_t outer_status; + apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */ err = svn_error_trace(handle_response(request, response, handler, &inner_status, @@ -2287,9 +1461,34 @@ handle_response_cb(serf_request_t *request, if (!outer_status) outer_status = inner_status; - /* Make sure the DONE flag is set properly. */ + /* Make sure the DONE flag is set properly and requests are cleaned up. */ if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status)) - handler->done = TRUE; + { + svn_ra_serf__session_t *sess = handler->session; + handler->done = TRUE; + handler->scheduled = FALSE; + outer_status = APR_EOF; + + /* We use a cached handler->session here to allow handler to free the + memory containing the handler */ + save_error(sess, + handler->done_delegate(request, handler->done_delegate_baton, + scratch_pool)); + } + else if (SERF_BUCKET_READ_ERROR(outer_status) + && handler->session->pending_error) + { + handler->discard_body = TRUE; /* Discard further data */ + handler->done = TRUE; /* Mark as done */ + /* handler->scheduled is still TRUE, as we still expect data. + If we would return an error outer-status the connection + would have to be restarted. With scheduled still TRUE + destroying the handler's pool will still reset the + connection, avoiding the posibility of returning + an error for this handler when a new request is + scheduled. */ + outer_status = APR_EAGAIN; /* Exit context loop */ + } return outer_status; } @@ -2312,9 +1511,8 @@ setup_request(serf_request_t *request, { serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request); - /* ### should pass the scratch_pool */ SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton, - bkt_alloc, request_pool)); + bkt_alloc, request_pool, scratch_pool)); } else { @@ -2338,17 +1536,17 @@ setup_request(serf_request_t *request, SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt, handler->session, handler->method, handler->path, body_bkt, handler->body_type, accept_encoding, - request_pool, scratch_pool)); + !handler->no_dav_headers, request_pool, + scratch_pool)); if (handler->header_delegate) { - /* ### should pass the scratch_pool */ SVN_ERR(handler->header_delegate(headers_bkt, handler->header_delegate_baton, - request_pool)); + request_pool, scratch_pool)); } - return APR_SUCCESS; + return SVN_NO_ERROR; } /* Implements the serf_request_setup_t interface (which sets up both a @@ -2362,51 +1560,58 @@ setup_request_cb(serf_request_t *request, void **acceptor_baton, serf_response_handler_t *s_handler, void **s_handler_baton, - apr_pool_t *pool) + apr_pool_t *request_pool) { svn_ra_serf__handler_t *handler = setup_baton; + apr_pool_t *scratch_pool; svn_error_t *err; - /* ### construct a scratch_pool? serf gives us a pool that will live for - ### the duration of the request. */ - apr_pool_t *scratch_pool = pool; + /* Construct a scratch_pool? serf gives us a pool that will live for + the duration of the request. But requests are retried in some cases */ + scratch_pool = svn_pool_create(request_pool); if (strcmp(handler->method, "HEAD") == 0) *acceptor = accept_head; else *acceptor = accept_response; - *acceptor_baton = handler->session; + *acceptor_baton = handler; *s_handler = handle_response_cb; *s_handler_baton = handler; err = svn_error_trace(setup_request(request, handler, req_bkt, - pool /* request_pool */, scratch_pool)); + request_pool, scratch_pool)); + svn_pool_destroy(scratch_pool); return save_error(handler->session, err); } void svn_ra_serf__request_create(svn_ra_serf__handler_t *handler) { - SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL); - - /* In case HANDLER is re-queued, reset the various transient fields. + SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL + && !handler->scheduled); - ### prior to recent changes, HANDLER was constant. maybe we should - ### break out these processing fields, apart from the request - ### definition. */ + /* In case HANDLER is re-queued, reset the various transient fields. */ handler->done = FALSE; handler->server_error = NULL; handler->sline.version = 0; handler->location = NULL; handler->reading_body = FALSE; handler->discard_body = FALSE; + handler->scheduled = TRUE; + + /* Keeping track of the returned request object would be nice, but doesn't + work the way we would expect in ra_serf.. + + Serf sometimes creates a new request for us (and destroys the old one) + without telling, like when authentication failed (401/407 response. - /* ### do we ever alter the >response_handler? */ + We 'just' trust serf to do the right thing and expect it to tell us + when the state of the request changes. - /* ### do we need to hold onto the returned request object, or just - ### not worry about it (the serf ctx will manage it). */ + ### I fixed a request leak in serf in r2258 on auth failures. + */ (void) serf_connection_request_create(handler->conn->conn, setup_request_cb, handler); } @@ -2415,8 +1620,7 @@ svn_ra_serf__request_create(svn_ra_serf__handler_t *handler) svn_error_t * svn_ra_serf__discover_vcc(const char **vcc_url, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, - apr_pool_t *pool) + apr_pool_t *scratch_pool) { const char *path; const char *relative_path; @@ -2429,12 +1633,6 @@ svn_ra_serf__discover_vcc(const char **vcc_url, return SVN_NO_ERROR; } - /* If no connection is provided, use the default one. */ - if (! conn) - { - conn = session->conns[0]; - } - path = session->session_url.path; *vcc_url = NULL; uuid = NULL; @@ -2444,9 +1642,10 @@ svn_ra_serf__discover_vcc(const char **vcc_url, apr_hash_t *props; svn_error_t *err; - err = svn_ra_serf__fetch_node_props(&props, conn, + err = svn_ra_serf__fetch_node_props(&props, session, path, SVN_INVALID_REVNUM, - base_props, pool, pool); + base_props, + scratch_pool, scratch_pool); if (! err) { apr_hash_t *ns_props; @@ -2474,12 +1673,7 @@ svn_ra_serf__discover_vcc(const char **vcc_url, svn_error_clear(err); /* Okay, strip off a component from PATH. */ - path = svn_urlpath__dirname(path, pool); - - /* An error occurred on conns. serf 0.4.0 remembers that - the connection had a problem. We need to reset it, in - order to use it again. */ - serf_connection_reset(conn->conn); + path = svn_urlpath__dirname(path, scratch_pool); } } } @@ -2505,7 +1699,7 @@ svn_ra_serf__discover_vcc(const char **vcc_url, { svn_stringbuf_t *url_buf; - url_buf = svn_stringbuf_create(path, pool); + url_buf = svn_stringbuf_create(path, scratch_pool); svn_path_remove_components(url_buf, svn_path_component_count(relative_path)); @@ -2533,7 +1727,6 @@ svn_error_t * svn_ra_serf__get_relative_path(const char **rel_path, const char *orig_path, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, apr_pool_t *pool) { const char *decoded_root, *decoded_orig; @@ -2550,7 +1743,6 @@ svn_ra_serf__get_relative_path(const char **rel_path, promises to populate the session's root-url cache, and that's what we really want. */ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, - conn ? conn : session->conns[0], pool)); } @@ -2564,7 +1756,6 @@ svn_ra_serf__get_relative_path(const char **rel_path, svn_error_t * svn_ra_serf__report_resource(const char **report_target, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, apr_pool_t *pool) { /* If we have HTTP v2 support, we want to report against the 'me' @@ -2574,7 +1765,7 @@ svn_ra_serf__report_resource(const char **report_target, /* Otherwise, we'll use the default VCC. */ else - SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool)); + SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool)); return SVN_NO_ERROR; } @@ -2588,13 +1779,14 @@ svn_ra_serf__error_on_status(serf_status_line sline, { case 301: case 302: + case 303: case 307: + case 308: return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL, (sline.code == 301) - ? _("Repository moved permanently to '%s';" - " please relocate") - : _("Repository moved temporarily to '%s';" - " please relocate"), location); + ? _("Repository moved permanently to '%s'") + : _("Repository moved temporarily to '%s'"), + location); case 403: return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL, _("Access to '%s' forbidden"), path); @@ -2602,6 +1794,16 @@ svn_ra_serf__error_on_status(serf_status_line sline, case 404: return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, _("'%s' path not found"), path); + case 405: + return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL, + _("HTTP method is not allowed on '%s'"), + path); + case 409: + return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, + _("'%s' conflicts"), path); + case 412: + return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL, + _("Precondition on '%s' failed"), path); case 423: return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL, _("'%s': no lock token available"), path); @@ -2612,21 +1814,59 @@ svn_ra_serf__error_on_status(serf_status_line sline, "server or an intermediate proxy does not accept " "chunked encoding. Try setting 'http-chunked-requests' " "to 'auto' or 'no' in your client configuration.")); + case 500: + return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("Unexpected server error %d '%s' on '%s'"), + sline.code, sline.reason, path); case 501: return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("The requested feature is not supported by " "'%s'"), path); } - if (sline.code >= 300) + if (sline.code >= 300 || sline.code <= 199) return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, - _("Unexpected HTTP status %d '%s' on '%s'\n"), + _("Unexpected HTTP status %d '%s' on '%s'"), sline.code, sline.reason, path); return SVN_NO_ERROR; } svn_error_t * +svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler) +{ + /* Is it a standard error status? */ + if (handler->sline.code != 405) + SVN_ERR(svn_ra_serf__error_on_status(handler->sline, + handler->path, + handler->location)); + + switch (handler->sline.code) + { + case 201: + return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("Path '%s' unexpectedly created"), + handler->path); + case 204: + return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, + _("Path '%s' already exists"), + handler->path); + + case 405: + return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL, + _("The HTTP method '%s' is not allowed" + " on '%s'"), + handler->method, handler->path); + default: + return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("Unexpected HTTP status %d '%s' on '%s' " + "request to '%s'"), + handler->sline.code, handler->sline.reason, + handler->method, handler->path); + } +} + +svn_error_t * svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session, svn_delta_shim_callbacks_t *callbacks) { @@ -2636,185 +1876,102 @@ svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session, return SVN_NO_ERROR; } - -/* Conforms to Expat's XML_StartElementHandler */ -static void -expat_start(void *userData, const char *raw_name, const char **attrs) -{ - struct expat_ctx_t *ectx = userData; - - if (ectx->inner_error != NULL) - return; - - ectx->inner_error = svn_error_trace( - svn_ra_serf__xml_cb_start(ectx->xmlctx, - raw_name, attrs)); - -#ifdef EXPAT_HAS_STOPPARSER - if (ectx->inner_error) - (void) XML_StopParser(ectx->parser, 0 /* resumable */); -#endif -} - - -/* Conforms to Expat's XML_EndElementHandler */ -static void -expat_end(void *userData, const char *raw_name) -{ - struct expat_ctx_t *ectx = userData; - - if (ectx->inner_error != NULL) - return; - - ectx->inner_error = svn_error_trace( - svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name)); - -#ifdef EXPAT_HAS_STOPPARSER - if (ectx->inner_error) - (void) XML_StopParser(ectx->parser, 0 /* resumable */); -#endif -} - - -/* Conforms to Expat's XML_CharacterDataHandler */ -static void -expat_cdata(void *userData, const char *data, int len) +/* Shared/standard done_delegate handler */ +static svn_error_t * +response_done(serf_request_t *request, + void *handler_baton, + apr_pool_t *scratch_pool) { - struct expat_ctx_t *ectx = userData; + svn_ra_serf__handler_t *handler = handler_baton; - if (ectx->inner_error != NULL) - return; - - ectx->inner_error = svn_error_trace( - svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len)); - -#ifdef EXPAT_HAS_STOPPARSER - if (ectx->inner_error) - (void) XML_StopParser(ectx->parser, 0 /* resumable */); -#endif -} + assert(handler->done); + if (handler->no_fail_on_http_failure_status) + return SVN_NO_ERROR; -/* Implements svn_ra_serf__response_handler_t */ -static svn_error_t * -expat_response_handler(serf_request_t *request, - serf_bucket_t *response, - void *baton, - apr_pool_t *scratch_pool) -{ - struct expat_ctx_t *ectx = baton; + if (handler->server_error) + return svn_ra_serf__server_error_create(handler, scratch_pool); - if (!ectx->parser) + if (handler->sline.code >= 400 || handler->sline.code <= 199) { - 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); + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); } - /* ### TODO: sline.code < 200 should really be handled by the core */ - if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300)) + if ((handler->sline.code >= 300 && handler->sline.code < 399) + && !handler->no_fail_on_http_redirect_status) { - /* By deferring to expect_empty_body(), it will make a choice on - how to handle the body. Whatever the decision, the core handler - will take over, and we will not be called again. */ - return svn_error_trace(svn_ra_serf__expect_empty_body( - request, response, ectx->handler, - scratch_pool)); + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); } - while (1) - { - apr_status_t status; - const char *data; - apr_size_t len; - int expat_status; - - status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); - if (SERF_BUCKET_READ_ERROR(status)) - return svn_ra_serf__wrap_err(status, NULL); - -#if 0 - /* ### move restart/skip into the core handler */ - ectx->handler->read_size += len; -#endif - - /* ### move PAUSED behavior to a new response handler that can feed - ### an inner handler, or can pause for a while. */ - - /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */ - - expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */); - - /* We need to check INNER_ERROR first. This is an error from the - callbacks that has been "dropped off" for us to retrieve. On - current Expat parsers, we stop the parser when an error occurs, - so we want to ignore EXPAT_STATUS (which reports the stoppage). - - If an error is not present, THEN we go ahead and look for parsing - errors. */ - if (ectx->inner_error) - { - apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser, - xml_parser_cleanup); - return svn_error_trace(ectx->inner_error); - } - if (expat_status == XML_STATUS_ERROR) - return svn_error_createf(SVN_ERR_XML_MALFORMED, - ectx->inner_error, - _("The %s response contains invalid XML" - " (%d %s)"), - ectx->handler->method, - ectx->handler->sline.code, - ectx->handler->sline.reason); - - /* The parsing went fine. What has the bucket told us? */ - - if (APR_STATUS_IS_EOF(status)) - { - /* Tell expat we've reached the end of the content. Ignore the - return status. We just don't care. */ - (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */); + return SVN_NO_ERROR; +} - svn_ra_serf__xml_context_destroy(ectx->xmlctx); - apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser, - xml_parser_cleanup); +/* Pool cleanup handler for request handlers. - /* ### should check XMLCTX to see if it has returned to the - ### INITIAL state. we may have ended early... */ - } + If a serf context run stops for some outside error, like when the user + cancels a request via ^C in the context loop, the handler is still + registered in the serf context. With the pool cleanup there would be + handlers registered in no freed memory. - if (status && !SERF_BUCKET_READ_ERROR(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } + This fallback kills the connection for this case, which will make serf + unregister any outstanding requests on it. */ +static apr_status_t +handler_cleanup(void *baton) +{ + svn_ra_serf__handler_t *handler = baton; + if (handler->scheduled) + { + svn_ra_serf__unschedule_handler(handler); } - /* NOTREACHED */ + return APR_SUCCESS; } - svn_ra_serf__handler_t * -svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx, - apr_pool_t *result_pool) +svn_ra_serf__create_handler(svn_ra_serf__session_t *session, + apr_pool_t *result_pool) { svn_ra_serf__handler_t *handler; - struct expat_ctx_t *ectx; - - ectx = apr_pcalloc(result_pool, sizeof(*ectx)); - ectx->xmlctx = xmlctx; - ectx->parser = NULL; - ectx->cleanup_pool = result_pool; - handler = apr_pcalloc(result_pool, sizeof(*handler)); handler->handler_pool = result_pool; - handler->response_handler = expat_response_handler; - handler->response_baton = ectx; - ectx->handler = handler; + apr_pool_cleanup_register(result_pool, handler, handler_cleanup, + apr_pool_cleanup_null); + + handler->session = session; + handler->conn = session->conns[0]; + + /* Setup the default done handler, to handle server errors */ + handler->done_delegate_baton = handler; + handler->done_delegate = response_done; return handler; } + +svn_error_t * +svn_ra_serf__uri_parse(apr_uri_t *uri, + const char *url_str, + apr_pool_t *result_pool) +{ + apr_status_t status; + + status = apr_uri_parse(result_pool, url_str, uri); + if (status) + { + /* Do not use returned error status in error message because currently + apr_uri_parse() returns APR_EGENERAL for all parsing errors. */ + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("Illegal URL '%s'"), + url_str); + } + + /* Depending the version of apr-util in use, for root paths uri.path + will be NULL or "", where serf requires "/". */ + if (uri->path == NULL || uri->path[0] == '\0') + { + uri->path = apr_pstrdup(result_pool, "/"); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_ra_serf/util_error.c b/subversion/libsvn_ra_serf/util_error.c index da66091..bce935a 100644 --- a/subversion/libsvn_ra_serf/util_error.c +++ b/subversion/libsvn_ra_serf/util_error.c @@ -88,7 +88,8 @@ svn_ra_serf__wrap_err(apr_status_t status, } if (err_msg) { - err->message = apr_pstrcat(err->pool, msg, ": ", err_msg, NULL); + err->message = apr_pstrcat(err->pool, msg, ": ", err_msg, + SVN_VA_NULL); } else { diff --git a/subversion/libsvn_ra_serf/xml.c b/subversion/libsvn_ra_serf/xml.c index a95eacc..1a98857 100644 --- a/subversion/libsvn_ra_serf/xml.c +++ b/subversion/libsvn_ra_serf/xml.c @@ -24,6 +24,7 @@ #include <apr_uri.h> +#include <expat.h> #include <serf.h> #include "svn_hash.h" @@ -42,13 +43,35 @@ #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 + + struct svn_ra_serf__xml_context_t { /* Current state information. */ svn_ra_serf__xml_estate_t *current; - /* If WAITING.NAMESPACE != NULL, wait for NAMESPACE:NAME element to be - closed before looking for transitions from CURRENT->STATE. */ - svn_ra_serf__dav_props_t waiting; + /* If WAITING >= then we are waiting for an element to close before + resuming events. The number stored here is the amount of nested + elements open. The Xml parser will make sure the document is well + formed. */ + int waiting; /* The transition table. */ const svn_ra_serf__xml_transition_t *ttable; @@ -83,6 +106,16 @@ struct svn_ra_serf__xml_context_t { }; +/* Structure which represents an XML namespace. */ +typedef struct svn_ra_serf__ns_t { + /* The assigned name. */ + const char *xmlns; + /* The full URL for this namespace. */ + const char *url; + /* The next namespace in our list. */ + struct svn_ra_serf__ns_t *next; +} svn_ra_serf__ns_t; + struct svn_ra_serf__xml_estate_t { /* The current state value. */ int state; @@ -114,6 +147,19 @@ struct svn_ra_serf__xml_estate_t { }; +struct expat_ctx_t { + svn_ra_serf__xml_context_t *xmlctx; + XML_Parser 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; +}; + static void define_namespaces(svn_ra_serf__ns_t **ns_list, @@ -140,7 +186,7 @@ define_namespaces(svn_ra_serf__ns_t **ns_list, /* Have we already defined this ns previously? */ for (cur_ns = *ns_list; cur_ns; cur_ns = cur_ns->next) { - if (strcmp(cur_ns->namespace, prefix) == 0) + if (strcmp(cur_ns->xmlns, prefix) == 0) { found = TRUE; break; @@ -157,7 +203,7 @@ define_namespaces(svn_ra_serf__ns_t **ns_list, else pool = baton; new_ns = apr_palloc(pool, sizeof(*new_ns)); - new_ns->namespace = apr_pstrdup(pool, prefix); + new_ns->xmlns = apr_pstrdup(pool, prefix); new_ns->url = apr_pstrdup(pool, tmp_attrs[1]); /* Push into the front of NS_LIST. Parent states will point @@ -170,22 +216,15 @@ define_namespaces(svn_ra_serf__ns_t **ns_list, } } - -void -svn_ra_serf__define_ns(svn_ra_serf__ns_t **ns_list, - const char *const *attrs, - apr_pool_t *result_pool) -{ - define_namespaces(ns_list, attrs, NULL /* get_pool */, result_pool); -} - - /* - * Look up NAME in the NS_LIST list for previously declared namespace - * definitions and return a DAV_PROPS_T-tuple that has values. + * Look up @a name in the @a ns_list list for previously declared namespace + * definitions. + * + * Return (in @a *returned_prop_name) a #svn_ra_serf__dav_props_t tuple + * representing the expanded name. */ -void -svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name, +static void +expand_ns(svn_ra_serf__dav_props_t *returned_prop_name, const svn_ra_serf__ns_t *ns_list, const char *name) { @@ -198,9 +237,9 @@ svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name, for (ns = ns_list; ns; ns = ns->next) { - if (strncmp(ns->namespace, name, colon - name) == 0) + if (strncmp(ns->xmlns, name, colon - name) == 0) { - returned_prop_name->namespace = ns->url; + returned_prop_name->xmlns = ns->url; returned_prop_name->name = colon + 1; return; } @@ -212,9 +251,9 @@ svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name, for (ns = ns_list; ns; ns = ns->next) { - if (! ns->namespace[0]) + if (! ns->xmlns[0]) { - returned_prop_name->namespace = ns->url; + returned_prop_name->xmlns = ns->url; returned_prop_name->name = name; return; } @@ -223,7 +262,7 @@ svn_ra_serf__expand_ns(svn_ra_serf__dav_props_t *returned_prop_name, /* If the prefix is not found, then the name is NOT within a namespace. */ - returned_prop_name->namespace = ""; + returned_prop_name->xmlns = ""; returned_prop_name->name = name; } @@ -285,6 +324,49 @@ svn_ra_serf__add_open_tag_buckets(serf_bucket_t *agg_bucket, } void +svn_ra_serf__add_empty_tag_buckets(serf_bucket_t *agg_bucket, + serf_bucket_alloc_t *bkt_alloc, + const char *tag, ...) +{ + va_list ap; + const char *key; + serf_bucket_t *tmp; + + tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, bkt_alloc); + serf_bucket_aggregate_append(agg_bucket, tmp); + + tmp = SERF_BUCKET_SIMPLE_STRING(tag, bkt_alloc); + serf_bucket_aggregate_append(agg_bucket, tmp); + + va_start(ap, tag); + while ((key = va_arg(ap, char *)) != NULL) + { + const char *val = va_arg(ap, const char *); + if (val) + { + tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" ", 1, bkt_alloc); + serf_bucket_aggregate_append(agg_bucket, tmp); + + tmp = SERF_BUCKET_SIMPLE_STRING(key, bkt_alloc); + serf_bucket_aggregate_append(agg_bucket, tmp); + + tmp = SERF_BUCKET_SIMPLE_STRING_LEN("=\"", 2, bkt_alloc); + serf_bucket_aggregate_append(agg_bucket, tmp); + + tmp = SERF_BUCKET_SIMPLE_STRING(val, bkt_alloc); + serf_bucket_aggregate_append(agg_bucket, tmp); + + tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"", 1, bkt_alloc); + serf_bucket_aggregate_append(agg_bucket, tmp); + } + } + va_end(ap); + + tmp = SERF_BUCKET_SIMPLE_STRING_LEN("/>", 2, bkt_alloc); + serf_bucket_aggregate_append(agg_bucket, tmp); +} + +void svn_ra_serf__add_close_tag_buckets(serf_bucket_t *agg_bucket, serf_bucket_alloc_t *bkt_alloc, const char *tag) @@ -368,7 +450,7 @@ void svn_ra_serf__add_tag_buckets(serf_bucket_t *agg_bucket, const char *tag, const char *value, serf_bucket_alloc_t *bkt_alloc) { - svn_ra_serf__add_open_tag_buckets(agg_bucket, bkt_alloc, tag, NULL); + svn_ra_serf__add_open_tag_buckets(agg_bucket, bkt_alloc, tag, SVN_VA_NULL); if (value) { @@ -379,54 +461,6 @@ void svn_ra_serf__add_tag_buckets(serf_bucket_t *agg_bucket, const char *tag, svn_ra_serf__add_close_tag_buckets(agg_bucket, bkt_alloc, tag); } -void -svn_ra_serf__xml_push_state(svn_ra_serf__xml_parser_t *parser, - int state) -{ - svn_ra_serf__xml_state_t *new_state; - - if (!parser->free_state) - { - new_state = apr_palloc(parser->pool, sizeof(*new_state)); - new_state->pool = svn_pool_create(parser->pool); - } - else - { - new_state = parser->free_state; - parser->free_state = parser->free_state->prev; - - svn_pool_clear(new_state->pool); - } - - if (parser->state) - { - new_state->private = parser->state->private; - new_state->ns_list = parser->state->ns_list; - } - else - { - new_state->private = NULL; - new_state->ns_list = NULL; - } - - new_state->current_state = state; - - /* Add it to the state chain. */ - new_state->prev = parser->state; - parser->state = new_state; -} - -void svn_ra_serf__xml_pop_state(svn_ra_serf__xml_parser_t *parser) -{ - svn_ra_serf__xml_state_t *cur_state; - - cur_state = parser->state; - parser->state = cur_state->prev; - cur_state->prev = parser->free_state; - parser->free_state = cur_state; -} - - /* Return a pool for XES to use for self-alloc (and other specifics). */ static apr_pool_t * xes_pool(const svn_ra_serf__xml_estate_t *xes) @@ -458,11 +492,50 @@ lazy_create_pool(void *baton) return xes->state_pool; } -void -svn_ra_serf__xml_context_destroy( - svn_ra_serf__xml_context_t *xmlctx) +svn_error_t * +svn_ra_serf__xml_context_done(svn_ra_serf__xml_context_t *xmlctx) { + if (xmlctx->current->prev) + { + /* Probably unreachable as this would be an xml parser error */ + return svn_error_createf(SVN_ERR_XML_MALFORMED, NULL, + _("XML stream truncated: closing '%s' missing"), + xmlctx->current->tag.name); + } + else if (! xmlctx->free_states) + { + /* If we have no items on the free_states list, we didn't push anything, + which tells us that we found an empty xml body */ + const svn_ra_serf__xml_transition_t *scan; + const svn_ra_serf__xml_transition_t *document = NULL; + const char *msg; + + for (scan = xmlctx->ttable; scan->ns != NULL; ++scan) + { + if (scan->from_state == XML_STATE_INITIAL) + { + if (document != NULL) + { + document = NULL; /* Multiple document elements defined */ + break; + } + document = scan; + } + } + + if (document) + msg = apr_psprintf(xmlctx->scratch_pool, "'%s' element not found", + document->name); + else + msg = _("document element not found"); + + return svn_error_createf(SVN_ERR_XML_MALFORMED, NULL, + _("XML stream truncated: %s"), + msg); + } + svn_pool_destroy(xmlctx->scratch_pool); + return SVN_NO_ERROR; } svn_ra_serf__xml_context_t * @@ -577,10 +650,10 @@ svn_ra_serf__xml_state_pool(svn_ra_serf__xml_estate_t *xes) } -svn_error_t * -svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx, - const char *raw_name, - const char *const *attrs) +static svn_error_t * +xml_cb_start(svn_ra_serf__xml_context_t *xmlctx, + const char *raw_name, + const char *const *attrs) { svn_ra_serf__xml_estate_t *current = xmlctx->current; svn_ra_serf__dav_props_t elemname; @@ -590,14 +663,17 @@ svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx, /* If we're waiting for an element to close, then just ignore all other element-opens. */ - if (xmlctx->waiting.namespace != NULL) - return SVN_NO_ERROR; + if (xmlctx->waiting > 0) + { + xmlctx->waiting++; + return SVN_NO_ERROR; + } /* Look for xmlns: attributes. Lazily create the state pool if any were found. */ define_namespaces(¤t->ns_list, attrs, lazy_create_pool, current); - svn_ra_serf__expand_ns(&elemname, current->ns_list, raw_name); + expand_ns(&elemname, current->ns_list, raw_name); for (scan = xmlctx->ttable; scan->ns != NULL; ++scan) { @@ -610,21 +686,20 @@ svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx, /* Found a specific transition. */ if (strcmp(elemname.name, scan->name) == 0 - && strcmp(elemname.namespace, scan->ns) == 0) + && strcmp(elemname.xmlns, scan->ns) == 0) break; } if (scan->ns == NULL) { - if (current->state == 0) + if (current->state == XML_STATE_INITIAL) { return svn_error_createf( - SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, + SVN_ERR_XML_UNEXPECTED_ELEMENT, NULL, _("XML Parsing failed: Unexpected root element '%s'"), elemname.name); } - xmlctx->waiting = elemname; - /* ### return? */ + xmlctx->waiting++; /* Start waiting for the close tag */ return SVN_NO_ERROR; } @@ -677,10 +752,11 @@ svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx, name = *saveattr; value = svn_xml_get_attr_value(name, attrs); if (value == NULL) - return svn_error_createf(SVN_ERR_XML_ATTRIB_NOT_FOUND, - NULL, - _("Missing XML attribute: '%s'"), - name); + return svn_error_createf( + SVN_ERR_XML_ATTRIB_NOT_FOUND, + NULL, + _("Missing XML attribute '%s' on '%s' element"), + name, scan->name); } if (value) @@ -699,7 +775,7 @@ svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx, /* Some basic copies to set up the new estate. */ new_xes->state = scan->to_state; new_xes->tag.name = apr_pstrdup(new_pool, elemname.name); - new_xes->tag.namespace = apr_pstrdup(new_pool, elemname.namespace); + new_xes->tag.xmlns = apr_pstrdup(new_pool, elemname.xmlns); new_xes->custom_close = scan->custom_close; /* Start with the parent's namespace set. */ @@ -723,39 +799,18 @@ svn_ra_serf__xml_cb_start(svn_ra_serf__xml_context_t *xmlctx, } -svn_error_t * -svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx, - const char *raw_name) +static svn_error_t * +xml_cb_end(svn_ra_serf__xml_context_t *xmlctx, + const char *raw_name) { svn_ra_serf__xml_estate_t *xes = xmlctx->current; - svn_ra_serf__dav_props_t elemname; - svn_ra_serf__expand_ns(&elemname, xes->ns_list, raw_name); - - if (xmlctx->waiting.namespace != NULL) + if (xmlctx->waiting > 0) { - /* If this element is not the closer, then keep waiting... */ - if (strcmp(elemname.name, xmlctx->waiting.name) != 0 - || strcmp(elemname.namespace, xmlctx->waiting.namespace) != 0) - return SVN_NO_ERROR; - - /* Found it. Stop waiting, and go back for more. */ - xmlctx->waiting.namespace = NULL; + xmlctx->waiting--; return SVN_NO_ERROR; } - /* We should be looking at the same tag that opened the current state. - - Unknown elements are simply skipped, so we wouldn't reach this check. - - Known elements push a new state for a given tag. Some other elemname - would imply closing an ancestor tag (where did ours go?) or a spurious - tag closure. */ - if (strcmp(elemname.name, xes->tag.name) != 0 - || strcmp(elemname.namespace, xes->tag.namespace) != 0) - return svn_error_create(SVN_ERR_XML_MALFORMED, NULL, - _("The response contains invalid XML")); - if (xes->custom_close) { const svn_string_t *cdata; @@ -799,14 +854,14 @@ svn_ra_serf__xml_cb_end(svn_ra_serf__xml_context_t *xmlctx, } -svn_error_t * -svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx, - const char *data, - apr_size_t len) +static svn_error_t * +xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx, + const char *data, + apr_size_t len) { /* If we are waiting for a closing tag, then we are uninterested in the cdata. Just return. */ - if (xmlctx->waiting.namespace != NULL) + if (xmlctx->waiting > 0) return SVN_NO_ERROR; /* If the current state is collecting cdata, then copy the cdata. */ @@ -831,3 +886,256 @@ svn_ra_serf__xml_cb_cdata(svn_ra_serf__xml_context_t *xmlctx, return SVN_NO_ERROR; } +/* svn_error_t * wrapper around 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; + + if (*xmlp) + { + (void) XML_ParserFree(*xmlp); + *xmlp = NULL; + } + + return APR_SUCCESS; +} + +/* Conforms to Expat's XML_StartElementHandler */ +static void +expat_start(void *userData, const char *raw_name, const char **attrs) +{ + struct expat_ctx_t *ectx = userData; + + if (ectx->inner_error != NULL) + return; + + ectx->inner_error = svn_error_trace(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 +} + + +/* Conforms to Expat's XML_EndElementHandler */ +static void +expat_end(void *userData, const char *raw_name) +{ + struct expat_ctx_t *ectx = userData; + + if (ectx->inner_error != NULL) + return; + + ectx->inner_error = svn_error_trace(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 +} + + +/* Conforms to Expat's XML_CharacterDataHandler */ +static void +expat_cdata(void *userData, const char *data, int len) +{ + struct expat_ctx_t *ectx = userData; + + if (ectx->inner_error != NULL) + return; + + ectx->inner_error = svn_error_trace(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 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 * +expat_response_handler(serf_request_t *request, + serf_bucket_t *response, + void *baton, + apr_pool_t *scratch_pool) +{ + struct expat_ctx_t *ectx = baton; + svn_boolean_t got_expected_status; + + if (ectx->expected_status) + { + const int *status = ectx->expected_status; + got_expected_status = FALSE; + + while (*status && ectx->handler->sline.code != *status) + status++; + + got_expected_status = (*status) != 0; + } + else + got_expected_status = (ectx->handler->sline.code == 200); + + if (!ectx->handler->server_error + && ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300) + || ! got_expected_status)) + { + /* By deferring to expect_empty_body(), it will make a choice on + how to handle the body. Whatever the decision, the core handler + will take over, and we will not be called again. */ + + /* ### This handles xml bodies as svn-errors (returned via serf context + ### loop), but ignores non-xml errors. + + Current code depends on this behavior and checks itself while other + continues, and then verifies if work has been performed. + + ### TODO: Make error checking consistent */ + + /* ### If !GOT_EXPECTED_STATUS, this should always produce an error */ + return svn_error_trace(svn_ra_serf__expect_empty_body( + request, response, ectx->handler, + scratch_pool)); + } + + 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 + } + + while (1) + { + 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); + if (SERF_BUCKET_READ_ERROR(status)) + return svn_ra_serf__wrap_err(status, NULL); + 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); + + /* The parsing went fine. What has the bucket told us? */ + if (at_eof) + { + /* Make sure we actually got xml and clean up after parsing */ + SVN_ERR(svn_ra_serf__xml_context_done(ectx->xmlctx)); + } + + if (status && !SERF_BUCKET_READ_ERROR(status)) + { + return svn_ra_serf__wrap_err(status, NULL); + } + } + + /* NOTREACHED */ +} + + +svn_ra_serf__handler_t * +svn_ra_serf__create_expat_handler(svn_ra_serf__session_t *session, + svn_ra_serf__xml_context_t *xmlctx, + const int *expected_status, + apr_pool_t *result_pool) +{ + svn_ra_serf__handler_t *handler; + struct expat_ctx_t *ectx; + + ectx = apr_pcalloc(result_pool, sizeof(*ectx)); + ectx->xmlctx = xmlctx; + ectx->parser = NULL; + ectx->expected_status = expected_status; + ectx->cleanup_pool = result_pool; + + handler = svn_ra_serf__create_handler(session, result_pool); + handler->response_handler = expat_response_handler; + handler->response_baton = ectx; + + ectx->handler = handler; + + return handler; +} |