1 /*
2 * options.c : entry point for OPTIONS RA functions for ra_serf
3 *
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
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 * under the License.
21 * ====================================================================
22 */
23
24
25
26 #include <apr_uri.h>
27
28 #include <serf.h>
29
30 #include "svn_dirent_uri.h"
31 #include "svn_hash.h"
32 #include "svn_pools.h"
33 #include "svn_path.h"
34 #include "svn_ra.h"
35 #include "svn_dav.h"
36 #include "svn_xml.h"
37 #include "svn_ctype.h"
38
39 #include "../libsvn_ra/ra_loader.h"
40 #include "svn_private_config.h"
41 #include "private/svn_fspath.h"
42
43 #include "ra_serf.h"
44
45
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"
49
50
51 /*
52 * This enum represents the current state of our XML parsing for an OPTIONS.
53 */
54 enum options_state_e {
55 INITIAL = XML_STATE_INITIAL,
56 OPTIONS,
57 ACTIVITY_COLLECTION,
58 HREF
59 };
60
61 typedef struct options_context_t {
62 /* pool to allocate memory from */
63 apr_pool_t *pool;
64
65 /* Have we extracted options values from the headers already? */
66 svn_boolean_t headers_processed;
67
68 svn_ra_serf__session_t *session;
69 svn_ra_serf__handler_t *handler;
70
71 svn_ra_serf__response_handler_t inner_handler;
72 void *inner_baton;
73
74 /* Have we received any DAV headers at all? */
75 svn_boolean_t received_dav_header;
76
77 const char *activity_collection;
78 svn_revnum_t youngest_rev;
79
80 } options_context_t;
81
82 #define D_ "DAV:"
83 #define S_ SVN_XML_NAMESPACE
84 static const svn_ra_serf__xml_transition_t options_ttable[] = {
85 { INITIAL, D_, "options-response", OPTIONS,
86 FALSE, { NULL }, FALSE },
87
88 { OPTIONS, D_, "activity-collection-set", ACTIVITY_COLLECTION,
89 FALSE, { NULL }, FALSE },
90
91 { ACTIVITY_COLLECTION, D_, "href", HREF,
92 TRUE, { NULL }, TRUE },
93
94 { 0 }
95 };
96
97
98 /* Conforms to svn_ra_serf__xml_closed_t */
99 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)100 options_closed(svn_ra_serf__xml_estate_t *xes,
101 void *baton,
102 int leaving_state,
103 const svn_string_t *cdata,
104 apr_hash_t *attrs,
105 apr_pool_t *scratch_pool)
106 {
107 options_context_t *opt_ctx = baton;
108
109 SVN_ERR_ASSERT(leaving_state == HREF);
110 SVN_ERR_ASSERT(cdata != NULL);
111
112 opt_ctx->activity_collection = svn_urlpath__canonicalize(cdata->data,
113 opt_ctx->pool);
114
115 return SVN_NO_ERROR;
116 }
117
118 /* Implements svn_ra_serf__request_body_delegate_t */
119 static svn_error_t *
create_options_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)120 create_options_body(serf_bucket_t **body_bkt,
121 void *baton,
122 serf_bucket_alloc_t *alloc,
123 apr_pool_t *pool /* request pool */,
124 apr_pool_t *scratch_pool)
125 {
126 serf_bucket_t *body;
127 body = serf_bucket_aggregate_create(alloc);
128 svn_ra_serf__add_xml_header_buckets(body, alloc);
129 svn_ra_serf__add_open_tag_buckets(body, alloc, "D:options",
130 "xmlns:D", "DAV:",
131 SVN_VA_NULL);
132 svn_ra_serf__add_tag_buckets(body, "D:activity-collection-set", NULL, alloc);
133 svn_ra_serf__add_close_tag_buckets(body, alloc, "D:options");
134
135 *body_bkt = body;
136 return SVN_NO_ERROR;
137 }
138
139
140 /* We use these static pointers so we can employ pointer comparison
141 * of our capabilities hash members instead of strcmp()ing all over
142 * the place.
143 */
144 /* Both server and repository support the capability. */
145 static const char *const capability_yes = "yes";
146 /* Either server or repository does not support the capability. */
147 static const char *const capability_no = "no";
148 /* Server supports the capability, but don't yet know if repository does. */
149 static const char *const capability_server_yes = "server-yes";
150
151
152 /* This implements serf_bucket_headers_do_callback_fn_t.
153 */
154 static int
capabilities_headers_iterator_callback(void * baton,const char * key,const char * val)155 capabilities_headers_iterator_callback(void *baton,
156 const char *key,
157 const char *val)
158 {
159 options_context_t *opt_ctx = baton;
160 svn_ra_serf__session_t *session = opt_ctx->session;
161
162 if (svn_cstring_casecmp(key, "dav") == 0)
163 {
164 /* Each header may contain multiple values, separated by commas, e.g.:
165 DAV: version-control,checkout,working-resource
166 DAV: merge,baseline,activity,version-controlled-collection
167 DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
168 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
169 opt_ctx->pool);
170
171 opt_ctx->received_dav_header = TRUE;
172
173 /* Right now we only have a few capabilities to detect, so just
174 seek for them directly. This could be written slightly more
175 efficiently, but that wouldn't be worth it until we have many
176 more capabilities. */
177
178 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
179 {
180 svn_hash_sets(session->capabilities,
181 SVN_RA_CAPABILITY_DEPTH, capability_yes);
182 }
183 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
184 {
185 /* The server doesn't know what repository we're referring
186 to, so it can't just say capability_yes. */
187 if (!svn_hash_gets(session->capabilities,
188 SVN_RA_CAPABILITY_MERGEINFO))
189 {
190 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
191 capability_server_yes);
192 }
193 }
194 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
195 {
196 svn_hash_sets(session->capabilities,
197 SVN_RA_CAPABILITY_LOG_REVPROPS, capability_yes);
198 }
199 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_ATOMIC_REVPROPS, vals))
200 {
201 svn_hash_sets(session->capabilities,
202 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, capability_yes);
203 }
204 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
205 {
206 svn_hash_sets(session->capabilities,
207 SVN_RA_CAPABILITY_PARTIAL_REPLAY, capability_yes);
208 }
209 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INHERITED_PROPS, vals))
210 {
211 svn_hash_sets(session->capabilities,
212 SVN_RA_CAPABILITY_INHERITED_PROPS, capability_yes);
213 }
214 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REVERSE_FILE_REVS,
215 vals))
216 {
217 svn_hash_sets(session->capabilities,
218 SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
219 capability_yes);
220 }
221 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_EPHEMERAL_TXNPROPS, vals))
222 {
223 svn_hash_sets(session->capabilities,
224 SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS, capability_yes);
225 }
226 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_INLINE_PROPS, vals))
227 {
228 session->supports_inline_props = TRUE;
229 }
230 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_REPLAY_REV_RESOURCE, vals))
231 {
232 session->supports_rev_rsrc_replay = TRUE;
233 }
234 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF1, vals))
235 {
236 /* Use compressed svndiff1 format for servers that properly
237 advertise this capability (Subversion 1.10 and greater). */
238 session->supports_svndiff1 = TRUE;
239 }
240 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_LIST, vals))
241 {
242 svn_hash_sets(session->capabilities,
243 SVN_RA_CAPABILITY_LIST, capability_yes);
244 }
245 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_SVNDIFF2, vals))
246 {
247 /* Same for svndiff2. */
248 session->supports_svndiff2 = TRUE;
249 }
250 if (svn_cstring_match_list(SVN_DAV_NS_DAV_SVN_PUT_RESULT_CHECKSUM, vals))
251 {
252 session->supports_put_result_checksum = TRUE;
253 }
254 }
255
256 /* SVN-specific headers -- if present, server supports HTTP protocol v2 */
257 else if (!svn_ctype_casecmp(key[0], 'S')
258 && !svn_ctype_casecmp(key[1], 'V')
259 && !svn_ctype_casecmp(key[2], 'N'))
260 {
261 /* If we've not yet seen any information about supported POST
262 requests, we'll initialize the list/hash with "create-txn"
263 (which we know is supported by virtue of the server speaking
264 HTTPv2 at all. */
265 if (! session->supported_posts)
266 {
267 session->supported_posts = apr_hash_make(session->pool);
268 apr_hash_set(session->supported_posts, "create-txn", 10, (void *)1);
269 }
270
271 if (svn_cstring_casecmp(key, SVN_DAV_ROOT_URI_HEADER) == 0)
272 {
273 session->repos_root = session->session_url;
274 session->repos_root.path =
275 (char *)svn_fspath__canonicalize(val, session->pool);
276 session->repos_root_str =
277 svn_urlpath__canonicalize(
278 apr_uri_unparse(session->pool, &session->repos_root, 0),
279 session->pool);
280 }
281 else if (svn_cstring_casecmp(key, SVN_DAV_ME_RESOURCE_HEADER) == 0)
282 {
283 #ifdef SVN_DEBUG
284 char *ignore_v2_env_var = getenv(SVN_IGNORE_V2_ENV_VAR);
285
286 if (!(ignore_v2_env_var
287 && apr_strnatcasecmp(ignore_v2_env_var, "yes") == 0))
288 session->me_resource = apr_pstrdup(session->pool, val);
289 #else
290 session->me_resource = apr_pstrdup(session->pool, val);
291 #endif
292 }
293 else if (svn_cstring_casecmp(key, SVN_DAV_REV_STUB_HEADER) == 0)
294 {
295 session->rev_stub = apr_pstrdup(session->pool, val);
296 }
297 else if (svn_cstring_casecmp(key, SVN_DAV_REV_ROOT_STUB_HEADER) == 0)
298 {
299 session->rev_root_stub = apr_pstrdup(session->pool, val);
300 }
301 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_STUB_HEADER) == 0)
302 {
303 session->txn_stub = apr_pstrdup(session->pool, val);
304 }
305 else if (svn_cstring_casecmp(key, SVN_DAV_TXN_ROOT_STUB_HEADER) == 0)
306 {
307 session->txn_root_stub = apr_pstrdup(session->pool, val);
308 }
309 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_STUB_HEADER) == 0)
310 {
311 session->vtxn_stub = apr_pstrdup(session->pool, val);
312 }
313 else if (svn_cstring_casecmp(key, SVN_DAV_VTXN_ROOT_STUB_HEADER) == 0)
314 {
315 session->vtxn_root_stub = apr_pstrdup(session->pool, val);
316 }
317 else if (svn_cstring_casecmp(key, SVN_DAV_REPOS_UUID_HEADER) == 0)
318 {
319 session->uuid = apr_pstrdup(session->pool, val);
320 }
321 else if (svn_cstring_casecmp(key, SVN_DAV_YOUNGEST_REV_HEADER) == 0)
322 {
323 opt_ctx->youngest_rev = SVN_STR_TO_REV(val);
324 }
325 else if (svn_cstring_casecmp(key, SVN_DAV_ALLOW_BULK_UPDATES) == 0)
326 {
327 session->server_allows_bulk = apr_pstrdup(session->pool, val);
328 }
329 else if (svn_cstring_casecmp(key, SVN_DAV_SUPPORTED_POSTS_HEADER) == 0)
330 {
331 /* May contain multiple values, separated by commas. */
332 int i;
333 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE,
334 session->pool);
335
336 for (i = 0; i < vals->nelts; i++)
337 {
338 const char *post_val = APR_ARRAY_IDX(vals, i, const char *);
339
340 svn_hash_sets(session->supported_posts, post_val, (void *)1);
341 }
342 }
343 else if (svn_cstring_casecmp(key, SVN_DAV_REPOSITORY_MERGEINFO) == 0)
344 {
345 if (svn_cstring_casecmp(val, "yes") == 0)
346 {
347 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
348 capability_yes);
349 }
350 else if (svn_cstring_casecmp(val, "no") == 0)
351 {
352 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
353 capability_no);
354 }
355 }
356 }
357
358 return 0;
359 }
360
361
362 /* A custom serf_response_handler_t which is mostly a wrapper around
363 the expat-based response handler -- it just notices OPTIONS response
364 headers first, before handing off to the xml parser.
365 Implements svn_ra_serf__response_handler_t */
366 static svn_error_t *
options_response_handler(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * pool)367 options_response_handler(serf_request_t *request,
368 serf_bucket_t *response,
369 void *baton,
370 apr_pool_t *pool)
371 {
372 options_context_t *opt_ctx = baton;
373
374 if (!opt_ctx->headers_processed)
375 {
376 svn_ra_serf__session_t *session = opt_ctx->session;
377 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
378 serf_connection_t *conn;
379
380 /* Start out assuming all capabilities are unsupported. */
381 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
382 capability_no);
383 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_DEPTH,
384 capability_no);
385 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
386 NULL);
387 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
388 capability_no);
389 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
390 capability_no);
391 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_INHERITED_PROPS,
392 capability_no);
393 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS,
394 capability_no);
395 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE,
396 capability_no);
397 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_LIST,
398 capability_no);
399
400 /* Then see which ones we can discover. */
401 serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback,
402 opt_ctx);
403
404 /* Bail out early if we're not talking to a DAV server.
405 Note that this check is only valid if we've received a success
406 response; redirects and errors don't count. */
407 if (opt_ctx->handler->sline.code >= 200
408 && opt_ctx->handler->sline.code < 300
409 && !opt_ctx->received_dav_header)
410 {
411 return svn_error_createf
412 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
413 _("The server at '%s' does not support the HTTP/DAV protocol"),
414 session->session_url_str);
415 }
416
417 /* Assume mergeinfo capability unsupported, if didn't receive information
418 about server or repository mergeinfo capability. */
419 if (!svn_hash_gets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO))
420 svn_hash_sets(session->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
421 capability_no);
422
423 /* Remember our latency. */
424 conn = serf_request_get_conn(request);
425 session->conn_latency = serf_connection_get_latency(conn);
426
427 opt_ctx->headers_processed = TRUE;
428 }
429
430 /* Execute the 'real' response handler to XML-parse the response body. */
431 return opt_ctx->inner_handler(request, response, opt_ctx->inner_baton, pool);
432 }
433
434
435 static svn_error_t *
create_options_req(options_context_t ** opt_ctx,svn_ra_serf__session_t * session,apr_pool_t * pool)436 create_options_req(options_context_t **opt_ctx,
437 svn_ra_serf__session_t *session,
438 apr_pool_t *pool)
439 {
440 options_context_t *new_ctx;
441 svn_ra_serf__xml_context_t *xmlctx;
442 svn_ra_serf__handler_t *handler;
443
444 new_ctx = apr_pcalloc(pool, sizeof(*new_ctx));
445 new_ctx->pool = pool;
446 new_ctx->session = session;
447
448 new_ctx->youngest_rev = SVN_INVALID_REVNUM;
449
450 xmlctx = svn_ra_serf__xml_context_create(options_ttable,
451 NULL, options_closed, NULL,
452 new_ctx,
453 pool);
454 handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL, pool);
455
456 handler->method = "OPTIONS";
457 handler->path = session->session_url.path;
458 handler->body_delegate = create_options_body;
459 handler->body_type = "text/xml";
460
461 new_ctx->handler = handler;
462
463 new_ctx->inner_handler = handler->response_handler;
464 new_ctx->inner_baton = handler->response_baton;
465 handler->response_handler = options_response_handler;
466 handler->response_baton = new_ctx;
467
468 *opt_ctx = new_ctx;
469
470 return SVN_NO_ERROR;
471 }
472
473
474 svn_error_t *
svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t * youngest,svn_ra_serf__session_t * session,apr_pool_t * scratch_pool)475 svn_ra_serf__v2_get_youngest_revnum(svn_revnum_t *youngest,
476 svn_ra_serf__session_t *session,
477 apr_pool_t *scratch_pool)
478 {
479 options_context_t *opt_ctx;
480
481 SVN_ERR_ASSERT(SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
482
483 SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
484 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
485
486 if (opt_ctx->handler->sline.code != 200)
487 return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
488
489 if (! SVN_IS_VALID_REVNUM(opt_ctx->youngest_rev))
490 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
491 _("The OPTIONS response did not include "
492 "the youngest revision"));
493
494 *youngest = opt_ctx->youngest_rev;
495
496 return SVN_NO_ERROR;
497 }
498
499
500 svn_error_t *
svn_ra_serf__v1_get_activity_collection(const char ** activity_url,svn_ra_serf__session_t * session,apr_pool_t * result_pool,apr_pool_t * scratch_pool)501 svn_ra_serf__v1_get_activity_collection(const char **activity_url,
502 svn_ra_serf__session_t *session,
503 apr_pool_t *result_pool,
504 apr_pool_t *scratch_pool)
505 {
506 options_context_t *opt_ctx;
507
508 SVN_ERR_ASSERT(!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
509
510 if (session->activity_collection_url)
511 {
512 *activity_url = apr_pstrdup(result_pool,
513 session->activity_collection_url);
514 return SVN_NO_ERROR;
515 }
516
517 SVN_ERR(create_options_req(&opt_ctx, session, scratch_pool));
518 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
519
520 if (opt_ctx->handler->sline.code != 200)
521 return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
522
523 /* Cache the result. */
524 if (opt_ctx->activity_collection)
525 {
526 session->activity_collection_url =
527 apr_pstrdup(session->pool, opt_ctx->activity_collection);
528 }
529 else
530 {
531 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
532 _("The OPTIONS response did not include the "
533 "requested activity-collection-set value"));
534 }
535
536 *activity_url = apr_pstrdup(result_pool, opt_ctx->activity_collection);
537
538 return SVN_NO_ERROR;
539
540 }
541
542
543
544 /** Capabilities exchange. */
545
546 svn_error_t *
svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t * serf_sess,const char ** corrected_url,const char ** redirect_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)547 svn_ra_serf__exchange_capabilities(svn_ra_serf__session_t *serf_sess,
548 const char **corrected_url,
549 const char **redirect_url,
550 apr_pool_t *result_pool,
551 apr_pool_t *scratch_pool)
552 {
553 options_context_t *opt_ctx;
554
555 if (corrected_url)
556 *corrected_url = NULL;
557 if (redirect_url)
558 *redirect_url = NULL;
559
560 /* This routine automatically fills in serf_sess->capabilities */
561 SVN_ERR(create_options_req(&opt_ctx, serf_sess, scratch_pool));
562
563 opt_ctx->handler->no_fail_on_http_redirect_status = TRUE;
564
565 SVN_ERR(svn_ra_serf__context_run_one(opt_ctx->handler, scratch_pool));
566
567 /* If our caller cares about server redirections, and our response
568 carries such a thing, report as much. We'll disregard ERR --
569 it's most likely just a complaint about the response body not
570 successfully parsing as XML or somesuch. */
571 if (corrected_url && (opt_ctx->handler->sline.code == 301))
572 {
573 if (!opt_ctx->handler->location || !*opt_ctx->handler->location)
574 {
575 return svn_error_create(
576 SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
577 _("Location header not set on redirect response"));
578 }
579 else if (svn_path_is_url(opt_ctx->handler->location))
580 {
581 SVN_ERR(svn_uri_canonicalize_safe(corrected_url, NULL,
582 opt_ctx->handler->location, result_pool, scratch_pool));
583 if (redirect_url)
584 *redirect_url = apr_pstrdup(result_pool,
585 opt_ctx->handler->location);
586 }
587 else
588 {
589 /* RFC1945 and RFC2616 state that the Location header's value
590 (from whence this CORRECTED_URL comes), if present, must be an
591 absolute URI. But some Apache versions (those older than 2.2.11,
592 it seems) transmit only the path portion of the URI.
593 See issue #3775 for details. */
594
595 apr_uri_t corrected_URI = serf_sess->session_url;
596 char *absolute_uri;
597
598 corrected_URI.path = (char *)corrected_url;
599 absolute_uri = apr_uri_unparse(scratch_pool, &corrected_URI, 0);
600 SVN_ERR(svn_uri_canonicalize_safe(corrected_url, NULL,
601 absolute_uri, result_pool, scratch_pool));
602 if (redirect_url)
603 *redirect_url = apr_pstrdup(result_pool, absolute_uri);
604 }
605
606 return SVN_NO_ERROR;
607 }
608 else if (opt_ctx->handler->sline.code >= 300
609 && opt_ctx->handler->sline.code < 399)
610 {
611 return svn_error_createf(SVN_ERR_RA_SESSION_URL_MISMATCH, NULL,
612 (opt_ctx->handler->sline.code == 301
613 ? _("Repository moved permanently to '%s'")
614 : _("Repository moved temporarily to '%s'")),
615 opt_ctx->handler->location);
616 }
617
618 if (opt_ctx->handler->sline.code != 200)
619 return svn_error_trace(svn_ra_serf__unexpected_status(opt_ctx->handler));
620
621 /* Opportunistically cache any reported activity URL. (We don't
622 want to have to ask for this again later, potentially against an
623 unreadable commit anchor URL.) */
624 if (opt_ctx->activity_collection)
625 {
626 serf_sess->activity_collection_url =
627 apr_pstrdup(serf_sess->pool, opt_ctx->activity_collection);
628 }
629
630 return SVN_NO_ERROR;
631 }
632
633 /* Implements svn_ra_serf__request_body_delegate_t */
634 static svn_error_t *
create_simple_options_body(serf_bucket_t ** body_bkt,void * baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)635 create_simple_options_body(serf_bucket_t **body_bkt,
636 void *baton,
637 serf_bucket_alloc_t *alloc,
638 apr_pool_t *pool /* request pool */,
639 apr_pool_t *scratch_pool)
640 {
641 serf_bucket_t *body;
642 serf_bucket_t *s;
643
644 body = serf_bucket_aggregate_create(alloc);
645 svn_ra_serf__add_xml_header_buckets(body, alloc);
646
647 s = SERF_BUCKET_SIMPLE_STRING("<D:options xmlns:D=\"DAV:\" />", alloc);
648 serf_bucket_aggregate_append(body, s);
649
650 *body_bkt = body;
651 return SVN_NO_ERROR;
652 }
653
654
655 svn_error_t *
svn_ra_serf__probe_proxy(svn_ra_serf__session_t * serf_sess,apr_pool_t * scratch_pool)656 svn_ra_serf__probe_proxy(svn_ra_serf__session_t *serf_sess,
657 apr_pool_t *scratch_pool)
658 {
659 svn_ra_serf__handler_t *handler;
660
661 handler = svn_ra_serf__create_handler(serf_sess, scratch_pool);
662 handler->method = "OPTIONS";
663 handler->path = serf_sess->session_url.path;
664
665 /* We don't care about the response body, so discard it. */
666 handler->response_handler = svn_ra_serf__handle_discard_body;
667
668 /* We need a simple body, in order to send it in chunked format. */
669 handler->body_delegate = create_simple_options_body;
670 handler->no_fail_on_http_failure_status = TRUE;
671
672 /* No special headers. */
673
674 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
675 /* Some versions of nginx in reverse proxy mode will return 411. They want
676 a Content-Length header, rather than chunked requests. We can keep other
677 HTTP/1.1 features, but will disable the chunking. */
678 if (handler->sline.code == 411)
679 {
680 serf_sess->using_chunked_requests = FALSE;
681
682 return SVN_NO_ERROR;
683 }
684 if (handler->sline.code != 200)
685 SVN_ERR(svn_ra_serf__unexpected_status(handler));
686
687 return SVN_NO_ERROR;
688 }
689
690
691 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)692 svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
693 svn_boolean_t *has,
694 const char *capability,
695 apr_pool_t *pool)
696 {
697 svn_ra_serf__session_t *serf_sess = ra_session->priv;
698 const char *cap_result;
699
700 /* This capability doesn't rely on anything server side. */
701 if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
702 {
703 *has = TRUE;
704 return SVN_NO_ERROR;
705 }
706
707 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
708
709 /* If any capability is unknown, they're all unknown, so ask. */
710 if (cap_result == NULL)
711 SVN_ERR(svn_ra_serf__exchange_capabilities(serf_sess, NULL, NULL,
712 pool, pool));
713
714 /* Try again, now that we've fetched the capabilities. */
715 cap_result = svn_hash_gets(serf_sess->capabilities, capability);
716
717 /* Some capabilities depend on the repository as well as the server. */
718 if (cap_result == capability_server_yes)
719 {
720 if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
721 {
722 /* Handle mergeinfo specially. Mergeinfo depends on the
723 repository as well as the server, but the server routine
724 that answered our svn_ra_serf__exchange_capabilities() call above
725 didn't even know which repository we were interested in
726 -- it just told us whether the server supports mergeinfo.
727 If the answer was 'no', there's no point checking the
728 particular repository; but if it was 'yes', we still must
729 change it to 'no' iff the repository itself doesn't
730 support mergeinfo. */
731 svn_mergeinfo_catalog_t ignored;
732 svn_error_t *err;
733 apr_array_header_t *paths = apr_array_make(pool, 1,
734 sizeof(char *));
735 APR_ARRAY_PUSH(paths, const char *) = "";
736
737 err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
738 svn_mergeinfo_explicit,
739 FALSE /* include_descendants */,
740 pool);
741
742 if (err)
743 {
744 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
745 {
746 svn_error_clear(err);
747 cap_result = capability_no;
748 }
749 else if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
750 {
751 /* Mergeinfo requests use relative paths, and
752 anyway we're in r0, so this is a likely error,
753 but it means the repository supports mergeinfo! */
754 svn_error_clear(err);
755 cap_result = capability_yes;
756 }
757 else
758 return svn_error_trace(err);
759 }
760 else
761 cap_result = capability_yes;
762
763 svn_hash_sets(serf_sess->capabilities,
764 SVN_RA_CAPABILITY_MERGEINFO, cap_result);
765 }
766 else
767 {
768 return svn_error_createf
769 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
770 _("Don't know how to handle '%s' for capability '%s'"),
771 capability_server_yes, capability);
772 }
773 }
774
775 if (cap_result == capability_yes)
776 {
777 *has = TRUE;
778 }
779 else if (cap_result == capability_no)
780 {
781 *has = FALSE;
782 }
783 else if (cap_result == NULL)
784 {
785 return svn_error_createf
786 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
787 _("Don't know anything about capability '%s'"), capability);
788 }
789 else /* "can't happen" */
790 {
791 /* Well, let's hope it's a string. */
792 return svn_error_createf
793 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
794 _("Attempt to fetch capability '%s' resulted in '%s'"),
795 capability, cap_result);
796 }
797
798 return SVN_NO_ERROR;
799 }
800