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 "../libsvn_ra/ra_loader.h"
38 #include "svn_private_config.h"
39 #include "private/svn_fspath.h"
44 /* In a debug build, setting this environment variable to "yes" will force
45 the client to speak v1, even if the server is capable of speaking v2. */
46 #define SVN_IGNORE_V2_ENV_VAR "SVN_I_LIKE_LATENCY_SO_IGNORE_HTTPV2"
50 * This enum represents the current state of our XML parsing for an OPTIONS.
52 enum options_state_e {
59 typedef struct options_context_t {
60 /* pool to allocate memory from */
63 /* Have we extracted options values from the headers already? */
64 svn_boolean_t headers_processed;
66 svn_ra_serf__session_t *session;
67 svn_ra_serf__connection_t *conn;
68 svn_ra_serf__handler_t *handler;
70 svn_ra_serf__response_handler_t inner_handler;
73 const char *activity_collection;
74 svn_revnum_t youngest_rev;
79 #define S_ SVN_XML_NAMESPACE
80 static const svn_ra_serf__xml_transition_t options_ttable[] = {
81 { INITIAL, D_, "options-response", OPTIONS,
82 FALSE, { NULL }, FALSE },
84 { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
85 FALSE, { NULL }, FALSE },
87 { ACTIVITY_COLLECTION, D_, "href", HREF,
88 TRUE, { NULL }, TRUE },
94 /* Conforms to svn_ra_serf__xml_closed_t */
96 options_closed(svn_ra_serf__xml_estate_t *xes,
99 const svn_string_t *cdata,
101 apr_pool_t *scratch_pool)
103 options_context_t *opt_ctx = baton;
105 SVN_ERR_ASSERT(leaving_state == HREF);
106 SVN_ERR_ASSERT(cdata != NULL);
108 opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
116 create_options_body(serf_bucket_t **body_bkt,
118 serf_bucket_alloc_t *alloc,
122 body = serf_bucket_aggregate_create(alloc);
123 svn_ra_serf__add_xml_header_buckets(body, alloc);
124 svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
127 svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
128 svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
135 /* We use these static pointers so we can employ pointer comparison
136 * of our capabilities hash members instead of strcmp()ing all over
139 /* Both server and repository support the capability. */
140 static const char *const capability_yes = "yes";
141 /* Either server or repository does not support the capability. */
142 static const char *const capability_no = "no";
143 /* Server supports the capability, but don't yet know if repository does. */
144 static const char *const capability_server_yes = "server-yes";
147 /* This implements serf_bucket_headers_do_callback_fn_t.
150 capabilities_headers_iterator_callback(void *baton,
154 options_context_t *opt_ctx = baton;
155 svn_ra_serf__session_t *session = opt_ctx->session;
157 if (svn_cstring_casecmp(key, "dav") == 0)
159 /* Each header may contain multiple values, separated by commas, e.g.:
160 DAV: version-control,checkout,working-resource
161 DAV: merge,baseline,activity,version-controlled-collection
162 DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
163 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
166 /* Right now we only have a few capabilities to detect, so just
167 seek for them directly. This could be written slightly more
168 efficiently, but that wouldn't be worth it until we have many
169 more capabilities. */
171 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
173 svn_hash_sets(session->capabilities,
174 SVN_RA_CAPABILITY_DEPTH, capability_yes);
176 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
178 /* The server doesn't know what repository we're referring
179 to, so it can't just say capability_yes. */
180 if (!svn_hash_gets(session->capabilities,
181 SVN_RA_CAPABILITY_MERGEINFO))
183 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
184 capability_server_yes);
187 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
189 svn_hash_sets(session->capabilities,
190 SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
192 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
194 svn_hash_sets(session->capabilities,
195 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
197 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
199 svn_hash_sets(session->capabilities,
200 SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
202 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
204 svn_hash_sets(session->capabilities,
205 SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
207 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
210 svn_hash_sets(session->capabilities,
211 SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
214 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
216 svn_hash_sets(session->capabilities,
217 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
219 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
221 session->supports_inline_props = TRUE;
223 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
225 session->supports_rev_rsrc_replay = TRUE;
229 /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
230 else if (strncmp(key, "SVN", 3) == 0)
232 /* If we've not yet seen any information about supported POST
233 requests, we'll initialize the list/hash with "create-txn"
234 (which we know is supported by virtue of the server speaking
236 if (! session->supported_posts)
238 session->supported_posts = apr_hash_make(session->pool);
239 apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
242 if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
244 session->repos_root = session->session_url;
245 session->repos_root.path =
246 (char *)svn_fspath__canonicalize(val, session->pool);
247 session->repos_root_str =
248 svn_urlpath__canonicalize(
249 apr_uri_unparse(session->pool, &session->repos_root, 0),
252 else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
255 char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
257 if (!(ignore_v2_env_var
258 && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
259 session->me_resource = apr_pstrdup(session->pool, val);
261 session->me_resource = apr_pstrdup(session->pool, val);
264 else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
266 session->rev_stub = apr_pstrdup(session->pool, val);
268 else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
270 session->rev_root_stub = apr_pstrdup(session->pool, val);
272 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
274 session->txn_stub = apr_pstrdup(session->pool, val);
276 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
278 session->txn_root_stub = apr_pstrdup(session->pool, val);
280 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
282 session->vtxn_stub = apr_pstrdup(session->pool, val);
284 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
286 session->vtxn_root_stub = apr_pstrdup(session->pool, val);
288 else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
290 session->uuid = apr_pstrdup(session->pool, val);
292 else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
294 opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
296 else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
298 session->server_allows_bulk = apr_pstrdup(session->pool, val);
300 else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
302 /* May contain multiple values, separated by commas. */
304 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
307 for (i = 0; i < vals->nelts; i++)
309 const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
311 svn_hash_sets(session->supported_posts, post_val, (void *)1);
314 else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
316 if (svn_cstring_casecmp(val, "yes") == 0)
318 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
321 else if (svn_cstring_casecmp(val, "no") == 0)
323 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
333 /* A custom serf_response_handler_t which is mostly a wrapper around
334 the expat-based response handler -- it just notices OPTIONS response
335 headers first, before handing off to the xml parser.
336 Implements svn_ra_serf__response_handler_t */
338 options_response_handler(serf_request_t *request,
339 serf_bucket_t *response,
343 options_context_t *opt_ctx = baton;
345 if (!opt_ctx->headers_processed)
347 svn_ra_serf__session_t *session = opt_ctx->session;
348 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
350 /* Start out assuming all capabilities are unsupported. */
351 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
353 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
355 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
357 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
359 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
361 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
363 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
365 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
368 /* Then see which ones we can discover. */
369 serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
372 /* Assume mergeinfo capability unsupported, if didn't recieve information
373 about server or repository mergeinfo capability. */
374 if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
375 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
378 opt_ctx->headers_processed = TRUE;
381 /* Execute the 'real' response handler to XML-parse the response body. */
382 return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
387 create_options_req(options_context_t **opt_ctx,
388 svn_ra_serf__session_t *session,
389 svn_ra_serf__connection_t *conn,
392 options_context_t *new_ctx;
393 svn_ra_serf__xml_context_t *xmlctx;
394 svn_ra_serf__handler_t *handler;
396 new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
397 new_ctx->pool = pool;
398 new_ctx->session = session;
399 new_ctx->conn = conn;
401 new_ctx->youngest_rev = SVN_INVALID_REVNUM;
403 xmlctx = svn_ra_serf__xml_context_create(options_ttable,
404 NULL, options_closed, NULL,
407 handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
409 handler->method = "OPTIONS";
410 handler->path = session->session_url.path;
411 handler->body_delegate = create_options_body;
412 handler->body_type = "text/xml";
413 handler->conn = conn;
414 handler->session = session;
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__connection_t *conn,
432 apr_pool_t *scratch_pool)
434 svn_ra_serf__session_t *session = conn->session;
435 options_context_t *opt_ctx;
437 SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
439 SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
440 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
441 SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
442 opt_ctx->handler->path,
443 opt_ctx->handler->location));
445 *youngest = opt_ctx->youngest_rev;
446 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(*youngest));
453 svn_ra_serf__v1_get_activity_collection(const char **activity_url,
454 svn_ra_serf__connection_t *conn,
455 apr_pool_t *result_pool,
456 apr_pool_t *scratch_pool)
458 svn_ra_serf__session_t *session = conn->session;
459 options_context_t *opt_ctx;
461 SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
463 SVN_ERR(create_options_req(&opt_ctx, session, conn, scratch_pool));
464 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
466 SVN_ERR(svn_ra_serf__error_on_status(opt_ctx->handler->sline,
467 opt_ctx->handler->path,
468 opt_ctx->handler->location));
470 *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
478 /** Capabilities exchange. */
481 svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
482 const char **corrected_url,
485 options_context_t *opt_ctx;
488 /* This routine automatically fills in serf_sess->capabilities */
489 SVN_ERR(create_options_req(&opt_ctx, serf_sess, serf_sess->conns[0], pool));
491 err = svn_ra_serf__context_run_one(opt_ctx->handler, pool);
493 /* If our caller cares about server redirections, and our response
494 carries such a thing, report as much. We'll disregard ERR --
495 it's most likely just a complaint about the response body not
496 successfully parsing as XML or somesuch. */
497 if (corrected_url && (opt_ctx->handler->sline.code == 301))
499 svn_error_clear(err);
500 *corrected_url = opt_ctx->handler->location;
504 SVN_ERR(svn_error_compose_create(
505 svn_ra_serf__error_on_status(opt_ctx->handler->sline,
506 serf_sess->session_url.path,
507 opt_ctx->handler->location),
510 /* Opportunistically cache any reported activity URL. (We don't
511 want to have to ask for this again later, potentially against an
512 unreadable commit anchor URL.) */
513 if (opt_ctx->activity_collection)
515 serf_sess->activity_collection_url =
516 apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
524 create_simple_options_body(serf_bucket_t **body_bkt,
526 serf_bucket_alloc_t *alloc,
532 body = serf_bucket_aggregate_create(alloc);
533 svn_ra_serf__add_xml_header_buckets(body, alloc);
535 s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
536 serf_bucket_aggregate_append(body, s);
544 svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
545 apr_pool_t *scratch_pool)
547 svn_ra_serf__handler_t *handler;
549 handler = apr_pcalloc(scratch_pool, sizeof(*handler));
550 handler->handler_pool = scratch_pool;
551 handler->method = "OPTIONS";
552 handler->path = serf_sess->session_url.path;
553 handler->conn = serf_sess->conns[0];
554 handler->session = serf_sess;
556 /* We don't care about the response body, so discard it. */
557 handler->response_handler = svn_ra_serf__handle_discard_body;
559 /* We need a simple body, in order to send it in chunked format. */
560 handler->body_delegate = create_simple_options_body;
562 /* No special headers. */
564 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
565 /* Some versions of nginx in reverse proxy mode will return 411. They want
566 a Content-Length header, rather than chunked requests. We can keep other
567 HTTP/1.1 features, but will disable the chunking. */
568 if (handler->sline.code == 411)
570 serf_sess->using_chunked_requests = FALSE;
574 SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
583 svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
585 const char *capability,
588 svn_ra_serf__session_t *serf_sess = ra_session->priv;
589 const char *cap_result;
591 /* This capability doesn't rely on anything server side. */
592 if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
598 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
600 /* If any capability is unknown, they're all unknown, so ask. */
601 if (cap_result == NULL)
602 SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, pool));
604 /* Try again, now that we've fetched the capabilities. */
605 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
607 /* Some capabilities depend on the repository as well as the server. */
608 if (cap_result == capability_server_yes)
610 if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
612 /* Handle mergeinfo specially. Mergeinfo depends on the
613 repository as well as the server, but the server routine
614 that answered our svn_ra_serf__exchange_capabilities() call above
615 didn't even know which repository we were interested in
616 -- it just told us whether the server supports mergeinfo.
617 If the answer was 'no', there's no point checking the
618 particular repository; but if it was 'yes', we still must
619 change it to 'no' iff the repository itself doesn't
620 support mergeinfo. */
621 svn_mergeinfo_catalog_t ignored;
623 apr_array_header_t *paths = apr_array_make(pool, 1,
625 APR_ARRAY_PUSH(paths, const char *) = "";
627 err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
632 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
634 svn_error_clear(err);
635 cap_result = capability_no;
637 else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
639 /* Mergeinfo requests use relative paths, and
640 anyway we're in r0, so this is a likely error,
641 but it means the repository supports mergeinfo! */
642 svn_error_clear(err);
643 cap_result = capability_yes;
649 cap_result = capability_yes;
651 svn_hash_sets(serf_sess->capabilities,
652 SVN_RA_CAPABILITY_MERGEINFO, cap_result);
656 return svn_error_createf
657 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
658 _("Don't know how to handle '%s' for capability '%s'"),
659 capability_server_yes, capability);
663 if (cap_result == capability_yes)
667 else if (cap_result == capability_no)
671 else if (cap_result == NULL)
673 return svn_error_createf
674 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
675 _("Don't know anything about capability '%s'"), capability);
677 else /* "can't happen" */
679 /* Well, let's hope it's a string. */
680 return svn_error_createf
681 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
682 _("Attempt to fetch capability '%s' resulted in '%s'"),
683 capability, cap_result);