aboutsummaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_ra_serf/options.c
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerPeter Wemm <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commit32547653cc5376642e1231fb644db99933ac8db4 (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_ra_serf/options.c
downloadsrc-32547653cc5376642e1231fb644db99933ac8db4.tar.gz
src-32547653cc5376642e1231fb644db99933ac8db4.zip
Import trimmed svn-1.8.0-rc3vendor/subversion/subversion-1.8.0-rc3
Notes
Notes: svn path=/vendor/subversion/dist/; revision=251881 svn path=/vendor/subversion/subversion-1.8.0-rc3/; revision=251882; tag=vendor/subversion/subversion-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_ra_serf/options.c')
-rw-r--r--subversion/libsvn_ra_serf/options.c625
1 files changed, 625 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_serf/options.c b/subversion/libsvn_ra_serf/options.c
new file mode 100644
index 000000000000..0af0b1599c6d
--- /dev/null
+++ b/subversion/libsvn_ra_serf/options.c
@@ -0,0 +1,625 @@
+/*
+ * 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_ra.h"
+#include "svn_dav.h"
+#include "svn_xml.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 = 0,
+ 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__connection_t *conn;
+ svn_ra_serf__handler_t *handler;
+
+ svn_ra_serf__response_handler_t inner_handler;
+ void *inner_baton;
+
+ 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;
+}
+
+
+static svn_error_t *
+create_options_body(serf_bucket_t **body_bkt,
+ void *baton,
+ serf_bucket_alloc_t *alloc,
+ apr_pool_t *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_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);
+
+ /* 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;
+ }
+ }
+
+ /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
+ else if (strncmp(key, "SVN", 3) == 0)
+ {
+ /* 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,
+ opt_ctx->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);
+
+ /* 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);
+
+ /* Then see which ones we can discover. */
+ serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
+ opt_ctx);
+
+ /* Assume mergeinfo capability unsupported, if didn't recieve 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);
+
+ 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,
+ svn_ra_serf__connection_t *conn,
+ 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->conn = conn;
+
+ 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(xmlctx, 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;
+
+ 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__connection_t *conn,
+ 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(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
+ SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline.code,
+ opt_ctx->handler->path,
+ opt_ctx->handler->location));
+
+ *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__connection_t *conn,
+ 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));
+ 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.code,
+ opt_ctx->handler->path,
+ opt_ctx->handler->location));
+
+ *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,
+ apr_pool_t *pool)
+{
+ options_context_t *opt_ctx;
+ svn_error_t *err;
+
+ /* This routine automatically fills in serf_sess->capabilities */
+ SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool));
+
+ err = svn_ra_serf__context_run_one(opt_ctx->handler, 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))
+ {
+ svn_error_clear(err);
+ *corrected_url = opt_ctx->handler->location;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_error_compose_create(
+ svn_ra_serf__error_on_status(opt_ctx->handler->sline.code,
+ serf_sess->session_url.path,
+ opt_ctx->handler->location),
+ err));
+
+ /* 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;
+}
+
+
+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, 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,
+ FALSE, FALSE, 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 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;
+}