Reorganize the output to "svnserve --help".
[svn.git] / subversion / libsvn_ra_serf / serf.c
blobe4704fd2eb22b53cf79ae6ddcec0b079c44e12d0
1 /*
2 * serf.c : entry point for ra_serf
4 * ====================================================================
5 * Copyright (c) 2006-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h>
24 #include <apr_uri.h>
26 #include <expat.h>
28 #include <serf.h>
30 #include "svn_pools.h"
31 #include "svn_ra.h"
32 #include "svn_dav.h"
33 #include "svn_xml.h"
34 #include "../libsvn_ra/ra_loader.h"
35 #include "svn_config.h"
36 #include "svn_delta.h"
37 #include "svn_version.h"
38 #include "svn_path.h"
39 #include "svn_time.h"
41 #include "private/svn_dav_protocol.h"
42 #include "private/svn_dep_compat.h"
43 #include "svn_private_config.h"
45 #include "ra_serf.h"
48 /** Capabilities exchange. */
50 /* Both server and repository support the capability. */
51 static const char *capability_yes = "yes";
52 /* Either server or repository does not support the capability. */
53 static const char *capability_no = "no";
54 /* Server supports the capability, but don't yet know if repository does. */
55 static const char *capability_server_yes = "server-yes";
57 /* Baton type for parsing capabilities out of "OPTIONS" response headers. */
58 struct capabilities_response_baton
60 /* This session's capabilities table. */
61 apr_hash_t *capabilities;
63 /* Signaler for svn_ra_serf__context_run_wait(). */
64 svn_boolean_t done;
66 /* For temporary work only. */
67 apr_pool_t *pool;
71 /* This implements serf_bucket_headers_do_callback_fn_t.
72 * BATON is a 'struct capabilities_response_baton *'.
74 static int
75 capabilities_headers_iterator_callback(void *baton,
76 const char *key,
77 const char *val)
79 struct capabilities_response_baton *crb = baton;
81 if (svn_cstring_casecmp(key, "dav") == 0)
83 /* Each header may contain multiple values, separated by commas, e.g.:
84 DAV: version-control,checkout,working-resource
85 DAV: merge,baseline,activity,version-controlled-collection
86 DAV: http://subversion.tigris.org/xmlns/dav/svn/depth */
87 apr_array_header_t *vals = svn_cstring_split(val, ",", TRUE, crb->pool);
89 /* Right now we only have a few capabilities to detect, so just
90 seek for them directly. This could be written slightly more
91 efficiently, but that wouldn't be worth it until we have many
92 more capabilities. */
94 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
96 apr_hash_set(crb->capabilities, SVN_RA_CAPABILITY_DEPTH,
97 APR_HASH_KEY_STRING, capability_yes);
100 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
102 /* The server doesn't know what repository we're referring
103 to, so it can't just say capability_yes. */
104 apr_hash_set(crb->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
105 APR_HASH_KEY_STRING, capability_server_yes);
108 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
110 apr_hash_set(crb->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
111 APR_HASH_KEY_STRING, capability_yes);
114 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY, vals))
116 apr_hash_set(crb->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
117 APR_HASH_KEY_STRING, capability_yes);
121 return 0;
125 /* This implements serf_response_handler_t.
126 * HANDLER_BATON is a 'struct capabilities_response_baton *'.
128 static apr_status_t
129 capabilities_response_handler(serf_request_t *request,
130 serf_bucket_t *response,
131 void *handler_baton,
132 apr_pool_t *pool)
134 struct capabilities_response_baton *crb = handler_baton;
135 serf_bucket_t *hdrs = serf_bucket_response_get_headers(response);
137 /* Start out assuming all capabilities are unsupported. */
138 apr_hash_set(crb->capabilities, SVN_RA_CAPABILITY_DEPTH,
139 APR_HASH_KEY_STRING, capability_no);
140 apr_hash_set(crb->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
141 APR_HASH_KEY_STRING, capability_no);
142 apr_hash_set(crb->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
143 APR_HASH_KEY_STRING, capability_no);
145 /* Then see which ones we can discover. */
146 serf_bucket_headers_do(hdrs, capabilities_headers_iterator_callback, crb);
148 /* Bunch of exit conditions to set before we go. */
149 crb->done = TRUE;
150 serf_request_set_handler(request, svn_ra_serf__handle_discard_body, NULL);
151 return APR_SUCCESS;
154 /* Set up headers announcing the client's capabilities.
155 BATON and POOL are ignored. */
156 static apr_status_t
157 set_up_capabilities_headers(serf_bucket_t *headers,
158 void *baton,
159 apr_pool_t *pool)
161 serf_bucket_headers_set(headers, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
162 serf_bucket_headers_set(headers, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
163 serf_bucket_headers_set(headers, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
165 return APR_SUCCESS;
169 /* Exchange capabilities with the server, by sending an OPTIONS
170 request announcing the client's capabilities, and by filling
171 SERF_SESS->capabilities with the server's capabilities as read
172 from the response headers. Use POOL only for temporary allocation. */
173 static svn_error_t *
174 exchange_capabilities(svn_ra_serf__session_t *serf_sess, apr_pool_t *pool)
176 svn_ra_serf__handler_t *handler;
177 struct capabilities_response_baton crb;
179 crb.pool = pool;
180 crb.done = FALSE;
181 crb.capabilities = serf_sess->capabilities;
183 /* No obvious advantage to using svn_ra_serf__create_options_req() here. */
184 handler = apr_pcalloc(pool, sizeof(*handler));
185 handler->method = "OPTIONS";
186 handler->path = serf_sess->repos_url_str;
187 handler->body_buckets = NULL;
188 handler->response_handler = capabilities_response_handler;
189 handler->response_baton = &crb;
190 handler->header_delegate = set_up_capabilities_headers;
191 handler->session = serf_sess;
192 handler->conn = serf_sess->conns[0];
194 svn_ra_serf__request_create(handler);
195 return svn_ra_serf__context_run_wait(&(crb.done), serf_sess, pool);
199 svn_error_t *
200 svn_ra_serf__has_capability(svn_ra_session_t *ra_session,
201 svn_boolean_t *has,
202 const char *capability,
203 apr_pool_t *pool)
205 svn_ra_serf__session_t *serf_sess = ra_session->priv;
207 const char *cap_result = apr_hash_get(serf_sess->capabilities,
208 capability,
209 APR_HASH_KEY_STRING);
211 /* If any capability is unknown, they're all unknown, so ask. */
212 if (cap_result == NULL)
213 SVN_ERR(exchange_capabilities(serf_sess, pool));
215 /* Try again, now that we've fetched the capabilities. */
216 cap_result = apr_hash_get(serf_sess->capabilities,
217 capability, APR_HASH_KEY_STRING);
219 /* Some capabilities depend on the repository as well as the server.
220 NOTE: ../libsvn_ra_neon/session.c:svn_ra_neon__has_capability()
221 has a very similar code block. If you change something here,
222 check there as well. */
223 if (cap_result == capability_server_yes)
225 if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
227 /* Handle mergeinfo specially. Mergeinfo depends on the
228 repository as well as the server, but the server routine
229 that answered our exchange_capabilities() call above
230 didn't even know which repository we were interested in
231 -- it just told us whether the server supports mergeinfo.
232 If the answer was 'no', there's no point checking the
233 particular repository; but if it was 'yes, we still must
234 change it to 'no' iff the repository itself doesn't
235 support mergeinfo. */
236 svn_mergeinfo_catalog_t ignored;
237 svn_error_t *err;
238 apr_array_header_t *paths = apr_array_make(pool, 1,
239 sizeof(char *));
240 APR_ARRAY_PUSH(paths, const char *) = "";
242 err = svn_ra_serf__get_mergeinfo(ra_session, &ignored, paths, 0,
243 FALSE, FALSE, pool);
245 if (err)
247 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
249 svn_error_clear(err);
250 cap_result = capability_no;
252 else if (err->apr_err == SVN_ERR_FS_NOT_FOUND
253 || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
255 /* Mergeinfo requests use relative paths, and
256 anyway we're in r0, so this is a likely error,
257 but it means the repository supports mergeinfo! */
258 svn_error_clear(err);
259 cap_result = capability_yes;
261 else
262 return err;
264 else
265 cap_result = capability_yes;
267 apr_hash_set(serf_sess->capabilities,
268 SVN_RA_CAPABILITY_MERGEINFO, APR_HASH_KEY_STRING,
269 cap_result);
271 else
273 return svn_error_createf
274 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
275 _("Don't know how to handle '%s' for capability '%s'"),
276 capability_server_yes, capability);
280 if (cap_result == capability_yes)
282 *has = TRUE;
284 else if (cap_result == capability_no)
286 *has = FALSE;
288 else if (cap_result == NULL)
290 return svn_error_createf
291 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
292 _("Don't know anything about capability '%s'"), capability);
294 else /* "can't happen" */
296 /* Well, let's hope it's a string. */
297 return svn_error_createf
298 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
299 _("Attempt to fetch capability '%s' resulted in '%s'"),
300 capability, cap_result);
303 return SVN_NO_ERROR;
308 static const svn_version_t *
309 ra_serf_version(void)
311 SVN_VERSION_BODY;
314 #define RA_SERF_DESCRIPTION \
315 N_("Module for accessing a repository via WebDAV protocol using serf.")
317 static const char *
318 ra_serf_get_description(void)
320 return _(RA_SERF_DESCRIPTION);
323 static const char * const *
324 ra_serf_get_schemes(apr_pool_t *pool)
326 static const char *serf_ssl[] = { "http", "https", NULL };
327 #if 0
328 /* ### Temporary: to shut up a warning. */
329 static const char *serf_no_ssl[] = { "http", NULL };
330 #endif
332 /* TODO: Runtime detection. */
333 return serf_ssl;
336 static svn_error_t *
337 load_config(svn_ra_serf__session_t *session,
338 apr_hash_t *config_hash,
339 apr_pool_t *pool)
341 svn_config_t *config;
342 const char *server_group;
343 #if SERF_VERSION_AT_LEAST(0, 1, 3)
344 const char *proxy_host = NULL, *port_str = NULL;
345 unsigned int proxy_port;
346 #endif
348 config = apr_hash_get(config_hash, SVN_CONFIG_CATEGORY_SERVERS,
349 APR_HASH_KEY_STRING);
351 SVN_ERR(svn_config_get_bool(config, &session->using_compression,
352 SVN_CONFIG_SECTION_GLOBAL,
353 SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
355 svn_auth_set_parameter(session->wc_callbacks->auth_baton,
356 SVN_AUTH_PARAM_CONFIG, config);
358 #if SERF_VERSION_AT_LEAST(0, 1, 3)
359 /* Load the global proxy server settings, if set. */
360 svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
361 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
362 svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
363 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
364 svn_config_get(config, &session->proxy_username, SVN_CONFIG_SECTION_GLOBAL,
365 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
366 svn_config_get(config, &session->proxy_password, SVN_CONFIG_SECTION_GLOBAL,
367 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
368 #endif
370 server_group = svn_config_find_group(config,
371 session->repos_url.hostname,
372 SVN_CONFIG_SECTION_GROUPS, pool);
374 if (server_group)
376 SVN_ERR(svn_config_get_bool(config, &session->using_compression,
377 server_group,
378 SVN_CONFIG_OPTION_HTTP_COMPRESSION,
379 session->using_compression));
380 svn_auth_set_parameter(session->wc_callbacks->auth_baton,
381 SVN_AUTH_PARAM_SERVER_GROUP, server_group);
383 #if SERF_VERSION_AT_LEAST(0, 1, 3)
384 /* Load the group proxy server settings, overriding global settings. */
385 svn_config_get(config, &proxy_host, server_group,
386 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
387 svn_config_get(config, &port_str, server_group,
388 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
389 svn_config_get(config, &session->proxy_username, server_group,
390 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
391 svn_config_get(config, &session->proxy_password, server_group,
392 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
393 #endif
396 #if SERF_VERSION_AT_LEAST(0, 1, 3)
397 /* Convert the proxy port value, if any. */
398 if (port_str)
400 char *endstr;
401 const long int port = strtol(port_str, &endstr, 10);
403 if (*endstr)
404 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
405 _("Invalid URL: illegal character in proxy "
406 "port number"));
407 if (port < 0)
408 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
409 _("Invalid URL: negative proxy port number"));
410 if (port > 65535)
411 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
412 _("Invalid URL: proxy port number greater "
413 "than maximum TCP port number 65535"));
414 proxy_port = port;
416 else
417 proxy_port = 80;
419 if (proxy_host)
421 apr_sockaddr_t *proxy_addr;
422 apr_status_t status;
424 status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
425 APR_INET, proxy_port, 0,
426 session->pool);
427 session->using_proxy = TRUE;
428 serf_config_proxy(session->context, proxy_addr);
430 else
431 session->using_proxy = FALSE;
432 #endif
434 return SVN_NO_ERROR;
437 #if SERF_VERSION_AT_LEAST(0,1,3)
438 static void
439 svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
441 const svn_ra_serf__session_t *serf_sess = progress_baton;
442 if (serf_sess->wc_progress_func)
444 serf_sess->wc_progress_func(read + written, -1,
445 serf_sess->wc_progress_baton,
446 serf_sess->pool);
449 #endif
451 static svn_error_t *
452 svn_ra_serf__open(svn_ra_session_t *session,
453 const char *repos_URL,
454 const svn_ra_callbacks2_t *callbacks,
455 void *callback_baton,
456 apr_hash_t *config,
457 apr_pool_t *pool)
459 apr_status_t status;
460 svn_ra_serf__session_t *serf_sess;
461 apr_uri_t url;
462 const char *client_string = NULL;
464 serf_sess = apr_pcalloc(pool, sizeof(*serf_sess));
465 apr_pool_create(&serf_sess->pool, pool);
466 serf_sess->bkt_alloc = serf_bucket_allocator_create(serf_sess->pool, NULL,
467 NULL);
468 serf_sess->cached_props = apr_hash_make(serf_sess->pool);
469 serf_sess->wc_callbacks = callbacks;
470 serf_sess->wc_callback_baton = callback_baton;
471 serf_sess->wc_progress_baton = callbacks->progress_baton;
472 serf_sess->wc_progress_func = callbacks->progress_func;
474 /* todo: reuse serf context across sessions */
475 serf_sess->context = serf_context_create(serf_sess->pool);
477 status = apr_uri_parse(serf_sess->pool, repos_URL, &url);
478 if (status)
480 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
481 _("Illegal repository URL '%s'"),
482 repos_URL);
485 serf_sess->repos_url = url;
486 serf_sess->repos_url_str = apr_pstrdup(serf_sess->pool, repos_URL);
488 if (!url.port)
490 url.port = apr_uri_port_of_scheme(url.scheme);
492 serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
494 serf_sess->capabilities = apr_hash_make(serf_sess->pool);
496 SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
498 /* register cleanups */
499 apr_pool_cleanup_register(serf_sess->pool, serf_sess,
500 svn_ra_serf__cleanup_serf_session,
501 apr_pool_cleanup_null);
503 serf_sess->conns = apr_palloc(serf_sess->pool, sizeof(*serf_sess->conns) * 4);
505 serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
506 sizeof(*serf_sess->conns[0]));
507 serf_sess->conns[0]->bkt_alloc =
508 serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
509 serf_sess->conns[0]->session = serf_sess;
510 serf_sess->conns[0]->last_status_code = -1;
512 /* Unless we're using a proxy, fetch the DNS record for this host */
513 if (! serf_sess->using_proxy)
515 status = apr_sockaddr_info_get(&serf_sess->conns[0]->address,
516 url.hostname,
517 APR_UNSPEC, url.port, 0, serf_sess->pool);
518 if (status)
520 return svn_error_wrap_apr(status,
521 _("Could not lookup hostname `%s'"),
522 url.hostname);
525 else
527 /* Create an address with unresolved hostname. */
528 apr_sockaddr_t *sa = apr_pcalloc(serf_sess->pool, sizeof(apr_sockaddr_t));
529 sa->pool = serf_sess->pool;
530 sa->hostname = apr_pstrdup(serf_sess->pool, url.hostname);
531 sa->port = url.port;
532 sa->family = APR_UNSPEC;
533 serf_sess->conns[0]->address = sa;
536 serf_sess->conns[0]->using_ssl = serf_sess->using_ssl;
537 serf_sess->conns[0]->using_compression = serf_sess->using_compression;
538 serf_sess->conns[0]->hostinfo = url.hostinfo;
539 serf_sess->conns[0]->auth_header = NULL;
540 serf_sess->conns[0]->auth_value = NULL;
541 serf_sess->conns[0]->useragent = NULL;
543 /* create the user agent string */
544 if (callbacks->get_client_string)
545 callbacks->get_client_string(callback_baton, &client_string, pool);
547 if (client_string)
548 serf_sess->conns[0]->useragent = apr_pstrcat(pool, USER_AGENT, "/",
549 client_string, NULL);
550 else
551 serf_sess->conns[0]->useragent = USER_AGENT;
553 /* go ahead and tell serf about the connection. */
554 serf_sess->conns[0]->conn =
555 serf_connection_create(serf_sess->context, serf_sess->conns[0]->address,
556 svn_ra_serf__conn_setup, serf_sess->conns[0],
557 svn_ra_serf__conn_closed, serf_sess->conns[0],
558 serf_sess->pool);
560 /* Set the progress callback. */
561 #if SERF_VERSION_AT_LEAST(0,1,3)
562 serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
563 serf_sess);
564 #endif
566 serf_sess->num_conns = 1;
568 session->priv = serf_sess;
570 SVN_ERR(exchange_capabilities(serf_sess, pool));
572 return SVN_NO_ERROR;
575 static svn_error_t *
576 svn_ra_serf__reparent(svn_ra_session_t *ra_session,
577 const char *url,
578 apr_pool_t *pool)
580 svn_ra_serf__session_t *session = ra_session->priv;
581 apr_uri_t new_url;
582 apr_status_t status;
584 /* If it's the URL we already have, wave our hands and do nothing. */
585 if (strcmp(session->repos_url_str, url) == 0)
587 return SVN_NO_ERROR;
590 /* Do we need to check that it's the same host and port? */
591 status = apr_uri_parse(session->pool, url, &new_url);
592 if (status)
594 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
595 _("Illegal repository URL '%s'"), url);
598 session->repos_url.path = new_url.path;
599 session->repos_url_str = apr_pstrdup(session->pool, url);
601 return SVN_NO_ERROR;
604 static svn_error_t *
605 svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
606 const char **url,
607 apr_pool_t *pool)
609 svn_ra_serf__session_t *session = ra_session->priv;
610 *url = apr_pstrdup(pool, session->repos_url_str);
611 return SVN_NO_ERROR;
614 static svn_error_t *
615 svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
616 svn_revnum_t *latest_revnum,
617 apr_pool_t *pool)
619 apr_hash_t *props;
620 svn_ra_serf__session_t *session = ra_session->priv;
621 const char *vcc_url, *baseline_url, *version_name;
623 props = apr_hash_make(pool);
625 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
626 session, session->conns[0],
627 session->repos_url.path, pool));
629 if (!vcc_url)
631 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
632 _("The OPTIONS response did not include the "
633 "requested version-controlled-configuration "
634 "value"));
637 /* Using the version-controlled-configuration, fetch the checked-in prop. */
638 SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
639 vcc_url, SVN_INVALID_REVNUM, "0",
640 checked_in_props, pool));
642 baseline_url = svn_ra_serf__get_prop(props, vcc_url,
643 "DAV:", "checked-in");
645 if (!baseline_url)
647 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
648 _("The OPTIONS response did not include the "
649 "requested checked-in value"));
652 /* Using the checked-in property, fetch:
653 * baseline-connection *and* version-name
655 SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
656 baseline_url, SVN_INVALID_REVNUM,
657 "0", baseline_props, pool));
659 version_name = svn_ra_serf__get_prop(props, baseline_url,
660 "DAV:", SVN_DAV__VERSION_NAME);
662 if (!version_name)
664 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
665 _("The OPTIONS response did not include the "
666 "requested version-name value"));
669 *latest_revnum = SVN_STR_TO_REV(version_name);
671 return SVN_NO_ERROR;
674 static svn_error_t *
675 svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
676 svn_revnum_t rev,
677 apr_hash_t **ret_props,
678 apr_pool_t *pool)
680 svn_ra_serf__session_t *session = ra_session->priv;
681 apr_hash_t *props;
682 const char *vcc_url;
684 props = apr_hash_make(pool);
685 *ret_props = apr_hash_make(pool);
687 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
688 session, session->conns[0],
689 session->repos_url.path, pool));
691 SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
692 vcc_url, rev, "0", all_props, pool));
694 svn_ra_serf__walk_all_props(props, vcc_url, rev, svn_ra_serf__set_bare_props,
695 *ret_props, pool);
697 return SVN_NO_ERROR;
700 static svn_error_t *
701 svn_ra_serf__rev_prop(svn_ra_session_t *session,
702 svn_revnum_t rev,
703 const char *name,
704 svn_string_t **value,
705 apr_pool_t *pool)
707 apr_hash_t *props;
709 SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
711 *value = apr_hash_get(props, name, APR_HASH_KEY_STRING);
713 return SVN_NO_ERROR;
716 static svn_error_t *
717 fetch_path_props(svn_ra_serf__propfind_context_t **ret_prop_ctx,
718 apr_hash_t **ret_props,
719 const char **ret_path,
720 svn_revnum_t *ret_revision,
721 svn_ra_serf__session_t *session,
722 const char *rel_path,
723 svn_revnum_t revision,
724 const svn_ra_serf__dav_props_t *desired_props,
725 apr_pool_t *pool)
727 svn_ra_serf__propfind_context_t *prop_ctx;
728 apr_hash_t *props;
729 const char *path;
731 path = session->repos_url.path;
733 /* If we have a relative path, append it. */
734 if (rel_path)
736 path = svn_path_url_add_component(path, rel_path, pool);
739 props = apr_hash_make(pool);
741 prop_ctx = NULL;
743 /* If we were given a specific revision, we have to fetch the VCC and
744 * do a PROPFIND off of that.
746 if (!SVN_IS_VALID_REVNUM(revision))
748 svn_ra_serf__deliver_props(&prop_ctx, props, session, session->conns[0],
749 path, revision, "0", desired_props, TRUE,
750 NULL, session->pool);
752 else
754 const char *vcc_url, *relative_url, *basecoll_url;
756 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &relative_url,
757 session, session->conns[0],
758 path, pool));
760 SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
761 vcc_url, revision,
762 "0", baseline_props, pool));
764 basecoll_url = svn_ra_serf__get_ver_prop(props, vcc_url, revision,
765 "DAV:", "baseline-collection");
767 if (!basecoll_url)
769 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
770 _("The OPTIONS response did not include the "
771 "requested baseline-collection value"));
774 /* We will try again with our new path; however, we're now
775 * technically an unversioned resource because we are accessing
776 * the revision's baseline-collection.
778 prop_ctx = NULL;
779 path = svn_path_url_add_component(basecoll_url, relative_url, pool);
780 revision = SVN_INVALID_REVNUM;
781 svn_ra_serf__deliver_props(&prop_ctx, props, session, session->conns[0],
782 path, revision, "0",
783 desired_props, TRUE,
784 NULL, session->pool);
787 if (prop_ctx)
789 SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, session, pool));
792 *ret_path = path;
793 *ret_prop_ctx = prop_ctx;
794 *ret_props = props;
795 *ret_revision = revision;
797 return SVN_NO_ERROR;
800 static svn_error_t *
801 svn_ra_serf__check_path(svn_ra_session_t *ra_session,
802 const char *rel_path,
803 svn_revnum_t revision,
804 svn_node_kind_t *kind,
805 apr_pool_t *pool)
807 svn_ra_serf__session_t *session = ra_session->priv;
808 apr_hash_t *props;
809 svn_ra_serf__propfind_context_t *prop_ctx;
810 const char *path, *res_type;
811 svn_revnum_t fetched_rev;
813 SVN_ERR(fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
814 session, rel_path,
815 revision, check_path_props, pool));
817 if (prop_ctx && (svn_ra_serf__propfind_status_code(prop_ctx) == 404))
819 *kind = svn_node_none;
821 else
823 res_type = svn_ra_serf__get_ver_prop(props, path, fetched_rev,
824 "DAV:", "resourcetype");
825 if (!res_type)
827 /* How did this happen? */
828 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
829 _("The OPTIONS response did not include the "
830 "requested resourcetype value"));
832 else if (strcmp(res_type, "collection") == 0)
834 *kind = svn_node_dir;
836 else
838 *kind = svn_node_file;
842 return SVN_NO_ERROR;
845 static svn_error_t *
846 dirent_walker(void *baton,
847 const char *ns, apr_ssize_t ns_len,
848 const char *name, apr_ssize_t name_len,
849 const svn_string_t *val,
850 apr_pool_t *pool)
852 svn_dirent_t *entry = baton;
854 if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
856 entry->has_props = TRUE;
858 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
860 entry->has_props = TRUE;
862 else if (strcmp(ns, "DAV:") == 0)
864 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
866 entry->created_rev = SVN_STR_TO_REV(val->data);
868 else if (strcmp(name, "creator-displayname") == 0)
870 entry->last_author = val->data;
872 else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
874 SVN_ERR(svn_time_from_cstring(&entry->time, val->data, pool));
876 else if (strcmp(name, "getcontentlength") == 0)
878 entry->size = apr_atoi64(val->data);
880 else if (strcmp(name, "resourcetype") == 0)
882 if (strcmp(val->data, "collection") == 0)
884 entry->kind = svn_node_dir;
886 else
888 entry->kind = svn_node_file;
893 return SVN_NO_ERROR;
896 struct path_dirent_visitor_t {
897 apr_hash_t *full_paths;
898 apr_hash_t *base_paths;
899 const char *orig_path;
902 static svn_error_t *
903 path_dirent_walker(void *baton,
904 const char *path, apr_ssize_t path_len,
905 const char *ns, apr_ssize_t ns_len,
906 const char *name, apr_ssize_t name_len,
907 const svn_string_t *val,
908 apr_pool_t *pool)
910 struct path_dirent_visitor_t *dirents = baton;
911 svn_dirent_t *entry;
913 /* Skip our original path. */
914 if (strcmp(path, dirents->orig_path) == 0)
916 return SVN_NO_ERROR;
919 entry = apr_hash_get(dirents->full_paths, path, path_len);
921 if (!entry)
923 const char *base_name;
925 entry = apr_pcalloc(pool, sizeof(*entry));
927 apr_hash_set(dirents->full_paths, path, path_len, entry);
929 base_name = svn_path_uri_decode(svn_path_basename(path, pool), pool);
931 apr_hash_set(dirents->base_paths, base_name, APR_HASH_KEY_STRING, entry);
934 return dirent_walker(entry, ns, ns_len, name, name_len, val, pool);
937 static svn_error_t *
938 svn_ra_serf__stat(svn_ra_session_t *ra_session,
939 const char *rel_path,
940 svn_revnum_t revision,
941 svn_dirent_t **dirent,
942 apr_pool_t *pool)
944 svn_ra_serf__session_t *session = ra_session->priv;
945 apr_hash_t *props;
946 svn_ra_serf__propfind_context_t *prop_ctx;
947 const char *path;
948 svn_revnum_t fetched_rev;
949 svn_dirent_t *entry;
951 SVN_ERR(fetch_path_props(&prop_ctx, &props, &path, &fetched_rev,
952 session, rel_path, revision, all_props, pool));
954 entry = apr_pcalloc(pool, sizeof(*entry));
956 svn_ra_serf__walk_all_props(props, path, fetched_rev, dirent_walker, entry,
957 pool);
959 *dirent = entry;
961 return SVN_NO_ERROR;
964 static svn_error_t *
965 svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
966 apr_hash_t **dirents,
967 svn_revnum_t *fetched_rev,
968 apr_hash_t **ret_props,
969 const char *rel_path,
970 svn_revnum_t revision,
971 apr_uint32_t dirent_fields,
972 apr_pool_t *pool)
974 svn_ra_serf__session_t *session = ra_session->priv;
975 apr_hash_t *props;
976 const char *path;
978 path = session->repos_url.path;
980 /* If we have a relative path, append it. */
981 if (rel_path)
983 path = svn_path_url_add_component(path, rel_path, pool);
986 props = apr_hash_make(pool);
988 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
990 const char *vcc_url, *relative_url, *basecoll_url;
992 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, &relative_url,
993 session, session->conns[0],
994 path, pool));
996 SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
997 vcc_url, revision,
998 "0", baseline_props, pool));
1000 basecoll_url = svn_ra_serf__get_ver_prop(props, vcc_url, revision,
1001 "DAV:", "baseline-collection");
1003 if (!basecoll_url)
1005 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1006 _("The OPTIONS response did not include the "
1007 "requested baseline-collection value"));
1010 if (fetched_rev)
1012 *fetched_rev = revision;
1015 path = svn_path_url_add_component(basecoll_url, relative_url, pool);
1016 revision = SVN_INVALID_REVNUM;
1019 /* If we're asked for children, fetch them now. */
1020 if (dirents)
1022 svn_ra_serf__propfind_context_t *prop_ctx;
1023 struct path_dirent_visitor_t dirent_walk;
1025 prop_ctx = NULL;
1026 svn_ra_serf__deliver_props(&prop_ctx, props, session, session->conns[0],
1027 path, revision, "1", all_props, TRUE,
1028 NULL, session->pool);
1030 if (prop_ctx)
1032 SVN_ERR(svn_ra_serf__wait_for_props(prop_ctx, session, pool));
1035 /* We're going to create two hashes to help the walker along.
1036 * We're going to return the 2nd one back to the caller as it
1037 * will have the basenames it expects.
1039 dirent_walk.full_paths = apr_hash_make(pool);
1040 dirent_walk.base_paths = apr_hash_make(pool);
1041 dirent_walk.orig_path = svn_path_canonicalize(path, pool);
1043 svn_ra_serf__walk_all_paths(props, revision, path_dirent_walker,
1044 &dirent_walk, pool);
1046 *dirents = dirent_walk.base_paths;
1049 /* If we're asked for the directory properties, fetch them too. */
1050 if (ret_props)
1052 props = apr_hash_make(pool);
1053 *ret_props = apr_hash_make(pool);
1055 SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
1056 path, revision, "0", all_props,
1057 pool));
1058 svn_ra_serf__walk_all_props(props, path, revision,
1059 svn_ra_serf__set_flat_props,
1060 *ret_props, pool);
1063 return SVN_NO_ERROR;
1066 static svn_error_t *
1067 svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
1068 const char **url,
1069 apr_pool_t *pool)
1071 svn_ra_serf__session_t *session = ra_session->priv;
1073 if (!session->repos_root_str)
1075 const char *vcc_url;
1077 SVN_ERR(svn_ra_serf__discover_root(&vcc_url, NULL,
1078 session, session->conns[0],
1079 session->repos_url.path, pool));
1082 *url = session->repos_root_str;
1083 return SVN_NO_ERROR;
1086 static svn_error_t *
1087 svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
1088 const char **uuid,
1089 apr_pool_t *pool)
1091 svn_ra_serf__session_t *session = ra_session->priv;
1092 apr_hash_t *props;
1093 const char *root_url;
1095 props = apr_hash_make(pool);
1097 SVN_ERR(svn_ra_serf__get_repos_root(ra_session, &root_url, pool));
1098 SVN_ERR(svn_ra_serf__retrieve_props(props, session, session->conns[0],
1099 root_url, SVN_INVALID_REVNUM, "0",
1100 uuid_props, pool));
1101 *uuid = svn_ra_serf__get_prop(props, root_url,
1102 SVN_DAV_PROP_NS_DAV, "repository-uuid");
1104 if (!*uuid)
1106 return svn_error_create(APR_EGENERAL, NULL,
1107 _("The UUID property was not found on the "
1108 "resource or any of its parents"));
1111 return SVN_NO_ERROR;
1115 static const svn_ra__vtable_t serf_vtable = {
1116 ra_serf_version,
1117 ra_serf_get_description,
1118 ra_serf_get_schemes,
1119 svn_ra_serf__open,
1120 svn_ra_serf__reparent,
1121 svn_ra_serf__get_session_url,
1122 svn_ra_serf__get_latest_revnum,
1123 svn_ra_serf__get_dated_revision,
1124 svn_ra_serf__change_rev_prop,
1125 svn_ra_serf__rev_proplist,
1126 svn_ra_serf__rev_prop,
1127 svn_ra_serf__get_commit_editor,
1128 svn_ra_serf__get_file,
1129 svn_ra_serf__get_dir,
1130 svn_ra_serf__get_mergeinfo,
1131 svn_ra_serf__do_update,
1132 svn_ra_serf__do_switch,
1133 svn_ra_serf__do_status,
1134 svn_ra_serf__do_diff,
1135 svn_ra_serf__get_log,
1136 svn_ra_serf__check_path,
1137 svn_ra_serf__stat,
1138 svn_ra_serf__get_uuid,
1139 svn_ra_serf__get_repos_root,
1140 svn_ra_serf__get_locations,
1141 svn_ra_serf__get_location_segments,
1142 svn_ra_serf__get_file_revs,
1143 svn_ra_serf__lock,
1144 svn_ra_serf__unlock,
1145 svn_ra_serf__get_lock,
1146 svn_ra_serf__get_locks,
1147 svn_ra_serf__replay,
1148 svn_ra_serf__has_capability,
1149 svn_ra_serf__replay_range,
1152 svn_error_t *
1153 svn_ra_serf__init(const svn_version_t *loader_version,
1154 const svn_ra__vtable_t **vtable,
1155 apr_pool_t *pool)
1157 static const svn_version_checklist_t checklist[] =
1159 { "svn_subr", svn_subr_version },
1160 { "svn_delta", svn_delta_version },
1161 { NULL, NULL }
1164 SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist));
1166 /* Simplified version check to make sure we can safely use the
1167 VTABLE parameter. The RA loader does a more exhaustive check. */
1168 if (loader_version->major != SVN_VER_MAJOR)
1170 return svn_error_createf
1171 (SVN_ERR_VERSION_MISMATCH, NULL,
1172 _("Unsupported RA loader version (%d) for ra_serf"),
1173 loader_version->major);
1176 *vtable = &serf_vtable;
1178 return SVN_NO_ERROR;
1181 /* Compatibility wrapper for pre-1.2 subversions. Needed? */
1182 #define NAME "ra_serf"
1183 #define DESCRIPTION RA_SERF_DESCRIPTION
1184 #define VTBL serf_vtable
1185 #define INITFUNC svn_ra_serf__init
1186 #define COMPAT_INITFUNC svn_ra_serf_init
1187 #include "../libsvn_ra/wrapper_template.h"