2 * serf.c : entry point 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 * ====================================================================
26 #define APR_WANT_STRFUNC
32 #include "svn_pools.h"
36 #include "../libsvn_ra/ra_loader.h"
37 #include "svn_config.h"
38 #include "svn_delta.h"
39 #include "svn_dirent_uri.h"
43 #include "svn_version.h"
45 #include "private/svn_dav_protocol.h"
46 #include "private/svn_dep_compat.h"
47 #include "private/svn_fspath.h"
48 #include "private/svn_subr_private.h"
49 #include "svn_private_config.h"
54 /* Implements svn_ra__vtable_t.get_version(). */
55 static const svn_version_t *
61 #define RA_SERF_DESCRIPTION \
62 N_("Module for accessing a repository via WebDAV protocol using serf.")
64 /* Implements svn_ra__vtable_t.get_description(). */
66 ra_serf_get_description(void)
68 return _(RA_SERF_DESCRIPTION);
71 /* Implements svn_ra__vtable_t.get_schemes(). */
72 static const char * const *
73 ra_serf_get_schemes(apr_pool_t *pool)
75 static const char *serf_ssl[] = { "http", "https", NULL };
77 /* ### Temporary: to shut up a warning. */
78 static const char *serf_no_ssl[] = { "http", NULL };
81 /* TODO: Runtime detection. */
85 /* Load the setting http-auth-types from the global or server specific
86 section, parse its value and set the types of authentication we should
87 accept from the server. */
89 load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
90 const char *server_group,
93 const char *http_auth_types = NULL;
94 *authn_types = SERF_AUTHN_NONE;
96 svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
97 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
101 svn_config_get(config, &http_auth_types, server_group,
102 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
108 char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
109 apr_collapse_spaces(auth_types_list, http_auth_types);
110 while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
112 if (svn_cstring_casecmp("basic", token) == 0)
113 *authn_types |= SERF_AUTHN_BASIC;
114 else if (svn_cstring_casecmp("digest", token) == 0)
115 *authn_types |= SERF_AUTHN_DIGEST;
116 else if (svn_cstring_casecmp("ntlm", token) == 0)
117 *authn_types |= SERF_AUTHN_NTLM;
118 else if (svn_cstring_casecmp("negotiate", token) == 0)
119 *authn_types |= SERF_AUTHN_NEGOTIATE;
121 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
122 _("Invalid config: unknown %s "
124 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
129 /* Nothing specified by the user, so accept all types. */
130 *authn_types = SERF_AUTHN_ALL;
136 /* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
137 runtime configuration variable. */
138 #define DEFAULT_HTTP_TIMEOUT 600
140 /* Private symbol for the 1.9-public SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS */
141 #define OPTION_HTTP_CHUNKED_REQUESTS "http-chunked-requests"
145 load_config(svn_ra_serf__session_t *session,
146 apr_hash_t *config_hash,
149 svn_config_t *config, *config_client;
150 const char *server_group;
151 const char *proxy_host = NULL;
152 const char *port_str = NULL;
153 const char *timeout_str = NULL;
154 const char *exceptions;
155 apr_port_t proxy_port;
156 svn_tristate_t chunked_requests;
160 config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
161 config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
166 config_client = NULL;
169 SVN_ERR(svn_config_get_bool(config, &session->using_compression,
170 SVN_CONFIG_SECTION_GLOBAL,
171 SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
172 svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
173 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
175 if (session->wc_callbacks->auth_baton)
179 svn_auth_set_parameter(session->wc_callbacks->auth_baton,
180 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
185 svn_auth_set_parameter(session->wc_callbacks->auth_baton,
186 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS,
191 /* Use the default proxy-specific settings if and only if
192 "http-proxy-exceptions" is not set to exclude this host. */
193 svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
194 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
195 if (! svn_cstring_match_glob_list(session->session_url.hostname,
196 svn_cstring_split(exceptions, ",",
199 svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
200 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
201 svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
202 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
203 svn_config_get(config, &session->proxy_username,
204 SVN_CONFIG_SECTION_GLOBAL,
205 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
206 svn_config_get(config, &session->proxy_password,
207 SVN_CONFIG_SECTION_GLOBAL,
208 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
211 /* Load the global ssl settings, if set. */
212 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
213 SVN_CONFIG_SECTION_GLOBAL,
214 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
216 svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
217 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
219 /* If set, read the flag that tells us to do bulk updates or not. Defaults
220 to skelta updates. */
221 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
222 SVN_CONFIG_SECTION_GLOBAL,
223 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
225 svn_tristate_unknown));
227 /* Load the maximum number of parallel session connections. */
228 SVN_ERR(svn_config_get_int64(config, &session->max_connections,
229 SVN_CONFIG_SECTION_GLOBAL,
230 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
231 SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
233 /* Should we use chunked transfer encoding. */
234 SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
235 SVN_CONFIG_SECTION_GLOBAL,
236 OPTION_HTTP_CHUNKED_REQUESTS,
237 "auto", svn_tristate_unknown));
240 server_group = svn_config_find_group(config,
241 session->session_url.hostname,
242 SVN_CONFIG_SECTION_GROUPS, pool);
248 SVN_ERR(svn_config_get_bool(config, &session->using_compression,
250 SVN_CONFIG_OPTION_HTTP_COMPRESSION,
251 session->using_compression));
252 svn_config_get(config, &timeout_str, server_group,
253 SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
255 svn_auth_set_parameter(session->wc_callbacks->auth_baton,
256 SVN_AUTH_PARAM_SERVER_GROUP, server_group);
258 /* Load the group proxy server settings, overriding global
259 settings. We intentionally ignore 'http-proxy-exceptions'
260 here because, well, if this site was an exception, why is
261 there a per-server proxy configuration for it? */
262 svn_config_get(config, &proxy_host, server_group,
263 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
264 svn_config_get(config, &port_str, server_group,
265 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
266 svn_config_get(config, &session->proxy_username, server_group,
267 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
268 session->proxy_username);
269 svn_config_get(config, &session->proxy_password, server_group,
270 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
271 session->proxy_password);
273 /* Load the group ssl settings. */
274 SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
276 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
277 session->trust_default_ca));
278 svn_config_get(config, &session->ssl_authorities, server_group,
279 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
280 session->ssl_authorities);
282 /* Load the group bulk updates flag. */
283 SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
285 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
287 session->bulk_updates));
289 /* Load the maximum number of parallel session connections,
290 overriding global values. */
291 SVN_ERR(svn_config_get_int64(config, &session->max_connections,
293 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
294 session->max_connections));
296 /* Should we use chunked transfer encoding. */
297 SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
299 OPTION_HTTP_CHUNKED_REQUESTS,
300 "auto", chunked_requests));
303 /* Don't allow the http-max-connections value to be larger than our
304 compiled-in limit, or to be too small to operate. Broken
305 functionality and angry administrators are equally undesirable. */
306 if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
307 session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
308 if (session->max_connections < 2)
309 session->max_connections = 2;
311 /* Parse the connection timeout value, if any. */
312 session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
316 const long int timeout = strtol(timeout_str, &endstr, 10);
319 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
320 _("Invalid config: illegal character in "
323 return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
324 _("Invalid config: negative timeout value"));
325 session->timeout = apr_time_from_sec(timeout);
327 SVN_ERR_ASSERT(session->timeout >= 0);
329 /* Convert the proxy port value, if any. */
333 const long int port = strtol(port_str, &endstr, 10);
336 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
337 _("Invalid URL: illegal character in proxy "
340 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
341 _("Invalid URL: negative proxy port number"));
343 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
344 _("Invalid URL: proxy port number greater "
345 "than maximum TCP port number 65535"));
346 proxy_port = (apr_port_t) port;
355 apr_sockaddr_t *proxy_addr;
358 status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
359 APR_UNSPEC, proxy_port, 0,
363 return svn_ra_serf__wrap_err(
364 status, _("Could not resolve proxy server '%s'"),
367 session->using_proxy = TRUE;
368 serf_config_proxy(session->context, proxy_addr);
372 session->using_proxy = FALSE;
375 /* Setup detect_chunking and using_chunked_requests based on
376 * the chunked_requests tristate */
377 if (chunked_requests == svn_tristate_unknown)
379 session->detect_chunking = TRUE;
380 session->using_chunked_requests = TRUE;
382 else if (chunked_requests == svn_tristate_true)
384 session->detect_chunking = FALSE;
385 session->using_chunked_requests = TRUE;
387 else /* chunked_requests == svn_tristate_false */
389 session->detect_chunking = FALSE;
390 session->using_chunked_requests = FALSE;
393 /* Setup authentication. */
394 SVN_ERR(load_http_auth_types(pool, config, server_group,
395 &session->authn_types));
396 serf_config_authn_types(session->context, session->authn_types);
397 serf_config_credentials_callback(session->context,
398 svn_ra_serf__credentials_callback);
402 #undef DEFAULT_HTTP_TIMEOUT
405 svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
407 const svn_ra_serf__session_t *serf_sess = progress_baton;
408 if (serf_sess->progress_func)
410 serf_sess->progress_func(read + written, -1,
411 serf_sess->progress_baton,
416 /* Implements svn_ra__vtable_t.open_session(). */
418 svn_ra_serf__open(svn_ra_session_t *session,
419 const char **corrected_url,
420 const char *session_URL,
421 const svn_ra_callbacks2_t *callbacks,
422 void *callback_baton,
427 svn_ra_serf__session_t *serf_sess;
429 const char *client_string = NULL;
433 *corrected_url = NULL;
435 serf_sess = apr_pcalloc(pool, sizeof(*serf_sess));
436 serf_sess->pool = svn_pool_create(pool);
437 serf_sess->wc_callbacks = callbacks;
438 serf_sess->wc_callback_baton = callback_baton;
439 serf_sess->progress_func = callbacks->progress_func;
440 serf_sess->progress_baton = callbacks->progress_baton;
441 serf_sess->cancel_func = callbacks->cancel_func;
442 serf_sess->cancel_baton = callback_baton;
444 /* todo: reuse serf context across sessions */
445 serf_sess->context = serf_context_create(serf_sess->pool);
447 SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
451 status = apr_uri_parse(serf_sess->pool, session_URL, &url);
454 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
455 _("Illegal URL '%s'"),
458 /* Depending the version of apr-util in use, for root paths url.path
459 will be NULL or "", where serf requires "/". */
460 if (url.path == NULL || url.path[0] == '\0')
462 url.path = apr_pstrdup(serf_sess->pool, "/");
466 url.port = apr_uri_port_of_scheme(url.scheme);
468 serf_sess->session_url = url;
469 serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
470 serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
472 serf_sess->supports_deadprop_count = svn_tristate_unknown;
474 serf_sess->capabilities = apr_hash_make(serf_sess->pool);
476 /* We have to assume that the server only supports HTTP/1.0. Once it's clear
477 HTTP/1.1 is supported, we can upgrade. */
478 serf_sess->http10 = TRUE;
480 /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable
481 this, if we find an intervening proxy does not support chunked requests. */
482 serf_sess->using_chunked_requests = TRUE;
484 SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
486 serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
487 sizeof(*serf_sess->conns[0]));
488 serf_sess->conns[0]->bkt_alloc =
489 serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
490 serf_sess->conns[0]->session = serf_sess;
491 serf_sess->conns[0]->last_status_code = -1;
493 /* create the user agent string */
494 if (callbacks->get_client_string)
495 SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool));
498 serf_sess->useragent = apr_pstrcat(pool, USER_AGENT, " ",
499 client_string, (char *)NULL);
501 serf_sess->useragent = USER_AGENT;
503 /* go ahead and tell serf about the connection. */
505 serf_connection_create2(&serf_sess->conns[0]->conn,
508 svn_ra_serf__conn_setup, serf_sess->conns[0],
509 svn_ra_serf__conn_closed, serf_sess->conns[0],
512 return svn_ra_serf__wrap_err(status, NULL);
514 /* Set the progress callback. */
515 serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
518 serf_sess->num_conns = 1;
520 session->priv = serf_sess;
522 err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
524 /* serf should produce a usable error code instead of APR_EGENERAL */
525 if (err && err->apr_err == APR_EGENERAL)
526 err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
527 _("Connection to '%s' failed"), session_URL);
530 /* We have set up a useful connection (that doesn't indication a redirect).
531 If we've been told there is possibly a worrisome proxy in our path to the
532 server AND we switched to HTTP/1.1 (chunked requests), then probe for
533 problems in any proxy. */
534 if ((corrected_url == NULL || *corrected_url == NULL)
535 && serf_sess->detect_chunking && !serf_sess->http10)
536 SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, pool));
541 /* Implements svn_ra__vtable_t.reparent(). */
543 svn_ra_serf__reparent(svn_ra_session_t *ra_session,
547 svn_ra_serf__session_t *session = ra_session->priv;
551 /* If it's the URL we already have, wave our hands and do nothing. */
552 if (strcmp(session->session_url_str, url) == 0)
557 if (!session->repos_root_str)
560 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
563 if (!svn_uri__is_ancestor(session->repos_root_str, url))
565 return svn_error_createf(
566 SVN_ERR_RA_ILLEGAL_URL, NULL,
567 _("URL '%s' is not a child of the session's repository root "
568 "URL '%s'"), url, session->repos_root_str);
571 status = apr_uri_parse(pool, url, &new_url);
574 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
575 _("Illegal repository URL '%s'"), url);
578 /* Depending the version of apr-util in use, for root paths url.path
579 will be NULL or "", where serf requires "/". */
580 /* ### Maybe we should use a string buffer for these strings so we
581 ### don't allocate memory in the session on every reparent? */
582 if (new_url.path == NULL || new_url.path[0] == '\0')
584 session->session_url.path = apr_pstrdup(session->pool, "/");
588 session->session_url.path = apr_pstrdup(session->pool, new_url.path);
590 session->session_url_str = apr_pstrdup(session->pool, url);
595 /* Implements svn_ra__vtable_t.get_session_url(). */
597 svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
601 svn_ra_serf__session_t *session = ra_session->priv;
602 *url = apr_pstrdup(pool, session->session_url_str);
606 /* Implements svn_ra__vtable_t.get_latest_revnum(). */
608 svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
609 svn_revnum_t *latest_revnum,
612 svn_ra_serf__session_t *session = ra_session->priv;
614 return svn_error_trace(svn_ra_serf__get_youngest_revnum(
615 latest_revnum, session, pool));
618 /* Implements svn_ra__vtable_t.rev_proplist(). */
620 svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
622 apr_hash_t **ret_props,
625 svn_ra_serf__session_t *session = ra_session->priv;
627 const char *propfind_path;
629 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
631 propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
633 /* svn_ra_serf__retrieve_props() wants to added the revision as
634 a Label to the PROPFIND, which isn't really necessary when
635 querying a rev-stub URI. *Shrug* Probably okay to leave the
636 Label, but whatever. */
637 rev = SVN_INVALID_REVNUM;
641 /* Use the VCC as the propfind target path. */
642 SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool));
645 /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash. */
646 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
647 propfind_path, rev, "0", all_props,
650 SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props,
656 /* Implements svn_ra__vtable_t.rev_prop(). */
658 svn_ra_serf__rev_prop(svn_ra_session_t *session,
661 svn_string_t **value,
666 SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
668 *value = svn_hash_gets(props, name);
674 fetch_path_props(apr_hash_t **props,
675 svn_ra_serf__session_t *session,
676 const char *session_relpath,
677 svn_revnum_t revision,
678 const svn_ra_serf__dav_props_t *desired_props,
679 apr_pool_t *result_pool,
680 apr_pool_t *scratch_pool)
684 url = session->session_url.path;
686 /* If we have a relative path, append it. */
688 url = svn_path_url_add_component2(url, session_relpath, scratch_pool);
690 /* If we were given a specific revision, get a URL that refers to that
691 specific revision (rather than floating with HEAD). */
692 if (SVN_IS_VALID_REVNUM(revision))
694 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
695 session, NULL /* conn */,
697 scratch_pool, scratch_pool));
700 /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
701 Or we started with SVN_INVALID_REVNUM and URL may be floating. */
702 SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0],
703 url, SVN_INVALID_REVNUM,
705 result_pool, scratch_pool));
710 /* Implements svn_ra__vtable_t.check_path(). */
712 svn_ra_serf__check_path(svn_ra_session_t *ra_session,
713 const char *rel_path,
714 svn_revnum_t revision,
715 svn_node_kind_t *kind,
718 svn_ra_serf__session_t *session = ra_session->priv;
721 svn_error_t *err = fetch_path_props(&props, session, rel_path,
722 revision, check_path_props,
725 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
727 svn_error_clear(err);
728 *kind = svn_node_none;
732 /* Any other error, raise to caller. */
734 return svn_error_trace(err);
736 SVN_ERR(svn_ra_serf__get_resource_type(kind, props));
743 struct dirent_walker_baton_t {
744 /* Update the fields in this entry. */
747 svn_tristate_t *supports_deadprop_count;
749 /* If allocations are necessary, then use this pool. */
750 apr_pool_t *result_pool;
754 dirent_walker(void *baton,
757 const svn_string_t *val,
758 apr_pool_t *scratch_pool)
760 struct dirent_walker_baton_t *dwb = baton;
762 if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
764 dwb->entry->has_props = TRUE;
766 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
768 dwb->entry->has_props = TRUE;
770 else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
772 if(strcmp(name, "deadprop-count") == 0)
776 apr_int64_t deadprop_count;
777 SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
778 dwb->entry->has_props = deadprop_count > 0;
779 if (dwb->supports_deadprop_count)
780 *dwb->supports_deadprop_count = svn_tristate_true;
782 else if (dwb->supports_deadprop_count)
783 *dwb->supports_deadprop_count = svn_tristate_false;
786 else if (strcmp(ns, "DAV:") == 0)
788 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
790 dwb->entry->created_rev = SVN_STR_TO_REV(val->data);
792 else if (strcmp(name, "creator-displayname") == 0)
794 dwb->entry->last_author = val->data;
796 else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
798 SVN_ERR(svn_time_from_cstring(&dwb->entry->time,
802 else if (strcmp(name, "getcontentlength") == 0)
804 /* 'getcontentlength' property is empty for directories. */
807 SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data));
810 else if (strcmp(name, "resourcetype") == 0)
812 if (strcmp(val->data, "collection") == 0)
814 dwb->entry->kind = svn_node_dir;
818 dwb->entry->kind = svn_node_file;
826 struct path_dirent_visitor_t {
827 apr_hash_t *full_paths;
828 apr_hash_t *base_paths;
829 const char *orig_path;
830 svn_tristate_t supports_deadprop_count;
831 apr_pool_t *result_pool;
835 path_dirent_walker(void *baton,
836 const char *path, apr_ssize_t path_len,
837 const char *ns, apr_ssize_t ns_len,
838 const char *name, apr_ssize_t name_len,
839 const svn_string_t *val,
842 struct path_dirent_visitor_t *dirents = baton;
843 struct dirent_walker_baton_t dwb;
846 /* Skip our original path. */
847 if (strcmp(path, dirents->orig_path) == 0)
852 entry = apr_hash_get(dirents->full_paths, path, path_len);
856 const char *base_name;
858 entry = svn_dirent_create(pool);
860 apr_hash_set(dirents->full_paths, path, path_len, entry);
862 base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool),
865 svn_hash_sets(dirents->base_paths, base_name, entry);
869 dwb.supports_deadprop_count = &dirents->supports_deadprop_count;
870 dwb.result_pool = dirents->result_pool;
871 return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool));
874 static const svn_ra_serf__dav_props_t *
875 get_dirent_props(apr_uint32_t dirent_fields,
876 svn_ra_serf__session_t *session,
879 svn_ra_serf__dav_props_t *prop;
880 apr_array_header_t *props = apr_array_make
881 (pool, 7, sizeof(svn_ra_serf__dav_props_t));
883 if (session->supports_deadprop_count != svn_tristate_false
884 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
886 if (dirent_fields & SVN_DIRENT_KIND)
888 prop = apr_array_push(props);
889 prop->namespace = "DAV:";
890 prop->name = "resourcetype";
893 if (dirent_fields & SVN_DIRENT_SIZE)
895 prop = apr_array_push(props);
896 prop->namespace = "DAV:";
897 prop->name = "getcontentlength";
900 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
902 prop = apr_array_push(props);
903 prop->namespace = SVN_DAV_PROP_NS_DAV;
904 prop->name = "deadprop-count";
907 if (dirent_fields & SVN_DIRENT_CREATED_REV)
909 svn_ra_serf__dav_props_t *p = apr_array_push(props);
910 p->namespace = "DAV:";
911 p->name = SVN_DAV__VERSION_NAME;
914 if (dirent_fields & SVN_DIRENT_TIME)
916 prop = apr_array_push(props);
917 prop->namespace = "DAV:";
918 prop->name = SVN_DAV__CREATIONDATE;
921 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
923 prop = apr_array_push(props);
924 prop->namespace = "DAV:";
925 prop->name = "creator-displayname";
930 /* We found an old subversion server that can't handle
931 the deadprop-count property in the way we expect.
933 The neon behavior is to retrieve all properties in this case */
934 prop = apr_array_push(props);
935 prop->namespace = "DAV:";
936 prop->name = "allprop";
939 prop = apr_array_push(props);
940 prop->namespace = NULL;
943 return (svn_ra_serf__dav_props_t *) props->elts;
946 /* Implements svn_ra__vtable_t.stat(). */
948 svn_ra_serf__stat(svn_ra_session_t *ra_session,
949 const char *rel_path,
950 svn_revnum_t revision,
951 svn_dirent_t **dirent,
954 svn_ra_serf__session_t *session = ra_session->priv;
957 struct dirent_walker_baton_t dwb;
958 svn_tristate_t deadprop_count = svn_tristate_unknown;
960 err = fetch_path_props(&props,
961 session, rel_path, revision,
962 get_dirent_props(SVN_DIRENT_ALL, session, pool),
966 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
968 svn_error_clear(err);
973 return svn_error_trace(err);
976 dwb.entry = svn_dirent_create(pool);
977 dwb.supports_deadprop_count = &deadprop_count;
978 dwb.result_pool = pool;
979 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
981 if (deadprop_count == svn_tristate_false
982 && session->supports_deadprop_count == svn_tristate_unknown
983 && !dwb.entry->has_props)
985 /* We have to requery as the server didn't give us the right
987 session->supports_deadprop_count = svn_tristate_false;
989 SVN_ERR(fetch_path_props(&props,
990 session, rel_path, SVN_INVALID_REVNUM,
991 get_dirent_props(SVN_DIRENT_ALL, session, pool),
994 SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
997 if (deadprop_count != svn_tristate_unknown)
998 session->supports_deadprop_count = deadprop_count;
1000 *dirent = dwb.entry;
1002 return SVN_NO_ERROR;
1005 /* Reads the 'resourcetype' property from the list PROPS and checks if the
1006 * resource at PATH@REVISION really is a directory. Returns
1007 * SVN_ERR_FS_NOT_DIRECTORY if not.
1009 static svn_error_t *
1010 resource_is_directory(apr_hash_t *props)
1012 svn_node_kind_t kind;
1014 SVN_ERR(svn_ra_serf__get_resource_type(&kind, props));
1016 if (kind != svn_node_dir)
1018 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1019 _("Can't get entries of non-directory"));
1022 return SVN_NO_ERROR;
1025 /* Implements svn_ra__vtable_t.get_dir(). */
1026 static svn_error_t *
1027 svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
1028 apr_hash_t **dirents,
1029 svn_revnum_t *fetched_rev,
1030 apr_hash_t **ret_props,
1031 const char *rel_path,
1032 svn_revnum_t revision,
1033 apr_uint32_t dirent_fields,
1036 svn_ra_serf__session_t *session = ra_session->priv;
1039 path = session->session_url.path;
1041 /* If we have a relative path, URI encode and append it. */
1044 path = svn_path_url_add_component2(path, rel_path, pool);
1047 /* If the user specified a peg revision other than HEAD, we have to fetch
1048 the baseline collection url for that revision. If not, we can use the
1050 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
1052 SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
1053 session, NULL /* conn */,
1056 revision = SVN_INVALID_REVNUM;
1058 /* REVISION is always SVN_INVALID_REVNUM */
1059 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
1061 /* If we're asked for children, fetch them now. */
1064 struct path_dirent_visitor_t dirent_walk;
1068 /* Always request node kind to check that path is really a
1071 dirent_fields |= SVN_DIRENT_KIND;
1072 SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
1073 path, SVN_INVALID_REVNUM, "1",
1074 get_dirent_props(dirent_fields,
1078 /* Check if the path is really a directory. */
1079 rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype");
1080 if (rtype == NULL || strcmp(rtype, "collection") != 0)
1081 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1082 _("Can't get entries of non-directory"));
1084 /* We're going to create two hashes to help the walker along.
1085 * We're going to return the 2nd one back to the caller as it
1086 * will have the basenames it expects.
1088 dirent_walk.full_paths = apr_hash_make(pool);
1089 dirent_walk.base_paths = apr_hash_make(pool);
1090 dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool);
1091 dirent_walk.supports_deadprop_count = svn_tristate_unknown;
1092 dirent_walk.result_pool = pool;
1094 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
1095 path_dirent_walker, &dirent_walk,
1098 if (dirent_walk.supports_deadprop_count == svn_tristate_false
1099 && session->supports_deadprop_count == svn_tristate_unknown
1100 && dirent_fields & SVN_DIRENT_HAS_PROPS)
1102 /* We have to requery as the server didn't give us the right
1104 session->supports_deadprop_count = svn_tristate_false;
1105 SVN_ERR(svn_ra_serf__retrieve_props(&props, session,
1107 path, SVN_INVALID_REVNUM, "1",
1108 get_dirent_props(dirent_fields,
1112 apr_hash_clear(dirent_walk.full_paths);
1113 apr_hash_clear(dirent_walk.base_paths);
1115 SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
1117 &dirent_walk, pool));
1120 *dirents = dirent_walk.base_paths;
1122 if (dirent_walk.supports_deadprop_count != svn_tristate_unknown)
1123 session->supports_deadprop_count = dirent_walk.supports_deadprop_count;
1126 /* If we're asked for the directory properties, fetch them too. */
1131 SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0],
1132 path, SVN_INVALID_REVNUM,
1136 /* Check if the path is really a directory. */
1137 SVN_ERR(resource_is_directory(props));
1139 /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
1140 ### put them into POOL, so we're okay. */
1141 SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool));
1144 return SVN_NO_ERROR;
1148 svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
1152 svn_ra_serf__session_t *session = ra_session->priv;
1154 if (!session->repos_root_str)
1156 const char *vcc_url;
1157 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
1160 *url = session->repos_root_str;
1161 return SVN_NO_ERROR;
1164 /* TODO: to fetch the uuid from the repository, we need:
1165 1. a path that exists in HEAD
1166 2. a path that's readable
1168 get_uuid handles the case where a path doesn't exist in HEAD and also the
1169 case where the root of the repository is not readable.
1170 However, it does not handle the case where we're fetching path not existing
1171 in HEAD of a repository with unreadable root directory.
1173 Implements svn_ra__vtable_t.get_uuid().
1175 static svn_error_t *
1176 svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
1180 svn_ra_serf__session_t *session = ra_session->priv;
1184 const char *vcc_url;
1186 /* We should never get here if we have HTTP v2 support, because
1187 any server with that support should be transmitting the
1188 UUID in the initial OPTIONS response. */
1189 SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1191 /* We're not interested in vcc_url and relative_url, but this call also
1192 stores the repository's uuid in the session. */
1193 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
1196 return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
1197 _("The UUID property was not found on the "
1198 "resource or any of its parents"));
1202 *uuid = session->uuid;
1204 return SVN_NO_ERROR;
1208 static const svn_ra__vtable_t serf_vtable = {
1210 ra_serf_get_description,
1211 ra_serf_get_schemes,
1213 svn_ra_serf__reparent,
1214 svn_ra_serf__get_session_url,
1215 svn_ra_serf__get_latest_revnum,
1216 svn_ra_serf__get_dated_revision,
1217 svn_ra_serf__change_rev_prop,
1218 svn_ra_serf__rev_proplist,
1219 svn_ra_serf__rev_prop,
1220 svn_ra_serf__get_commit_editor,
1221 svn_ra_serf__get_file,
1222 svn_ra_serf__get_dir,
1223 svn_ra_serf__get_mergeinfo,
1224 svn_ra_serf__do_update,
1225 svn_ra_serf__do_switch,
1226 svn_ra_serf__do_status,
1227 svn_ra_serf__do_diff,
1228 svn_ra_serf__get_log,
1229 svn_ra_serf__check_path,
1231 svn_ra_serf__get_uuid,
1232 svn_ra_serf__get_repos_root,
1233 svn_ra_serf__get_locations,
1234 svn_ra_serf__get_location_segments,
1235 svn_ra_serf__get_file_revs,
1237 svn_ra_serf__unlock,
1238 svn_ra_serf__get_lock,
1239 svn_ra_serf__get_locks,
1240 svn_ra_serf__replay,
1241 svn_ra_serf__has_capability,
1242 svn_ra_serf__replay_range,
1243 svn_ra_serf__get_deleted_rev,
1244 svn_ra_serf__register_editor_shim_callbacks,
1245 svn_ra_serf__get_inherited_props
1249 svn_ra_serf__init(const svn_version_t *loader_version,
1250 const svn_ra__vtable_t **vtable,
1253 static const svn_version_checklist_t checklist[] =
1255 { "svn_subr", svn_subr_version },
1256 { "svn_delta", svn_delta_version },
1263 SVN_ERR(svn_ver_check_list(ra_serf_version(), checklist));
1265 /* Simplified version check to make sure we can safely use the
1266 VTABLE parameter. The RA loader does a more exhaustive check. */
1267 if (loader_version->major != SVN_VER_MAJOR)
1269 return svn_error_createf(
1270 SVN_ERR_VERSION_MISMATCH, NULL,
1271 _("Unsupported RA loader version (%d) for ra_serf"),
1272 loader_version->major);
1275 /* Make sure that we have loaded a compatible library: the MAJOR must
1276 match, and the minor must be at *least* what we compiled against.
1277 The patch level is simply ignored. */
1278 serf_lib_version(&serf_major, &serf_minor, &serf_patch);
1279 if (serf_major != SERF_MAJOR_VERSION
1280 || serf_minor < SERF_MINOR_VERSION)
1282 return svn_error_createf(
1283 /* ### should return a unique error */
1284 SVN_ERR_VERSION_MISMATCH, NULL,
1285 _("ra_serf was compiled for serf %d.%d.%d but loaded "
1286 "an incompatible %d.%d.%d library"),
1287 SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
1288 serf_major, serf_minor, serf_patch);
1291 *vtable = &serf_vtable;
1293 return SVN_NO_ERROR;
1296 /* Compatibility wrapper for pre-1.2 subversions. Needed? */
1297 #define NAME "ra_serf"
1298 #define DESCRIPTION RA_SERF_DESCRIPTION
1299 #define VTBL serf_vtable
1300 #define INITFUNC svn_ra_serf__init
1301 #define COMPAT_INITFUNC svn_ra_serf_init
1302 #include "../libsvn_ra/wrapper_template.h"