2 * options.c : entry point for OPTIONS RA functions for ra_serf
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
30 #include "svn_dirent_uri.h"
32 #include "svn_pools.h"
37 #include "svn_ctype.h"
39 #include "../libsvn_ra/ra_loader.h"
40 #include "svn_private_config.h"
41 #include "private/svn_fspath.h"
46 /* In a debug build, setting this environment variable to "yes" will force
47 the client to speak v1, even if the server is capable of speaking v2. */
48 #define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
52 * This enum represents the current state of our XML parsing for an OPTIONS.
54 enum options_state_e {
55 INITIAL = XML_STATE_INITIAL,
61 typedef struct options_context_t {
62 /* pool to allocate memory from */
65 /* Have we extracted options values from the headers already? */
66 svn_boolean_t headers_processed;
68 svn_ra_serf__session_t *session;
69 svn_ra_serf__handler_t *handler;
71 svn_ra_serf__response_handler_t inner_handler;
74 const char *activity_collection;
75 svn_revnum_t youngest_rev;
80 #define S_ SVN_XML_NAMESPACE
81 static const svn_ra_serf__xml_transition_t options_ttable[] = {
82 { INITIAL, D_, "options-response", OPTIONS,
83 FALSE, { NULL }, FALSE },
85 { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
86 FALSE, { NULL }, FALSE },
88 { ACTIVITY_COLLECTION, D_, "href", HREF,
89 TRUE, { NULL }, TRUE },
95 /* Conforms to svn_ra_serf__xml_closed_t */
97 options_closed(svn_ra_serf__xml_estate_t *xes,
100 const svn_string_t *cdata,
102 apr_pool_t *scratch_pool)
104 options_context_t *opt_ctx = baton;
106 SVN_ERR_ASSERT(leaving_state == HREF);
107 SVN_ERR_ASSERT(cdata != NULL);
109 opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
115 /* Implements svn_ra_serf__request_body_delegate_t */
117 create_options_body(serf_bucket_t **body_bkt,
119 serf_bucket_alloc_t *alloc,
120 apr_pool_t *pool /* request pool */,
121 apr_pool_t *scratch_pool)
124 body = serf_bucket_aggregate_create(alloc);
125 svn_ra_serf__add_xml_header_buckets(body, alloc);
126 svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
129 svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
130 svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
137 /* We use these static pointers so we can employ pointer comparison
138 * of our capabilities hash members instead of strcmp()ing all over
141 /* Both server and repository support the capability. */
142 static const char *const capability_yes = "yes";
143 /* Either server or repository does not support the capability. */
144 static const char *const capability_no = "no";
145 /* Server supports the capability, but don't yet know if repository does. */
146 static const char *const capability_server_yes = "server-yes";
149 /* This implements serf_bucket_headers_do_callback_fn_t.
152 capabilities_headers_iterator_callback(void *baton,
156 options_context_t *opt_ctx = baton;
157 svn_ra_serf__session_t *session = opt_ctx->session;
159 if (svn_cstring_casecmp(key, "dav") == 0)
161 /* Each header may contain multiple values, separated by commas, e.g.:
162 DAV: version-control,checkout,working-resource
163 DAV: merge,baseline,activity,version-controlled-collection
164 DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
165 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
168 /* Right now we only have a few capabilities to detect, so just
169 seek for them directly. This could be written slightly more
170 efficiently, but that wouldn't be worth it until we have many
171 more capabilities. */
173 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
175 svn_hash_sets(session->capabilities,
176 SVN_RA_CAPABILITY_DEPTH, capability_yes);
178 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
180 /* The server doesn't know what repository we're referring
181 to, so it can't just say capability_yes. */
182 if (!svn_hash_gets(session->capabilities,
183 SVN_RA_CAPABILITY_MERGEINFO))
185 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
186 capability_server_yes);
189 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
191 svn_hash_sets(session->capabilities,
192 SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
194 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
196 svn_hash_sets(session->capabilities,
197 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
199 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
201 svn_hash_sets(session->capabilities,
202 SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
204 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
206 svn_hash_sets(session->capabilities,
207 SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
209 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
212 svn_hash_sets(session->capabilities,
213 SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
216 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
218 svn_hash_sets(session->capabilities,
219 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
221 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
223 session->supports_inline_props = TRUE;
225 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
227 session->supports_rev_rsrc_replay = TRUE;
229 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF1, vals))
231 /* Use compressed svndiff1 format for servers that properly
232 advertise this capability (Subversion 1.10 and greater). */
233 session->supports_svndiff1 = TRUE;
235 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LIST, vals))
237 svn_hash_sets(session->capabilities,
238 SVN_RA_CAPABILITY_LIST, capability_yes);
240 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF2, vals))
242 /* Same for svndiff2. */
243 session->supports_svndiff2 = TRUE;
245 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PUT_RESULT_CHECKSUM, vals))
247 session->supports_put_result_checksum = TRUE;
251 /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
252 else if (!svn_ctype_casecmp(key[0], 'S')
253 && !svn_ctype_casecmp(key[1], 'V')
254 && !svn_ctype_casecmp(key[2], 'N'))
256 /* If we've not yet seen any information about supported POST
257 requests, we'll initialize the list/hash with "create-txn"
258 (which we know is supported by virtue of the server speaking
260 if (! session->supported_posts)
262 session->supported_posts = apr_hash_make(session->pool);
263 apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
266 if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
268 session->repos_root = session->session_url;
269 session->repos_root.path =
270 (char *)svn_fspath__canonicalize(val, session->pool);
271 session->repos_root_str =
272 svn_urlpath__canonicalize(
273 apr_uri_unparse(session->pool, &session->repos_root, 0),
276 else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
279 char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
281 if (!(ignore_v2_env_var
282 && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
283 session->me_resource = apr_pstrdup(session->pool, val);
285 session->me_resource = apr_pstrdup(session->pool, val);
288 else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
290 session->rev_stub = apr_pstrdup(session->pool, val);
292 else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
294 session->rev_root_stub = apr_pstrdup(session->pool, val);
296 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
298 session->txn_stub = apr_pstrdup(session->pool, val);
300 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
302 session->txn_root_stub = apr_pstrdup(session->pool, val);
304 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
306 session->vtxn_stub = apr_pstrdup(session->pool, val);
308 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
310 session->vtxn_root_stub = apr_pstrdup(session->pool, val);
312 else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
314 session->uuid = apr_pstrdup(session->pool, val);
316 else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
318 opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
320 else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
322 session->server_allows_bulk = apr_pstrdup(session->pool, val);
324 else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
326 /* May contain multiple values, separated by commas. */
328 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
331 for (i = 0; i < vals->nelts; i++)
333 const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
335 svn_hash_sets(session->supported_posts, post_val, (void *)1);
338 else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
340 if (svn_cstring_casecmp(val, "yes") == 0)
342 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
345 else if (svn_cstring_casecmp(val, "no") == 0)
347 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
357 /* A custom serf_response_handler_t which is mostly a wrapper around
358 the expat-based response handler -- it just notices OPTIONS response
359 headers first, before handing off to the xml parser.
360 Implements svn_ra_serf__response_handler_t */
362 options_response_handler(serf_request_t *request,
363 serf_bucket_t *response,
367 options_context_t *opt_ctx = baton;
369 if (!opt_ctx->headers_processed)
371 svn_ra_serf__session_t *session = opt_ctx->session;
372 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
373 serf_connection_t *conn;
375 /* Start out assuming all capabilities are unsupported. */
376 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
378 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
380 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
382 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
384 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
386 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
388 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
390 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
392 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LIST,
395 /* Then see which ones we can discover. */
396 serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
399 /* Assume mergeinfo capability unsupported, if didn't receive information
400 about server or repository mergeinfo capability. */
401 if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
402 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
405 /* Remember our latency. */
406 conn = serf_request_get_conn(request);
407 session->conn_latency = serf_connection_get_latency(conn);
409 opt_ctx->headers_processed = TRUE;
412 /* Execute the 'real' response handler to XML-parse the response body. */
413 return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
418 create_options_req(options_context_t **opt_ctx,
419 svn_ra_serf__session_t *session,
422 options_context_t *new_ctx;
423 svn_ra_serf__xml_context_t *xmlctx;
424 svn_ra_serf__handler_t *handler;
426 new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
427 new_ctx->pool = pool;
428 new_ctx->session = session;
430 new_ctx->youngest_rev = SVN_INVALID_REVNUM;
432 xmlctx = svn_ra_serf__xml_context_create(options_ttable,
433 NULL, options_closed, NULL,
436 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
438 handler->method = "OPTIONS";
439 handler->path = session->session_url.path;
440 handler->body_delegate = create_options_body;
441 handler->body_type = "text/xml";
443 new_ctx->handler = handler;
445 new_ctx->inner_handler = handler->response_handler;
446 new_ctx->inner_baton = handler->response_baton;
447 handler->response_handler = options_response_handler;
448 handler->response_baton = new_ctx;
457 svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
458 svn_ra_serf__session_t *session,
459 apr_pool_t *scratch_pool)
461 options_context_t *opt_ctx;
463 SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
465 SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
466 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
468 if (opt_ctx->handler->sline.code != 200)
469 return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
471 if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev))
472 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
473 _("The OPTIONS response did not include "
474 "the youngest revision"));
476 *youngest = opt_ctx->youngest_rev;
483 svn_ra_serf__v1_get_activity_collection(const char **activity_url,
484 svn_ra_serf__session_t *session,
485 apr_pool_t *result_pool,
486 apr_pool_t *scratch_pool)
488 options_context_t *opt_ctx;
490 SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
492 if (session->activity_collection_url)
494 *activity_url = apr_pstrdup(result_pool,
495 session->activity_collection_url);
499 SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
500 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
502 if (opt_ctx->handler->sline.code != 200)
503 return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
505 /* Cache the result. */
506 if (opt_ctx->activity_collection)
508 session->activity_collection_url =
509 apr_pstrdup(session->pool, opt_ctx->activity_collection);
513 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
514 _("The OPTIONS response did not include the "
515 "requested activity-collection-set value"));
518 *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
526 /** Capabilities exchange. */
529 svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
530 const char **corrected_url,
531 apr_pool_t *result_pool,
532 apr_pool_t *scratch_pool)
534 options_context_t *opt_ctx;
537 *corrected_url = NULL;
539 /* This routine automatically fills in serf_sess->capabilities */
540 SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool));
542 opt_ctx->handler->no_fail_on_http_redirect_status = TRUE;
544 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
546 /* If our caller cares about server redirections, and our response
547 carries such a thing, report as much. We'll disregard ERR --
548 it's most likely just a complaint about the response body not
549 successfully parsing as XML or somesuch. */
550 if (corrected_url && (opt_ctx->handler->sline.code == 301))
552 if (!opt_ctx->handler->location || !*opt_ctx->handler->location)
554 return svn_error_create(
555 SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
556 _("Location header not set on redirect response"));
558 else if (svn_path_is_url(opt_ctx->handler->location))
560 *corrected_url = svn_uri_canonicalize(opt_ctx->handler->location,
565 /* RFC1945 and RFC2616 state that the Location header's value
566 (from whence this CORRECTED_URL comes), if present, must be an
567 absolute URI. But some Apache versions (those older than 2.2.11,
568 it seems) transmit only the path portion of the URI.
569 See issue #3775 for details. */
571 apr_uri_t corrected_URI = serf_sess->session_url;
573 corrected_URI.path = (char *)corrected_url;
574 *corrected_url = svn_uri_canonicalize(
575 apr_uri_unparse(scratch_pool, &corrected_URI, 0),
581 else if (opt_ctx->handler->sline.code >= 300
582 && opt_ctx->handler->sline.code < 399)
584 return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL,
585 (opt_ctx->handler->sline.code == 301
586 ? _("Repository moved permanently to '%s'")
587 : _("Repository moved temporarily to '%s'")),
588 opt_ctx->handler->location);
591 if (opt_ctx->handler->sline.code != 200)
592 return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
594 /* Opportunistically cache any reported activity URL. (We don't
595 want to have to ask for this again later, potentially against an
596 unreadable commit anchor URL.) */
597 if (opt_ctx->activity_collection)
599 serf_sess->activity_collection_url =
600 apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
606 /* Implements svn_ra_serf__request_body_delegate_t */
608 create_simple_options_body(serf_bucket_t **body_bkt,
610 serf_bucket_alloc_t *alloc,
611 apr_pool_t *pool /* request pool */,
612 apr_pool_t *scratch_pool)
617 body = serf_bucket_aggregate_create(alloc);
618 svn_ra_serf__add_xml_header_buckets(body, alloc);
620 s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
621 serf_bucket_aggregate_append(body, s);
629 svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
630 apr_pool_t *scratch_pool)
632 svn_ra_serf__handler_t *handler;
634 handler = svn_ra_serf__create_handler(serf_sess, scratch_pool);
635 handler->method = "OPTIONS";
636 handler->path = serf_sess->session_url.path;
638 /* We don't care about the response body, so discard it. */
639 handler->response_handler = svn_ra_serf__handle_discard_body;
641 /* We need a simple body, in order to send it in chunked format. */
642 handler->body_delegate = create_simple_options_body;
643 handler->no_fail_on_http_failure_status = TRUE;
645 /* No special headers. */
647 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
648 /* Some versions of nginx in reverse proxy mode will return 411. They want
649 a Content-Length header, rather than chunked requests. We can keep other
650 HTTP/1.1 features, but will disable the chunking. */
651 if (handler->sline.code == 411)
653 serf_sess->using_chunked_requests = FALSE;
657 if (handler->sline.code != 200)
658 SVN_ERR(svn_ra_serf__unexpected_status(handler));
665 svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
667 const char *capability,
670 svn_ra_serf__session_t *serf_sess = ra_session->priv;
671 const char *cap_result;
673 /* This capability doesn't rely on anything server side. */
674 if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
680 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
682 /* If any capability is unknown, they're all unknown, so ask. */
683 if (cap_result == NULL)
684 SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool, pool));
686 /* Try again, now that we've fetched the capabilities. */
687 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
689 /* Some capabilities depend on the repository as well as the server. */
690 if (cap_result == capability_server_yes)
692 if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
694 /* Handle mergeinfo specially. Mergeinfo depends on the
695 repository as well as the server, but the server routine
696 that answered our svn_ra_serf__exchange_capabilities() call above
697 didn't even know which repository we were interested in
698 -- it just told us whether the server supports mergeinfo.
699 If the answer was 'no', there's no point checking the
700 particular repository; but if it was 'yes', we still must
701 change it to 'no' iff the repository itself doesn't
702 support mergeinfo. */
703 svn_mergeinfo_catalog_t ignored;
705 apr_array_header_t *paths = apr_array_make(pool, 1,
707 APR_ARRAY_PUSH(paths, const char *) = "";
709 err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
710 svn_mergeinfo_explicit,
711 FALSE /* include_descendants */,
716 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
718 svn_error_clear(err);
719 cap_result = capability_no;
721 else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
723 /* Mergeinfo requests use relative paths, and
724 anyway we're in r0, so this is a likely error,
725 but it means the repository supports mergeinfo! */
726 svn_error_clear(err);
727 cap_result = capability_yes;
730 return svn_error_trace(err);
733 cap_result = capability_yes;
735 svn_hash_sets(serf_sess->capabilities,
736 SVN_RA_CAPABILITY_MERGEINFO, cap_result);
740 return svn_error_createf
741 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
742 _("Don't know how to handle '%s' for capability '%s'"),
743 capability_server_yes, capability);
747 if (cap_result == capability_yes)
751 else if (cap_result == capability_no)
755 else if (cap_result == NULL)
757 return svn_error_createf
758 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
759 _("Don't know anything about capability '%s'"), capability);
761 else /* "can't happen" */
763 /* Well, let's hope it's a string. */
764 return svn_error_createf
765 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
766 _("Attempt to fetch capability '%s' resulted in '%s'"),
767 capability, cap_result);