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;
231 /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
232 else if (!svn_ctype_casecmp(key[0], 'S')
233 && !svn_ctype_casecmp(key[1], 'V')
234 && !svn_ctype_casecmp(key[2], 'N'))
236 /* If we've not yet seen any information about supported POST
237 requests, we'll initialize the list/hash with "create-txn"
238 (which we know is supported by virtue of the server speaking
240 if (! session->supported_posts)
242 session->supported_posts = apr_hash_make(session->pool);
243 apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
246 if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
248 session->repos_root = session->session_url;
249 session->repos_root.path =
250 (char *)svn_fspath__canonicalize(val, session->pool);
251 session->repos_root_str =
252 svn_urlpath__canonicalize(
253 apr_uri_unparse(session->pool, &session->repos_root, 0),
256 else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
259 char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
261 if (!(ignore_v2_env_var
262 && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
263 session->me_resource = apr_pstrdup(session->pool, val);
265 session->me_resource = apr_pstrdup(session->pool, val);
268 else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
270 session->rev_stub = apr_pstrdup(session->pool, val);
272 else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
274 session->rev_root_stub = apr_pstrdup(session->pool, val);
276 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
278 session->txn_stub = apr_pstrdup(session->pool, val);
280 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
282 session->txn_root_stub = apr_pstrdup(session->pool, val);
284 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
286 session->vtxn_stub = apr_pstrdup(session->pool, val);
288 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
290 session->vtxn_root_stub = apr_pstrdup(session->pool, val);
292 else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
294 session->uuid = apr_pstrdup(session->pool, val);
296 else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
298 opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
300 else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
302 session->server_allows_bulk = apr_pstrdup(session->pool, val);
304 else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
306 /* May contain multiple values, separated by commas. */
308 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
311 for (i = 0; i < vals->nelts; i++)
313 const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
315 svn_hash_sets(session->supported_posts, post_val, (void *)1);
318 else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
320 if (svn_cstring_casecmp(val, "yes") == 0)
322 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
325 else if (svn_cstring_casecmp(val, "no") == 0)
327 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
337 /* A custom serf_response_handler_t which is mostly a wrapper around
338 the expat-based response handler -- it just notices OPTIONS response
339 headers first, before handing off to the xml parser.
340 Implements svn_ra_serf__response_handler_t */
342 options_response_handler(serf_request_t *request,
343 serf_bucket_t *response,
347 options_context_t *opt_ctx = baton;
349 if (!opt_ctx->headers_processed)
351 svn_ra_serf__session_t *session = opt_ctx->session;
352 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
354 /* Start out assuming all capabilities are unsupported. */
355 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
357 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
359 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
361 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
363 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
365 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
367 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
369 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
372 /* Then see which ones we can discover. */
373 serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
376 /* Assume mergeinfo capability unsupported, if didn't receive information
377 about server or repository mergeinfo capability. */
378 if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
379 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
382 opt_ctx->headers_processed = TRUE;
385 /* Execute the 'real' response handler to XML-parse the response body. */
386 return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
391 create_options_req(options_context_t **opt_ctx,
392 svn_ra_serf__session_t *session,
395 options_context_t *new_ctx;
396 svn_ra_serf__xml_context_t *xmlctx;
397 svn_ra_serf__handler_t *handler;
399 new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
400 new_ctx->pool = pool;
401 new_ctx->session = session;
403 new_ctx->youngest_rev = SVN_INVALID_REVNUM;
405 xmlctx = svn_ra_serf__xml_context_create(options_ttable,
406 NULL, options_closed, NULL,
409 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
411 handler->method = "OPTIONS";
412 handler->path = session->session_url.path;
413 handler->body_delegate = create_options_body;
414 handler->body_type = "text/xml";
416 new_ctx->handler = handler;
418 new_ctx->inner_handler = handler->response_handler;
419 new_ctx->inner_baton = handler->response_baton;
420 handler->response_handler = options_response_handler;
421 handler->response_baton = new_ctx;
430 svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
431 svn_ra_serf__session_t *session,
432 apr_pool_t *scratch_pool)
434 options_context_t *opt_ctx;
436 SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
438 SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
439 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
441 if (opt_ctx->handler->sline.code != 200)
442 return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
444 if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev))
445 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
446 _("The OPTIONS response did not include "
447 "the youngest revision"));
449 *youngest = opt_ctx->youngest_rev;
456 svn_ra_serf__v1_get_activity_collection(const char **activity_url,
457 svn_ra_serf__session_t *session,
458 apr_pool_t *result_pool,
459 apr_pool_t *scratch_pool)
461 options_context_t *opt_ctx;
463 SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
465 if (session->activity_collection_url)
467 *activity_url = apr_pstrdup(result_pool,
468 session->activity_collection_url);
472 SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
473 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
475 if (opt_ctx->handler->sline.code != 200)
476 return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
478 /* Cache the result. */
479 if (opt_ctx->activity_collection)
481 session->activity_collection_url =
482 apr_pstrdup(session->pool, opt_ctx->activity_collection);
486 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
487 _("The OPTIONS response did not include the "
488 "requested activity-collection-set value"));
491 *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
499 /** Capabilities exchange. */
502 svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
503 const char **corrected_url,
504 apr_pool_t *result_pool,
505 apr_pool_t *scratch_pool)
507 options_context_t *opt_ctx;
510 *corrected_url = NULL;
512 /* This routine automatically fills in serf_sess->capabilities */
513 SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool));
515 opt_ctx->handler->no_fail_on_http_redirect_status = TRUE;
517 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
519 /* If our caller cares about server redirections, and our response
520 carries such a thing, report as much. We'll disregard ERR --
521 it's most likely just a complaint about the response body not
522 successfully parsing as XML or somesuch. */
523 if (corrected_url && (opt_ctx->handler->sline.code == 301))
525 if (!opt_ctx->handler->location || !*opt_ctx->handler->location)
527 return svn_error_create(
528 SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
529 _("Location header not set on redirect response"));
531 else if (svn_path_is_url(opt_ctx->handler->location))
533 *corrected_url = svn_uri_canonicalize(opt_ctx->handler->location,
538 /* RFC1945 and RFC2616 state that the Location header's value
539 (from whence this CORRECTED_URL comes), if present, must be an
540 absolute URI. But some Apache versions (those older than 2.2.11,
541 it seems) transmit only the path portion of the URI.
542 See issue #3775 for details. */
544 apr_uri_t corrected_URI = serf_sess->session_url;
546 corrected_URI.path = (char *)corrected_url;
547 *corrected_url = svn_uri_canonicalize(
548 apr_uri_unparse(scratch_pool, &corrected_URI, 0),
554 else if (opt_ctx->handler->sline.code >= 300
555 && opt_ctx->handler->sline.code < 399)
557 return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL,
558 (opt_ctx->handler->sline.code == 301
559 ? _("Repository moved permanently to '%s'")
560 : _("Repository moved temporarily to '%s'")),
561 opt_ctx->handler->location);
564 if (opt_ctx->handler->sline.code != 200)
565 return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
567 /* Opportunistically cache any reported activity URL. (We don't
568 want to have to ask for this again later, potentially against an
569 unreadable commit anchor URL.) */
570 if (opt_ctx->activity_collection)
572 serf_sess->activity_collection_url =
573 apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
579 /* Implements svn_ra_serf__request_body_delegate_t */
581 create_simple_options_body(serf_bucket_t **body_bkt,
583 serf_bucket_alloc_t *alloc,
584 apr_pool_t *pool /* request pool */,
585 apr_pool_t *scratch_pool)
590 body = serf_bucket_aggregate_create(alloc);
591 svn_ra_serf__add_xml_header_buckets(body, alloc);
593 s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
594 serf_bucket_aggregate_append(body, s);
602 svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
603 apr_pool_t *scratch_pool)
605 svn_ra_serf__handler_t *handler;
607 handler = svn_ra_serf__create_handler(serf_sess, scratch_pool);
608 handler->method = "OPTIONS";
609 handler->path = serf_sess->session_url.path;
611 /* We don't care about the response body, so discard it. */
612 handler->response_handler = svn_ra_serf__handle_discard_body;
614 /* We need a simple body, in order to send it in chunked format. */
615 handler->body_delegate = create_simple_options_body;
616 handler->no_fail_on_http_failure_status = TRUE;
618 /* No special headers. */
620 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
621 /* Some versions of nginx in reverse proxy mode will return 411. They want
622 a Content-Length header, rather than chunked requests. We can keep other
623 HTTP/1.1 features, but will disable the chunking. */
624 if (handler->sline.code == 411)
626 serf_sess->using_chunked_requests = FALSE;
630 if (handler->sline.code != 200)
631 SVN_ERR(svn_ra_serf__unexpected_status(handler));
638 svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
640 const char *capability,
643 svn_ra_serf__session_t *serf_sess = ra_session->priv;
644 const char *cap_result;
646 /* This capability doesn't rely on anything server side. */
647 if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
653 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
655 /* If any capability is unknown, they're all unknown, so ask. */
656 if (cap_result == NULL)
657 SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool, pool));
659 /* Try again, now that we've fetched the capabilities. */
660 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
662 /* Some capabilities depend on the repository as well as the server. */
663 if (cap_result == capability_server_yes)
665 if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
667 /* Handle mergeinfo specially. Mergeinfo depends on the
668 repository as well as the server, but the server routine
669 that answered our svn_ra_serf__exchange_capabilities() call above
670 didn't even know which repository we were interested in
671 -- it just told us whether the server supports mergeinfo.
672 If the answer was 'no', there's no point checking the
673 particular repository; but if it was 'yes', we still must
674 change it to 'no' iff the repository itself doesn't
675 support mergeinfo. */
676 svn_mergeinfo_catalog_t ignored;
678 apr_array_header_t *paths = apr_array_make(pool, 1,
680 APR_ARRAY_PUSH(paths, const char *) = "";
682 err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
683 svn_mergeinfo_explicit,
684 FALSE /* include_descendants */,
689 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
691 svn_error_clear(err);
692 cap_result = capability_no;
694 else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
696 /* Mergeinfo requests use relative paths, and
697 anyway we're in r0, so this is a likely error,
698 but it means the repository supports mergeinfo! */
699 svn_error_clear(err);
700 cap_result = capability_yes;
703 return svn_error_trace(err);
706 cap_result = capability_yes;
708 svn_hash_sets(serf_sess->capabilities,
709 SVN_RA_CAPABILITY_MERGEINFO, cap_result);
713 return svn_error_createf
714 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
715 _("Don't know how to handle '%s' for capability '%s'"),
716 capability_server_yes, capability);
720 if (cap_result == capability_yes)
724 else if (cap_result == capability_no)
728 else if (cap_result == NULL)
730 return svn_error_createf
731 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
732 _("Don't know anything about capability '%s'"), capability);
734 else /* "can't happen" */
736 /* Well, let's hope it's a string. */
737 return svn_error_createf
738 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
739 _("Attempt to fetch capability '%s' resulted in '%s'"),
740 capability, cap_result);