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"
36 #include "svn_ctype.h"
38 #include "../libsvn_ra/ra_loader.h"
39 #include "svn_private_config.h"
40 #include "private/svn_fspath.h"
45 /* In a debug build, setting this environment variable to "yes" will force
46 the client to speak v1, even if the server is capable of speaking v2. */
47 #define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
51 * This enum represents the current state of our XML parsing for an OPTIONS.
53 enum options_state_e {
60 typedef struct options_context_t {
61 /* pool to allocate memory from */
64 /* Have we extracted options values from the headers already? */
65 svn_boolean_t headers_processed;
67 svn_ra_serf__session_t *session;
68 svn_ra_serf__connection_t *conn;
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,
117 create_options_body(serf_bucket_t **body_bkt,
119 serf_bucket_alloc_t *alloc,
123 body = serf_bucket_aggregate_create(alloc);
124 svn_ra_serf__add_xml_header_buckets(body, alloc);
125 svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
128 svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
129 svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
136 /* We use these static pointers so we can employ pointer comparison
137 * of our capabilities hash members instead of strcmp()ing all over
140 /* Both server and repository support the capability. */
141 static const char *const capability_yes = "yes";
142 /* Either server or repository does not support the capability. */
143 static const char *const capability_no = "no";
144 /* Server supports the capability, but don't yet know if repository does. */
145 static const char *const capability_server_yes = "server-yes";
148 /* This implements serf_bucket_headers_do_callback_fn_t.
151 capabilities_headers_iterator_callback(void *baton,
155 options_context_t *opt_ctx = baton;
156 svn_ra_serf__session_t *session = opt_ctx->session;
158 if (svn_cstring_casecmp(key, "dav") == 0)
160 /* Each header may contain multiple values, separated by commas, e.g.:
161 DAV: version-control,checkout,working-resource
162 DAV: merge,baseline,activity,version-controlled-collection
163 DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
164 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
167 /* Right now we only have a few capabilities to detect, so just
168 seek for them directly. This could be written slightly more
169 efficiently, but that wouldn't be worth it until we have many
170 more capabilities. */
172 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
174 svn_hash_sets(session->capabilities,
175 SVN_RA_CAPABILITY_DEPTH, capability_yes);
177 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
179 /* The server doesn't know what repository we're referring
180 to, so it can't just say capability_yes. */
181 if (!svn_hash_gets(session->capabilities,
182 SVN_RA_CAPABILITY_MERGEINFO))
184 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
185 capability_server_yes);
188 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
190 svn_hash_sets(session->capabilities,
191 SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
193 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
195 svn_hash_sets(session->capabilities,
196 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
198 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
200 svn_hash_sets(session->capabilities,
201 SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
203 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
205 svn_hash_sets(session->capabilities,
206 SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
208 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
211 svn_hash_sets(session->capabilities,
212 SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
215 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
217 svn_hash_sets(session->capabilities,
218 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
220 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
222 session->supports_inline_props = TRUE;
224 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
226 session->supports_rev_rsrc_replay = TRUE;
230 /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
231 else if (!svn_ctype_casecmp(key[0], 'S')
232 && !svn_ctype_casecmp(key[1], 'V')
233 && !svn_ctype_casecmp(key[2], 'N'))
235 /* If we've not yet seen any information about supported POST
236 requests, we'll initialize the list/hash with "create-txn"
237 (which we know is supported by virtue of the server speaking
239 if (! session->supported_posts)
241 session->supported_posts = apr_hash_make(session->pool);
242 apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
245 if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
247 session->repos_root = session->session_url;
248 session->repos_root.path =
249 (char *)svn_fspath__canonicalize(val, session->pool);
250 session->repos_root_str =
251 svn_urlpath__canonicalize(
252 apr_uri_unparse(session->pool, &session->repos_root, 0),
255 else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
258 char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
260 if (!(ignore_v2_env_var
261 && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
262 session->me_resource = apr_pstrdup(session->pool, val);
264 session->me_resource = apr_pstrdup(session->pool, val);
267 else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
269 session->rev_stub = apr_pstrdup(session->pool, val);
271 else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
273 session->rev_root_stub = apr_pstrdup(session->pool, val);
275 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
277 session->txn_stub = apr_pstrdup(session->pool, val);
279 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
281 session->txn_root_stub = apr_pstrdup(session->pool, val);
283 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
285 session->vtxn_stub = apr_pstrdup(session->pool, val);
287 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
289 session->vtxn_root_stub = apr_pstrdup(session->pool, val);
291 else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
293 session->uuid = apr_pstrdup(session->pool, val);
295 else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
297 opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
299 else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
301 session->server_allows_bulk = apr_pstrdup(session->pool, val);
303 else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
305 /* May contain multiple values, separated by commas. */
307 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
310 for (i = 0; i < vals->nelts; i++)
312 const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
314 svn_hash_sets(session->supported_posts, post_val, (void *)1);
317 else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
319 if (svn_cstring_casecmp(val, "yes") == 0)
321 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
324 else if (svn_cstring_casecmp(val, "no") == 0)
326 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
336 /* A custom serf_response_handler_t which is mostly a wrapper around
337 the expat-based response handler -- it just notices OPTIONS response
338 headers first, before handing off to the xml parser.
339 Implements svn_ra_serf__response_handler_t */
341 options_response_handler(serf_request_t *request,
342 serf_bucket_t *response,
346 options_context_t *opt_ctx = baton;
348 if (!opt_ctx->headers_processed)
350 svn_ra_serf__session_t *session = opt_ctx->session;
351 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
353 /* Start out assuming all capabilities are unsupported. */
354 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
356 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
358 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
360 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
362 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
364 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
366 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
368 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
371 /* Then see which ones we can discover. */
372 serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
375 /* Assume mergeinfo capability unsupported, if didn't recieve information
376 about server or repository mergeinfo capability. */
377 if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
378 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
381 opt_ctx->headers_processed = TRUE;
384 /* Execute the 'real' response handler to XML-parse the response body. */
385 return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
390 create_options_req(options_context_t **opt_ctx,
391 svn_ra_serf__session_t *session,
392 svn_ra_serf__connection_t *conn,
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;
402 new_ctx->conn = conn;
404 new_ctx->youngest_rev = SVN_INVALID_REVNUM;
406 xmlctx = svn_ra_serf__xml_context_create(options_ttable,
407 NULL, options_closed, NULL,
410 handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
412 handler->method = "OPTIONS";
413 handler->path = session->session_url.path;
414 handler->body_delegate = create_options_body;
415 handler->body_type = "text/xml";
416 handler->conn = conn;
417 handler->session = session;
419 new_ctx->handler = handler;
421 new_ctx->inner_handler = handler->response_handler;
422 new_ctx->inner_baton = handler->response_baton;
423 handler->response_handler = options_response_handler;
424 handler->response_baton = new_ctx;
433 svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
434 svn_ra_serf__connection_t *conn,
435 apr_pool_t *scratch_pool)
437 svn_ra_serf__session_t *session = conn->session;
438 options_context_t *opt_ctx;
440 SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
442 SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
443 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
444 SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
445 opt_ctx->handler->path,
446 opt_ctx->handler->location));
448 *youngest = opt_ctx->youngest_rev;
449 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*youngest));
456 svn_ra_serf__v1_get_activity_collection(const char **activity_url,
457 svn_ra_serf__connection_t *conn,
458 apr_pool_t *result_pool,
459 apr_pool_t *scratch_pool)
461 svn_ra_serf__session_t *session = conn->session;
462 options_context_t *opt_ctx;
464 SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
466 SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
467 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
469 SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
470 opt_ctx->handler->path,
471 opt_ctx->handler->location));
473 *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
481 /** Capabilities exchange. */
484 svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
485 const char **corrected_url,
488 options_context_t *opt_ctx;
491 /* This routine automatically fills in serf_sess->capabilities */
492 SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool));
494 err = svn_ra_serf__context_run_one(opt_ctx->handler, pool);
496 /* If our caller cares about server redirections, and our response
497 carries such a thing, report as much. We'll disregard ERR --
498 it's most likely just a complaint about the response body not
499 successfully parsing as XML or somesuch. */
500 if (corrected_url && (opt_ctx->handler->sline.code == 301))
502 svn_error_clear(err);
503 *corrected_url = opt_ctx->handler->location;
507 SVN_ERR(svn_error_compose_create(
508 svn_ra_serf__error_on_status(opt_ctx->handler->sline,
509 serf_sess->session_url.path,
510 opt_ctx->handler->location),
513 /* Opportunistically cache any reported activity URL. (We don't
514 want to have to ask for this again later, potentially against an
515 unreadable commit anchor URL.) */
516 if (opt_ctx->activity_collection)
518 serf_sess->activity_collection_url =
519 apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
527 create_simple_options_body(serf_bucket_t **body_bkt,
529 serf_bucket_alloc_t *alloc,
535 body = serf_bucket_aggregate_create(alloc);
536 svn_ra_serf__add_xml_header_buckets(body, alloc);
538 s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
539 serf_bucket_aggregate_append(body, s);
547 svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
548 apr_pool_t *scratch_pool)
550 svn_ra_serf__handler_t *handler;
552 handler = apr_pcalloc(scratch_pool, sizeof(*handler));
553 handler->handler_pool = scratch_pool;
554 handler->method = "OPTIONS";
555 handler->path = serf_sess->session_url.path;
556 handler->conn = serf_sess->conns[0];
557 handler->session = serf_sess;
559 /* We don't care about the response body, so discard it. */
560 handler->response_handler = svn_ra_serf__handle_discard_body;
562 /* We need a simple body, in order to send it in chunked format. */
563 handler->body_delegate = create_simple_options_body;
565 /* No special headers. */
567 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
568 /* Some versions of nginx in reverse proxy mode will return 411. They want
569 a Content-Length header, rather than chunked requests. We can keep other
570 HTTP/1.1 features, but will disable the chunking. */
571 if (handler->sline.code == 411)
573 serf_sess->using_chunked_requests = FALSE;
577 SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
586 svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
588 const char *capability,
591 svn_ra_serf__session_t *serf_sess = ra_session->priv;
592 const char *cap_result;
594 /* This capability doesn't rely on anything server side. */
595 if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
601 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
603 /* If any capability is unknown, they're all unknown, so ask. */
604 if (cap_result == NULL)
605 SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool));
607 /* Try again, now that we've fetched the capabilities. */
608 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
610 /* Some capabilities depend on the repository as well as the server. */
611 if (cap_result == capability_server_yes)
613 if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
615 /* Handle mergeinfo specially. Mergeinfo depends on the
616 repository as well as the server, but the server routine
617 that answered our svn_ra_serf__exchange_capabilities() call above
618 didn't even know which repository we were interested in
619 -- it just told us whether the server supports mergeinfo.
620 If the answer was 'no', there's no point checking the
621 particular repository; but if it was 'yes', we still must
622 change it to 'no' iff the repository itself doesn't
623 support mergeinfo. */
624 svn_mergeinfo_catalog_t ignored;
626 apr_array_header_t *paths = apr_array_make(pool, 1,
628 APR_ARRAY_PUSH(paths, const char *) = "";
630 err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
635 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
637 svn_error_clear(err);
638 cap_result = capability_no;
640 else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
642 /* Mergeinfo requests use relative paths, and
643 anyway we're in r0, so this is a likely error,
644 but it means the repository supports mergeinfo! */
645 svn_error_clear(err);
646 cap_result = capability_yes;
652 cap_result = capability_yes;
654 svn_hash_sets(serf_sess->capabilities,
655 SVN_RA_CAPABILITY_MERGEINFO, cap_result);
659 return svn_error_createf
660 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
661 _("Don't know how to handle '%s' for capability '%s'"),
662 capability_server_yes, capability);
666 if (cap_result == capability_yes)
670 else if (cap_result == capability_no)
674 else if (cap_result == NULL)
676 return svn_error_createf
677 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
678 _("Don't know anything about capability '%s'"), capability);
680 else /* "can't happen" */
682 /* Well, let's hope it's a string. */
683 return svn_error_createf
684 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
685 _("Attempt to fetch capability '%s' resulted in '%s'"),
686 capability, cap_result);