blob: a3d321a64715f3fbacfe58b151bc66e5efe97f45 [file] [log] [blame]
/*
* options.c : entry point for OPTIONS RA functions for ra_serf
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <apr_uri.h>
#include <serf.h>
#include "svn_dirent_uri.h"
#include "svn_hash.h"
#include "svn_pools.h"
#include "svn_path.h"
#include "svn_ra.h"
#include "svn_dav.h"
#include "svn_xml.h"
#include "svn_ctype.h"
#include "../libsvn_ra/ra_loader.h"
#include "svn_private_config.h"
#include "private/svn_fspath.h"
#include "ra_serf.h"
/* In a debug build, setting this environment variable to "yes" will force
the client to speak v1, even if the server is capable of speaking v2. */
#define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
/*
* This enum represents the current state of our XML parsing for an OPTIONS.
*/
enum options_state_e {
INITIAL = XML_STATE_INITIAL,
OPTIONS,
ACTIVITY_COLLECTION,
HREF
};
typedef struct options_context_t {
/* pool to allocate memory from */
apr_pool_t *pool;
/* Have we extracted options values from the headers already? */
svn_boolean_t headers_processed;
svn_ra_serf__session_t *session;
svn_ra_serf__handler_t *handler;
svn_ra_serf__response_handler_t inner_handler;
void *inner_baton;
/* Have we received any DAV headers at all? */
svn_boolean_t received_dav_header;
const char *activity_collection;
svn_revnum_t youngest_rev;
} options_context_t;
#define D_ "DAV:"
#define S_ SVN_XML_NAMESPACE
static const svn_ra_serf__xml_transition_t options_ttable[] = {
{ INITIAL, D_, "options-response", OPTIONS,
FALSE, { NULL }, FALSE },
{ OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
FALSE, { NULL }, FALSE },
{ ACTIVITY_COLLECTION, D_, "href", HREF,
TRUE, { NULL }, TRUE },
{ 0 }
};
/* Conforms to svn_ra_serf__xml_closed_t */
static svn_error_t *
options_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)
{
options_context_t *opt_ctx = baton;
SVN_ERR_ASSERT(leaving_state == HREF);
SVN_ERR_ASSERT(cdata != NULL);
opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
opt_ctx->pool);
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 /* 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:",
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");
*body_bkt = body;
return SVN_NO_ERROR;
}
/* We use these static pointers so we can employ pointer comparison
* of our capabilities hash members instead of strcmp()ing all over
* the place.
*/
/* Both server and repository support the capability. */
static const char *const capability_yes = "yes";
/* Either server or repository does not support the capability. */
static const char *const capability_no = "no";
/* Server supports the capability, but don't yet know if repository does. */
static const char *const capability_server_yes = "server-yes";
/* This implements serf_bucket_headers_do_callback_fn_t.
*/
static int
capabilities_headers_iterator_callback(void *baton,
const char *key,
const char *val)
{
options_context_t *opt_ctx = baton;
svn_ra_serf__session_t *session = opt_ctx->session;
if (svn_cstring_casecmp(key, "dav") == 0)
{
/* Each header may contain multiple values, separated by commas, e.g.:
DAV: version-control,checkout,working-resource
DAV: merge,baseline,activity,version-controlled-collection
DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
opt_ctx->pool);
opt_ctx->received_dav_header = TRUE;
/* Right now we only have a few capabilities to detect, so just
seek for them directly. This could be written slightly more
efficiently, but that wouldn't be worth it until we have many
more capabilities. */
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_DEPTH, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
{
/* The server doesn't know what repository we're referring
to, so it can't just say capability_yes. */
if (!svn_hash_gets(session->capabilities,
SVN_RA_CAPABILITY_MERGEINFO))
{
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
capability_server_yes);
}
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
{
session->supports_inline_props = TRUE;
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
{
session->supports_rev_rsrc_replay = TRUE;
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF1, vals))
{
/* Use compressed svndiff1 format for servers that properly
advertise this capability (Subversion 1.10 and greater). */
session->supports_svndiff1 = TRUE;
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LIST, vals))
{
svn_hash_sets(session->capabilities,
SVN_RA_CAPABILITY_LIST, capability_yes);
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF2, vals))
{
/* Same for svndiff2. */
session->supports_svndiff2 = TRUE;
}
if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PUT_RESULT_CHECKSUM, vals))
{
session->supports_put_result_checksum = TRUE;
}
}
/* SVN-specific headers -- if present, server supports HTTP protocol v2 */
else if (!svn_ctype_casecmp(key[0], 'S')
&& !svn_ctype_casecmp(key[1], 'V')
&& !svn_ctype_casecmp(key[2], 'N'))
{
/* If we've not yet seen any information about supported POST
requests, we'll initialize the list/hash with "create-txn"
(which we know is supported by virtue of the server speaking
HTTPv2 at all. */
if (! session->supported_posts)
{
session->supported_posts = apr_hash_make(session->pool);
apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
}
if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
{
session->repos_root = session->session_url;
session->repos_root.path =
(char *)svn_fspath__canonicalize(val, session->pool);
session->repos_root_str =
svn_urlpath__canonicalize(
apr_uri_unparse(session->pool, &session->repos_root, 0),
session->pool);
}
else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
{
#ifdef SVN_DEBUG
char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
if (!(ignore_v2_env_var
&& apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
session->me_resource = apr_pstrdup(session->pool, val);
#else
session->me_resource = apr_pstrdup(session->pool, val);
#endif
}
else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
{
session->rev_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
{
session->rev_root_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
{
session->txn_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
{
session->txn_root_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
{
session->vtxn_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
{
session->vtxn_root_stub = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
{
session->uuid = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
{
opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
{
session->server_allows_bulk = apr_pstrdup(session->pool, val);
}
else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
{
/* May contain multiple values, separated by commas. */
int i;
apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
session->pool);
for (i = 0; i < vals->nelts; i++)
{
const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
svn_hash_sets(session->supported_posts, post_val, (void *)1);
}
}
else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
{
if (svn_cstring_casecmp(val, "yes") == 0)
{
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
capability_yes);
}
else if (svn_cstring_casecmp(val, "no") == 0)
{
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
capability_no);
}
}
}
return 0;
}
/* A custom serf_response_handler_t which is mostly a wrapper around
the expat-based response handler -- it just notices OPTIONS response
headers first, before handing off to the xml parser.
Implements svn_ra_serf__response_handler_t */
static svn_error_t *
options_response_handler(serf_request_t *request,
serf_bucket_t *response,
void *baton,
apr_pool_t *pool)
{
options_context_t *opt_ctx = baton;
if (!opt_ctx->headers_processed)
{
svn_ra_serf__session_t *session = opt_ctx->session;
serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
serf_connection_t *conn;
/* Start out assuming all capabilities are unsupported. */
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
NULL);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
capability_no);
svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LIST,
capability_no);
/* Then see which ones we can discover. */
serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
opt_ctx);
/* Bail out early if we're not talking to a DAV server.
Note that this check is only valid if we've received a success
response; redirects and errors don't count. */
if (opt_ctx->handler->sline.code >= 200
&& opt_ctx->handler->sline.code < 300
&& !opt_ctx->received_dav_header)
{
return svn_error_createf
(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
_("The server at '%s' does not support the HTTP/DAV protocol"),
session->session_url_str);
}
/* 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,
capability_no);
/* Remember our latency. */
conn = serf_request_get_conn(request);
session->conn_latency = serf_connection_get_latency(conn);
opt_ctx->headers_processed = TRUE;
}
/* Execute the 'real' response handler to XML-parse the response body. */
return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
}
static svn_error_t *
create_options_req(options_context_t **opt_ctx,
svn_ra_serf__session_t *session,
apr_pool_t *pool)
{
options_context_t *new_ctx;
svn_ra_serf__xml_context_t *xmlctx;
svn_ra_serf__handler_t *handler;
new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
new_ctx->pool = pool;
new_ctx->session = session;
new_ctx->youngest_rev = SVN_INVALID_REVNUM;
xmlctx = svn_ra_serf__xml_context_create(options_ttable,
NULL, options_closed, NULL,
new_ctx,
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";
new_ctx->handler = handler;
new_ctx->inner_handler = handler->response_handler;
new_ctx->inner_baton = handler->response_baton;
handler->response_handler = options_response_handler;
handler->response_baton = new_ctx;
*opt_ctx = new_ctx;
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
svn_ra_serf__session_t *session,
apr_pool_t *scratch_pool)
{
options_context_t *opt_ctx;
SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
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;
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__v1_get_activity_collection(const char **activity_url,
svn_ra_serf__session_t *session,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
options_context_t *opt_ctx;
SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
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));
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);
return SVN_NO_ERROR;
}
/** Capabilities exchange. */
svn_error_t *
svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
const char **corrected_url,
const char **redirect_url,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
options_context_t *opt_ctx;
if (corrected_url)
*corrected_url = NULL;
if (redirect_url)
*redirect_url = NULL;
/* This routine automatically fills in serf_sess->capabilities */
SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_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 --
it's most likely just a complaint about the response body not
successfully parsing as XML or somesuch. */
if (corrected_url && (opt_ctx->handler->sline.code == 301))
{
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))
{
SVN_ERR(svn_uri_canonicalize_safe(corrected_url, NULL,
opt_ctx->handler->location, result_pool, scratch_pool));
if (redirect_url)
*redirect_url = apr_pstrdup(result_pool,
opt_ctx->handler->location);
}
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;
char *absolute_uri;
corrected_URI.path = (char *)corrected_url;
absolute_uri = apr_uri_unparse(scratch_pool, &corrected_URI, 0);
SVN_ERR(svn_uri_canonicalize_safe(corrected_url, NULL,
absolute_uri, result_pool, scratch_pool));
if (redirect_url)
*redirect_url = apr_pstrdup(result_pool, absolute_uri);
}
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);
}
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
unreadable commit anchor URL.) */
if (opt_ctx->activity_collection)
{
serf_sess->activity_collection_url =
apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
}
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 /* request pool */,
apr_pool_t *scratch_pool)
{
serf_bucket_t *body;
serf_bucket_t *s;
body = serf_bucket_aggregate_create(alloc);
svn_ra_serf__add_xml_header_buckets(body, alloc);
s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
serf_bucket_aggregate_append(body, s);
*body_bkt = body;
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
apr_pool_t *scratch_pool)
{
svn_ra_serf__handler_t *handler;
handler = svn_ra_serf__create_handler(serf_sess, scratch_pool);
handler->method = "OPTIONS";
handler->path = serf_sess->session_url.path;
/* 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. */
SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
/* Some versions of nginx in reverse proxy mode will return 411. They want
a Content-Length header, rather than chunked requests. We can keep other
HTTP/1.1 features, but will disable the chunking. */
if (handler->sline.code == 411)
{
serf_sess->using_chunked_requests = FALSE;
return SVN_NO_ERROR;
}
if (handler->sline.code != 200)
SVN_ERR(svn_ra_serf__unexpected_status(handler));
return SVN_NO_ERROR;
}
svn_error_t *
svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
svn_boolean_t *has,
const char *capability,
apr_pool_t *pool)
{
svn_ra_serf__session_t *serf_sess = ra_session->priv;
const char *cap_result;
/* This capability doesn't rely on anything server side. */
if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
{
*has = TRUE;
return SVN_NO_ERROR;
}
cap_result = svn_hash_gets(serf_sess->capabilities, capability);
/* 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, NULL,
pool, pool));
/* Try again, now that we've fetched the capabilities. */
cap_result = svn_hash_gets(serf_sess->capabilities, capability);
/* Some capabilities depend on the repository as well as the server. */
if (cap_result == capability_server_yes)
{
if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
{
/* Handle mergeinfo specially. Mergeinfo depends on the
repository as well as the server, but the server routine
that answered our svn_ra_serf__exchange_capabilities() call above
didn't even know which repository we were interested in
-- it just told us whether the server supports mergeinfo.
If the answer was 'no', there's no point checking the
particular repository; but if it was 'yes', we still must
change it to 'no' iff the repository itself doesn't
support mergeinfo. */
svn_mergeinfo_catalog_t ignored;
svn_error_t *err;
apr_array_header_t *paths = apr_array_make(pool, 1,
sizeof(char *));
APR_ARRAY_PUSH(paths, const char *) = "";
err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
svn_mergeinfo_explicit,
FALSE /* include_descendants */,
pool);
if (err)
{
if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
{
svn_error_clear(err);
cap_result = capability_no;
}
else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
{
/* Mergeinfo requests use relative paths, and
anyway we're in r0, so this is a likely error,
but it means the repository supports mergeinfo! */
svn_error_clear(err);
cap_result = capability_yes;
}
else
return svn_error_trace(err);
}
else
cap_result = capability_yes;
svn_hash_sets(serf_sess->capabilities,
SVN_RA_CAPABILITY_MERGEINFO, cap_result);
}
else
{
return svn_error_createf
(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
_("Don't know how to handle '%s' for capability '%s'"),
capability_server_yes, capability);
}
}
if (cap_result == capability_yes)
{
*has = TRUE;
}
else if (cap_result == capability_no)
{
*has = FALSE;
}
else if (cap_result == NULL)
{
return svn_error_createf
(SVN_ERR_UNKNOWN_CAPABILITY, NULL,
_("Don't know anything about capability '%s'"), capability);
}
else /* "can't happen" */
{
/* Well, let's hope it's a string. */
return svn_error_createf
(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
_("Attempt to fetch capability '%s' resulted in '%s'"),
capability, cap_result);
}
return SVN_NO_ERROR;
}