* www/release-process.html
[svn.git] / subversion / libsvn_ra_neon / session.c
blob2e48a7065babbecc6135cf52bcc996dbba9c89d3
1 /*
2 * session.c : routines for maintaining sessions state (to the DAV server)
4 * ====================================================================
5 * Copyright (c) 2000-2006 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 #include <assert.h>
22 #include <ctype.h>
24 #define APR_WANT_STRFUNC
25 #include <apr_want.h>
26 #include <apr_general.h>
27 #include <apr_xml.h>
29 #include <ne_auth.h>
31 #include "svn_error.h"
32 #include "svn_pools.h"
33 #include "svn_ra.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"
40 #include "svn_xml.h"
41 #include "svn_private_config.h"
43 #ifdef SVN_NEON_0_28
44 #include <ne_pkcs11.h>
45 #endif
47 #include "ra_neon.h"
49 #define DEFAULT_HTTP_TIMEOUT 3600
52 /* a cleanup routine attached to the pool that contains the RA session
53 baton. */
54 static apr_status_t cleanup_session(void *sess)
56 ne_session_destroy(sess);
57 return APR_SUCCESS;
60 /* a cleanup routine attached to the pool that contains the RA session
61 root URI. */
62 static apr_status_t cleanup_uri(void *uri)
64 ne_uri_free(uri);
65 return APR_SUCCESS;
68 #ifdef SVN_NEON_0_28
69 /* a cleanup routine attached to the pool that contains the PKCS#11
70 provider object. */
71 static apr_status_t cleanup_p11provider(void *provider)
73 ne_ssl_pkcs11_provider *prov = provider;
74 ne_ssl_pkcs11_provider_destroy(prov);
75 return APR_SUCCESS;
77 #endif
79 /* A neon-session callback to 'pull' authentication data when
80 challenged. In turn, this routine 'pulls' the data from the client
81 callbacks if needed. */
82 static int request_auth(void *userdata, const char *realm, int attempt,
83 char *username, char *password)
85 svn_error_t *err;
86 svn_ra_neon__session_t *ras = userdata;
87 void *creds;
88 svn_auth_cred_simple_t *simple_creds;
90 /* Start by clearing the cache of any previously-fetched username. */
91 ras->auth_username = NULL;
93 /* No auth_baton? Give up. */
94 if (! ras->callbacks->auth_baton)
95 return -1;
97 /* Neon automatically tries some auth protocols and bumps the attempt
98 count without using Subversion's callbacks, so we can't depend
99 on attempt == 0 the first time we are called -- we need to check
100 if the auth state has been initted as well. */
101 if (attempt == 0 || ras->auth_iterstate == NULL)
103 const char *realmstring;
105 /* <https://svn.collab.net:80> Subversion repository */
106 realmstring = apr_psprintf(ras->pool, "<%s://%s:%d> %s",
107 ras->root.scheme, ras->root.host,
108 ras->root.port, realm);
110 err = svn_auth_first_credentials(&creds,
111 &(ras->auth_iterstate),
112 SVN_AUTH_CRED_SIMPLE,
113 realmstring,
114 ras->callbacks->auth_baton,
115 ras->pool);
118 else /* attempt > 0 */
119 /* ### TODO: if the http realm changed this time around, we
120 should be calling first_creds(), not next_creds(). */
121 err = svn_auth_next_credentials(&creds,
122 ras->auth_iterstate,
123 ras->pool);
124 if (err || ! creds)
126 svn_error_clear(err);
127 return -1;
129 simple_creds = creds;
131 /* ### silently truncates username/password to 256 chars. */
132 apr_cpystrn(username, simple_creds->username, NE_ABUFSIZ);
133 apr_cpystrn(password, simple_creds->password, NE_ABUFSIZ);
135 /* Cache the fetched username in ra_session. */
136 ras->auth_username = apr_pstrdup(ras->pool, simple_creds->username);
138 return 0;
142 static const apr_uint32_t neon_failure_map[][2] =
144 { NE_SSL_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID },
145 { NE_SSL_EXPIRED, SVN_AUTH_SSL_EXPIRED },
146 { NE_SSL_IDMISMATCH, SVN_AUTH_SSL_CNMISMATCH },
147 { NE_SSL_UNTRUSTED, SVN_AUTH_SSL_UNKNOWNCA }
150 /* Convert neon's SSL failure mask to our own failure mask. */
151 static apr_uint32_t
152 convert_neon_failures(int neon_failures)
154 apr_uint32_t svn_failures = 0;
155 apr_size_t i;
157 for (i = 0; i < sizeof(neon_failure_map) / (2 * sizeof(int)); ++i)
159 if (neon_failures & neon_failure_map[i][0])
161 svn_failures |= neon_failure_map[i][1];
162 neon_failures &= ~neon_failure_map[i][0];
166 /* Map any remaining neon failure bits to our OTHER bit. */
167 if (neon_failures)
169 svn_failures |= SVN_AUTH_SSL_OTHER;
172 return svn_failures;
175 /* A neon-session callback to validate the SSL certificate when the CA
176 is unknown (e.g. a self-signed cert), or there are other SSL
177 certificate problems. */
178 static int
179 server_ssl_callback(void *userdata,
180 int failures,
181 const ne_ssl_certificate *cert)
183 svn_ra_neon__session_t *ras = userdata;
184 svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
185 void *creds;
186 svn_auth_iterstate_t *state;
187 apr_pool_t *pool;
188 svn_error_t *error;
189 char *ascii_cert = ne_ssl_cert_export(cert);
190 char *issuer_dname = ne_ssl_readable_dname(ne_ssl_cert_issuer(cert));
191 svn_auth_ssl_server_cert_info_t cert_info;
192 char fingerprint[NE_SSL_DIGESTLEN];
193 char valid_from[NE_SSL_VDATELEN], valid_until[NE_SSL_VDATELEN];
194 const char *realmstring;
195 apr_uint32_t *svn_failures = apr_palloc(ras->pool, sizeof(*svn_failures));
197 /* Construct the realmstring, e.g. https://svn.collab.net:80 */
198 realmstring = apr_psprintf(ras->pool, "%s://%s:%d", ras->root.scheme,
199 ras->root.host, ras->root.port);
201 *svn_failures = convert_neon_failures(failures);
202 svn_auth_set_parameter(ras->callbacks->auth_baton,
203 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
204 svn_failures);
206 /* Extract the info from the certificate */
207 cert_info.hostname = ne_ssl_cert_identity(cert);
208 if (ne_ssl_cert_digest(cert, fingerprint) != 0)
210 strcpy(fingerprint, "<unknown>");
212 cert_info.fingerprint = fingerprint;
213 ne_ssl_cert_validity(cert, valid_from, valid_until);
214 cert_info.valid_from = valid_from;
215 cert_info.valid_until = valid_until;
216 cert_info.issuer_dname = issuer_dname;
217 cert_info.ascii_cert = ascii_cert;
219 svn_auth_set_parameter(ras->callbacks->auth_baton,
220 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
221 &cert_info);
223 apr_pool_create(&pool, ras->pool);
224 error = svn_auth_first_credentials(&creds, &state,
225 SVN_AUTH_CRED_SSL_SERVER_TRUST,
226 realmstring,
227 ras->callbacks->auth_baton,
228 pool);
229 if (error || ! creds)
231 svn_error_clear(error);
233 else
235 server_creds = creds;
236 error = svn_auth_save_credentials(state, pool);
237 if (error)
239 /* It would be nice to show the error to the user somehow... */
240 svn_error_clear(error);
244 free(issuer_dname);
245 free(ascii_cert);
246 svn_auth_set_parameter(ras->callbacks->auth_baton,
247 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
249 svn_pool_destroy(pool);
250 return ! server_creds;
253 static svn_boolean_t
254 client_ssl_decrypt_cert(svn_ra_neon__session_t *ras,
255 const char *cert_file,
256 ne_ssl_client_cert *clicert)
258 svn_auth_iterstate_t *state;
259 svn_error_t *error;
260 apr_pool_t *pool;
261 svn_boolean_t ok = FALSE;
262 void *creds;
263 int try;
265 apr_pool_create(&pool, ras->pool);
266 for (try = 0; TRUE; ++try)
268 if (try == 0)
270 error = svn_auth_first_credentials(&creds, &state,
271 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
272 cert_file,
273 ras->callbacks->auth_baton,
274 pool);
276 else
278 error = svn_auth_next_credentials(&creds, state, pool);
281 if (error || ! creds)
283 /* Failure or too many attempts */
284 svn_error_clear(error);
285 break;
287 else
289 svn_auth_cred_ssl_client_cert_pw_t *pw_creds = creds;
291 if (ne_ssl_clicert_decrypt(clicert, pw_creds->password) == 0)
293 /* Success */
294 ok = TRUE;
295 break;
299 svn_pool_destroy(pool);
301 return ok;
304 #ifdef SVN_NEON_0_28
305 /* Callback invoked to enter PKCS#11 PIN code. */
306 static int
307 client_ssl_pkcs11_pin_entry(void *userdata,
308 int attempt,
309 const char *slot_descr,
310 const char *token_label,
311 unsigned int flags,
312 char *pin)
314 svn_ra_neon__session_t *ras = userdata;
315 svn_error_t *err;
316 void *creds;
317 svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
319 /* Always prevent PIN caching. */
320 svn_auth_set_parameter(ras->callbacks->auth_baton,
321 SVN_AUTH_PARAM_NO_AUTH_CACHE, "");
323 if (attempt == 0)
325 const char *realmstring;
327 realmstring = apr_psprintf(ras->pool,
328 _("PIN for token \"%s\" in slot \"%s\""),
329 token_label, slot_descr);
331 err = svn_auth_first_credentials(&creds,
332 &(ras->auth_iterstate),
333 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
334 realmstring,
335 ras->callbacks->auth_baton,
336 ras->pool);
338 else
340 err = svn_auth_next_credentials(&creds, ras->auth_iterstate, ras->pool);
343 if (err || ! creds)
345 svn_error_clear(err);
346 return -1;
349 pw_creds = creds;
351 apr_cpystrn(pin, pw_creds->password, NE_SSL_P11PINLEN);
353 return 0;
355 #endif
357 static void
358 client_ssl_callback(void *userdata, ne_session *sess,
359 const ne_ssl_dname *const *dnames,
360 int dncount)
362 svn_ra_neon__session_t *ras = userdata;
363 ne_ssl_client_cert *clicert = NULL;
364 void *creds;
365 svn_auth_iterstate_t *state;
366 const char *realmstring;
367 apr_pool_t *pool;
368 svn_error_t *error;
369 int try;
371 apr_pool_create(&pool, ras->pool);
373 realmstring = apr_psprintf(pool, "%s://%s:%d", ras->root.scheme,
374 ras->root.host, ras->root.port);
376 for (try = 0; TRUE; ++try)
378 if (try == 0)
380 error = svn_auth_first_credentials(&creds, &state,
381 SVN_AUTH_CRED_SSL_CLIENT_CERT,
382 realmstring,
383 ras->callbacks->auth_baton,
384 pool);
386 else
388 error = svn_auth_next_credentials(&creds, state, pool);
391 if (error || ! creds)
393 /* Failure or too many attempts */
394 svn_error_clear(error);
395 break;
397 else
399 svn_auth_cred_ssl_client_cert_t *client_creds = creds;
401 clicert = ne_ssl_clicert_read(client_creds->cert_file);
402 if (clicert)
404 if (! ne_ssl_clicert_encrypted(clicert) ||
405 client_ssl_decrypt_cert(ras, client_creds->cert_file,
406 clicert))
408 ne_ssl_set_clicert(sess, clicert);
410 break;
415 svn_pool_destroy(pool);
418 /* Set *PROXY_HOST, *PROXY_PORT, *PROXY_USERNAME, *PROXY_PASSWORD,
419 * *TIMEOUT_SECONDS, *NEON_DEBUG, *COMPRESSION, *NEON_AUTH_TYPES, and
420 * *PK11_PROVIDER to the information for REQUESTED_HOST, allocated in
421 * POOL, if there is any applicable information. If there is no
422 * applicable information or if there is an error, then set
423 * *PROXY_PORT to (unsigned int) -1, *TIMEOUT_SECONDS and *NEON_DEBUG
424 * to zero, *COMPRESSION to TRUE, *NEON_AUTH_TYPES is left untouched,
425 * and the rest are set to NULL. This function can return an error,
426 * so before examining any values, check the error return value.
428 static svn_error_t *get_server_settings(const char **proxy_host,
429 unsigned int *proxy_port,
430 const char **proxy_username,
431 const char **proxy_password,
432 int *timeout_seconds,
433 int *neon_debug,
434 svn_boolean_t *compression,
435 unsigned int *neon_auth_types,
436 const char **pk11_provider,
437 svn_config_t *cfg,
438 const char *requested_host,
439 apr_pool_t *pool)
441 const char *exceptions, *port_str, *timeout_str, *server_group;
442 const char *debug_str, *http_auth_types;
443 svn_boolean_t is_exception = FALSE;
444 /* If we find nothing, default to nulls. */
445 *proxy_host = NULL;
446 *proxy_port = (unsigned int) -1;
447 *proxy_username = NULL;
448 *proxy_password = NULL;
449 port_str = NULL;
450 timeout_str = NULL;
451 debug_str = NULL;
452 http_auth_types = NULL;
453 *pk11_provider = NULL;
455 /* Use the default proxy-specific settings if and only if
456 "http-proxy-exceptions" is not set to exclude this host. */
457 svn_config_get(cfg, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
458 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, NULL);
459 if (exceptions)
461 apr_array_header_t *l = svn_cstring_split(exceptions, ",", TRUE, pool);
462 is_exception = svn_cstring_match_glob_list(requested_host, l);
464 if (! is_exception)
466 svn_config_get(cfg, proxy_host, SVN_CONFIG_SECTION_GLOBAL,
467 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
468 svn_config_get(cfg, &port_str, SVN_CONFIG_SECTION_GLOBAL,
469 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
470 svn_config_get(cfg, proxy_username, SVN_CONFIG_SECTION_GLOBAL,
471 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
472 svn_config_get(cfg, proxy_password, SVN_CONFIG_SECTION_GLOBAL,
473 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
476 /* Apply non-proxy-specific settings regardless of exceptions: */
477 svn_config_get(cfg, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
478 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
479 SVN_ERR(svn_config_get_bool(cfg, compression, SVN_CONFIG_SECTION_GLOBAL,
480 SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
481 svn_config_get(cfg, &debug_str, SVN_CONFIG_SECTION_GLOBAL,
482 SVN_CONFIG_OPTION_NEON_DEBUG_MASK, NULL);
483 #ifdef SVN_NEON_0_26
484 svn_config_get(cfg, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
485 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
486 #endif
487 svn_config_get(cfg, pk11_provider, SVN_CONFIG_SECTION_GLOBAL,
488 SVN_CONFIG_OPTION_SSL_PKCS11_PROVIDER, NULL);
490 if (cfg)
491 server_group = svn_config_find_group(cfg, requested_host,
492 SVN_CONFIG_SECTION_GROUPS, pool);
493 else
494 server_group = NULL;
496 if (server_group)
498 svn_config_get(cfg, proxy_host, server_group,
499 SVN_CONFIG_OPTION_HTTP_PROXY_HOST, *proxy_host);
500 svn_config_get(cfg, &port_str, server_group,
501 SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
502 svn_config_get(cfg, proxy_username, server_group,
503 SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, *proxy_username);
504 svn_config_get(cfg, proxy_password, server_group,
505 SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, *proxy_password);
506 svn_config_get(cfg, &timeout_str, server_group,
507 SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
508 SVN_ERR(svn_config_get_bool(cfg, compression, server_group,
509 SVN_CONFIG_OPTION_HTTP_COMPRESSION,
510 *compression));
511 svn_config_get(cfg, &debug_str, server_group,
512 SVN_CONFIG_OPTION_NEON_DEBUG_MASK, debug_str);
513 #ifdef SVN_NEON_0_26
514 svn_config_get(cfg, &http_auth_types, server_group,
515 SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
516 #endif
517 svn_config_get(cfg, pk11_provider, server_group,
518 SVN_CONFIG_OPTION_SSL_PKCS11_PROVIDER, *pk11_provider);
521 /* Special case: convert the port value, if any. */
522 if (port_str)
524 char *endstr;
525 const long int port = strtol(port_str, &endstr, 10);
527 if (*endstr)
528 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
529 _("Invalid URL: illegal character in proxy "
530 "port number"));
531 if (port < 0)
532 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
533 _("Invalid URL: negative proxy port number"));
534 if (port > 65535)
535 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
536 _("Invalid URL: proxy port number greater "
537 "than maximum TCP port number 65535"));
538 *proxy_port = port;
540 else
541 *proxy_port = 80;
543 if (timeout_str)
545 char *endstr;
546 const long int timeout = strtol(timeout_str, &endstr, 10);
548 if (*endstr)
549 return svn_error_create(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
550 _("Invalid config: illegal character in "
551 "timeout value"));
552 if (timeout < 0)
553 return svn_error_create(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
554 _("Invalid config: negative timeout value"));
555 *timeout_seconds = timeout;
557 else
558 *timeout_seconds = 0;
560 if (debug_str)
562 char *endstr;
563 const long int debug = strtol(debug_str, &endstr, 10);
565 if (*endstr)
566 return svn_error_create(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
567 _("Invalid config: illegal character in "
568 "debug mask value"));
570 *neon_debug = debug;
572 else
573 *neon_debug = 0;
575 #ifdef SVN_NEON_0_26
576 if (http_auth_types)
578 char *token, *last;
579 char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
580 apr_collapse_spaces(auth_types_list, http_auth_types);
581 while ((token = apr_strtok(auth_types_list, ";", &last)) != NULL)
583 auth_types_list = NULL;
584 if (svn_cstring_casecmp("basic", token) == 0)
585 *neon_auth_types |= NE_AUTH_BASIC;
586 else if (svn_cstring_casecmp("digest", token) == 0)
587 *neon_auth_types |= NE_AUTH_DIGEST;
588 else if (svn_cstring_casecmp("negotiate", token) == 0)
589 *neon_auth_types |= NE_AUTH_NEGOTIATE;
590 else
591 return svn_error_createf(SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
592 _("Invalid config: unknown http auth"
593 "type '%s'"), token);
596 #endif /* SVN_NEON_0_26 */
598 return SVN_NO_ERROR;
602 /* Userdata for the `proxy_auth' function. */
603 struct proxy_auth_baton
605 const char *username; /* Cannot be NULL, but "" is okay. */
606 const char *password; /* Cannot be NULL, but "" is okay. */
610 /* An `ne_request_auth' callback, see ne_auth.h. USERDATA is a
611 * `struct proxy_auth_baton *'.
613 * If ATTEMPT < 10, copy USERDATA->username and USERDATA->password
614 * into USERNAME and PASSWORD respectively (but do not copy more than
615 * NE_ABUFSIZ bytes of either), and return zero to indicate to Neon
616 * that authentication should be attempted.
618 * If ATTEMPT >= 10, copy nothing into USERNAME and PASSWORD and
619 * return 1, to cancel further authentication attempts.
621 * Ignore REALM.
623 * ### Note: There is no particularly good reason for the 10-attempt
624 * limit. Perhaps there should only be one attempt, and if it fails,
625 * we just cancel any further attempts. I used 10 just in case the
626 * proxy tries various times with various realms, since we ignore
627 * REALM. And why do we ignore REALM? Because we currently don't
628 * have any way to specify different auth information for different
629 * realms. (I'm assuming that REALM would be a realm on the proxy
630 * server, not on the Subversion repository server that is the real
631 * destination.) Do we have any need to support proxy realms?
633 static int proxy_auth(void *userdata,
634 const char *realm,
635 int attempt,
636 char *username,
637 char *password)
639 struct proxy_auth_baton *pab = userdata;
641 if (attempt >= 10)
642 return 1;
644 /* Else. */
646 apr_cpystrn(username, pab->username, NE_ABUFSIZ);
647 apr_cpystrn(password, pab->password, NE_ABUFSIZ);
649 return 0;
652 #define RA_NEON_DESCRIPTION \
653 N_("Module for accessing a repository via WebDAV protocol using Neon.")
655 static const char *
656 ra_neon_get_description(void)
658 return _(RA_NEON_DESCRIPTION);
661 static const char * const *
662 ra_neon_get_schemes(apr_pool_t *pool)
664 static const char *schemes_no_ssl[] = { "http", NULL };
665 static const char *schemes_ssl[] = { "http", "https", NULL };
667 return ne_has_support(NE_FEATURE_SSL) ? schemes_ssl : schemes_no_ssl;
670 typedef struct neonprogress_baton_t
672 svn_ra_progress_notify_func_t progress_func;
673 void *progress_baton;
674 apr_pool_t *pool;
675 } neonprogress_baton_t;
677 static void
678 #ifdef SVN_NEON_0_27
679 ra_neon_neonprogress(void *baton, ne_off_t progress, ne_off_t total)
680 #else
681 ra_neon_neonprogress(void *baton, off_t progress, off_t total)
682 #endif /* SVN_NEON_0_27 */
684 const neonprogress_baton_t *neonprogress_baton = baton;
685 if (neonprogress_baton->progress_func)
687 neonprogress_baton->progress_func(progress, total,
688 neonprogress_baton->progress_baton,
689 neonprogress_baton->pool);
695 /** Capabilities exchange. */
697 /* Both server and repository support the capability. */
698 static const char *capability_yes = "yes";
699 /* Either server or repository does not support the capability. */
700 static const char *capability_no = "no";
701 /* Server supports the capability, but don't yet know if repository does. */
702 static const char *capability_server_yes = "server-yes";
705 /* Store in RAS the capabilities discovered from REQ's headers.
706 Use POOL for temporary allocation only. */
707 static void
708 parse_capabilities(ne_request *req,
709 svn_ra_neon__session_t *ras,
710 apr_pool_t *pool)
712 const char *header_value;
714 /* Start out assuming all capabilities are unsupported. */
715 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_DEPTH,
716 APR_HASH_KEY_STRING, capability_no);
717 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
718 APR_HASH_KEY_STRING, capability_no);
719 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
720 APR_HASH_KEY_STRING, capability_no);
722 /* Then find out which ones are supported. */
723 header_value = ne_get_response_header(req, "dav");
724 if (header_value)
726 /* Multiple headers of the same name will have been merged
727 together by the time we see them (either by an intermediary,
728 as is permitted in HTTP, or by neon) -- merged in the sense
729 that if a header "foo" appears multiple times, all the values
730 will be concatenated together, with spaces at the splice
731 points. For example, if the server sent:
733 DAV: 1,2
734 DAV: version-control,checkout,working-resource
735 DAV: merge,baseline,activity,version-controlled-collection
736 DAV: http://subversion.tigris.org/xmlns/dav/svn/depth
738 Here we might see:
740 header_value == "1,2, version-control,checkout,working-resource, merge,baseline,activity,version-controlled-collection, http://subversion.tigris.org/xmlns/dav/svn/depth, <http://apache.org/dav/propset/fs/1>"
742 (Deliberately not line-wrapping that, so you can see what
743 we're about to parse.)
746 apr_array_header_t *vals =
747 svn_cstring_split(header_value, ",", TRUE, pool);
749 /* Right now we only have a few capabilities to detect, so
750 just seek for them directly. This could be written
751 slightly more efficiently, but that wouldn't be worth it
752 until we have many more capabilities. */
754 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_DEPTH, vals))
755 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_DEPTH,
756 APR_HASH_KEY_STRING, capability_yes);
758 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_MERGEINFO, vals))
759 /* The server doesn't know what repository we're referring
760 to, so it can't just say capability_yes. */
761 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
762 APR_HASH_KEY_STRING, capability_server_yes);
764 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_LOG_REVPROPS, vals))
765 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
766 APR_HASH_KEY_STRING, capability_yes);
768 if (svn_cstring_match_glob_list(SVN_DAV_NS_DAV_SVN_PARTIAL_REPLAY,
769 vals))
770 apr_hash_set(ras->capabilities, SVN_RA_CAPABILITY_PARTIAL_REPLAY,
771 APR_HASH_KEY_STRING, capability_yes);
776 /* Exchange capabilities with the server, by sending an OPTIONS
777 request announcing the client's capabilities, and by filling
778 RAS->capabilities with the server's capabilities as read from the
779 response headers. Use POOL only for temporary allocation. */
780 static svn_error_t *
781 exchange_capabilities(svn_ra_neon__session_t *ras, apr_pool_t *pool)
783 int http_ret_code;
784 svn_ra_neon__request_t *rar;
785 svn_error_t *err = SVN_NO_ERROR;
787 rar = svn_ra_neon__request_create(ras, "OPTIONS", ras->url->data, pool);
789 ne_add_request_header(rar->ne_req, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
790 ne_add_request_header(rar->ne_req, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
791 ne_add_request_header(rar->ne_req, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
793 err = svn_ra_neon__request_dispatch(&http_ret_code, rar,
794 NULL, NULL, 200, 0, pool);
795 if (err)
796 goto cleanup;
798 if (http_ret_code == 200)
800 parse_capabilities(rar->ne_req, ras, pool);
802 else
804 /* "can't happen", because svn_ra_neon__request_dispatch()
805 itself should have returned error if response code != 200. */
806 return svn_error_createf
807 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
808 _("OPTIONS request (for capabilities) got HTTP response code %d"),
809 http_ret_code);
812 cleanup:
813 svn_ra_neon__request_destroy(rar);
815 return err;
819 svn_error_t *
820 svn_ra_neon__has_capability(svn_ra_session_t *session,
821 svn_boolean_t *has,
822 const char *capability,
823 apr_pool_t *pool)
825 svn_ra_neon__session_t *ras = session->priv;
826 const char *cap_result;
828 /* This capability doesn't rely on anything server side. */
829 if (strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0)
831 *has = TRUE;
832 return SVN_NO_ERROR;
835 cap_result = apr_hash_get(ras->capabilities,
836 capability,
837 APR_HASH_KEY_STRING);
839 /* If any capability is unknown, they're all unknown, so ask. */
840 if (cap_result == NULL)
841 SVN_ERR(exchange_capabilities(ras, pool));
844 /* Try again, now that we've fetched the capabilities. */
845 cap_result = apr_hash_get(ras->capabilities,
846 capability, APR_HASH_KEY_STRING);
848 /* Some capabilities depend on the repository as well as the server.
849 NOTE: ../libsvn_ra_serf/serf.c:svn_ra_serf__has_capability()
850 has a very similar code block. If you change something here,
851 check there as well. */
852 if (cap_result == capability_server_yes)
854 if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
856 /* Handle mergeinfo specially. Mergeinfo depends on the
857 repository as well as the server, but the server routine
858 that answered our exchange_capabilities() call above
859 didn't even know which repository we were interested in
860 -- it just told us whether the server supports mergeinfo.
861 If the answer was 'no', there's no point checking the
862 particular repository; but if it was 'yes, we still must
863 change it to 'no' iff the repository itself doesn't
864 support mergeinfo. */
865 svn_mergeinfo_catalog_t ignored;
866 svn_error_t *err;
867 apr_array_header_t *paths = apr_array_make(pool, 1,
868 sizeof(char *));
869 APR_ARRAY_PUSH(paths, const char *) = "";
871 err = svn_ra_neon__get_mergeinfo(session, &ignored, paths, 0,
872 FALSE, FALSE, pool);
874 if (err)
876 if (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)
878 svn_error_clear(err);
879 cap_result = capability_no;
881 else if (err->apr_err == SVN_ERR_FS_NOT_FOUND
882 || err->apr_err == SVN_ERR_RA_DAV_PATH_NOT_FOUND)
884 /* Mergeinfo requests use relative paths, and
885 anyway we're in r0, so this is a likely error,
886 but it means the repository supports mergeinfo! */
887 svn_error_clear(err);
888 cap_result = capability_yes;
890 else
891 return err;
894 else
895 cap_result = capability_yes;
897 apr_hash_set(ras->capabilities,
898 SVN_RA_CAPABILITY_MERGEINFO, APR_HASH_KEY_STRING,
899 cap_result);
901 else
903 return svn_error_createf
904 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
905 _("Don't know how to handle '%s' for capability '%s'"),
906 capability_server_yes, capability);
910 if (cap_result == capability_yes)
912 *has = TRUE;
914 else if (cap_result == capability_no)
916 *has = FALSE;
918 else if (cap_result == NULL)
920 return svn_error_createf
921 (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
922 _("Don't know anything about capability '%s'"), capability);
924 else /* "can't happen" */
926 /* Well, let's hope it's a string. */
927 return svn_error_createf
928 (SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
929 _("Attempt to fetch capability '%s' resulted in '%s'"),
930 capability, cap_result);
933 return SVN_NO_ERROR;
938 /* ### need an ne_session_dup to avoid the second gethostbyname
939 * call and make this halfway sane. */
942 /* Parse URL into *URI, doing some sanity checking and initializing the port
943 to a default value if it wasn't specified in URL. */
944 static svn_error_t *
945 parse_url(ne_uri *uri, const char *url)
947 if (ne_uri_parse(url, uri)
948 || uri->host == NULL || uri->path == NULL || uri->scheme == NULL)
950 ne_uri_free(uri);
951 return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
952 _("Malformed URL for repository"));
954 if (uri->port == 0)
955 uri->port = ne_uri_defaultport(uri->scheme);
957 return SVN_NO_ERROR;
960 static svn_error_t *
961 svn_ra_neon__open(svn_ra_session_t *session,
962 const char *repos_URL,
963 const svn_ra_callbacks2_t *callbacks,
964 void *callback_baton,
965 apr_hash_t *config,
966 apr_pool_t *pool)
968 apr_size_t len;
969 ne_session *sess, *sess2;
970 ne_uri *uri = apr_pcalloc(pool, sizeof(*uri));
971 svn_ra_neon__session_t *ras;
972 int is_ssl_session;
973 svn_boolean_t compression;
974 svn_config_t *cfg;
975 const char *server_group;
976 char *itr;
977 unsigned int neon_auth_types = 0;
978 const char *pkcs11_provider;
979 neonprogress_baton_t *neonprogress_baton =
980 apr_pcalloc(pool, sizeof(*neonprogress_baton));
981 const char *useragent = NULL;
982 const char *client_string = NULL;
984 if (callbacks->get_client_string)
985 callbacks->get_client_string(callback_baton, &client_string, pool);
987 if (client_string)
988 useragent = apr_pstrcat(pool, "SVN/" SVN_VERSION "/", client_string, NULL);
989 else
990 useragent = "SVN/" SVN_VERSION;
992 /* Sanity check the URI */
993 SVN_ERR(parse_url(uri, repos_URL));
995 /* make sure we eventually destroy the uri */
996 apr_pool_cleanup_register(pool, uri, cleanup_uri, apr_pool_cleanup_null);
999 /* Can we initialize network? */
1000 if (ne_sock_init() != 0)
1001 return svn_error_create(SVN_ERR_RA_DAV_SOCK_INIT, NULL,
1002 _("Network socket initialization failed"));
1004 /* we want to know if the repository is actually somewhere else */
1005 /* ### not yet: http_redirect_register(sess, ... ); */
1007 /* HACK! Neon uses strcmp when checking for https, but RFC 2396 says
1008 * we should be using case-insensitive comparisons when checking for
1009 * URI schemes. To allow our users to use WeIrd CasE HttPS we force
1010 * the scheme to lower case before we pass it on to Neon, otherwise we
1011 * would crash later on when we assume Neon has set up its https stuff
1012 * but it really didn't. */
1013 for (itr = uri->scheme; *itr; ++itr)
1014 *itr = tolower(*itr);
1016 is_ssl_session = (svn_cstring_casecmp(uri->scheme, "https") == 0);
1017 if (is_ssl_session)
1019 if (ne_has_support(NE_FEATURE_SSL) == 0)
1020 return svn_error_create(SVN_ERR_RA_DAV_SOCK_INIT, NULL,
1021 _("SSL is not supported"));
1023 /* Create two neon session objects, and set their properties... */
1024 sess = ne_session_create(uri->scheme, uri->host, uri->port);
1025 sess2 = ne_session_create(uri->scheme, uri->host, uri->port);
1026 /* make sure we will eventually destroy the session */
1027 apr_pool_cleanup_register(pool, sess, cleanup_session, apr_pool_cleanup_null);
1028 apr_pool_cleanup_register(pool, sess2, cleanup_session,
1029 apr_pool_cleanup_null);
1031 cfg = config ? apr_hash_get(config,
1032 SVN_CONFIG_CATEGORY_SERVERS,
1033 APR_HASH_KEY_STRING) : NULL;
1034 if (cfg)
1035 server_group = svn_config_find_group(cfg, uri->host,
1036 SVN_CONFIG_SECTION_GROUPS, pool);
1037 else
1038 server_group = NULL;
1040 /* If there's a timeout or proxy for this URL, use it. */
1042 const char *proxy_host;
1043 unsigned int proxy_port;
1044 const char *proxy_username;
1045 const char *proxy_password;
1046 int timeout;
1047 int debug;
1049 SVN_ERR(get_server_settings(&proxy_host,
1050 &proxy_port,
1051 &proxy_username,
1052 &proxy_password,
1053 &timeout,
1054 &debug,
1055 &compression,
1056 &neon_auth_types,
1057 &pkcs11_provider,
1058 cfg,
1059 uri->host,
1060 pool));
1062 #ifdef SVN_NEON_0_26
1063 if (neon_auth_types == 0)
1065 /* If there were no auth types specified in the configuration
1066 file, provide the appropriate defaults. */
1067 neon_auth_types = NE_AUTH_BASIC | NE_AUTH_DIGEST;
1068 if (is_ssl_session)
1069 neon_auth_types |= NE_AUTH_NEGOTIATE;
1071 #endif
1073 if (debug)
1074 ne_debug_init(stderr, debug);
1076 if (proxy_host)
1078 ne_session_proxy(sess, proxy_host, proxy_port);
1079 ne_session_proxy(sess2, proxy_host, proxy_port);
1081 if (proxy_username)
1083 /* Allocate the baton in pool, not on stack, so it will
1084 last till whenever Neon needs it. */
1085 struct proxy_auth_baton *pab = apr_palloc(pool, sizeof(*pab));
1087 pab->username = proxy_username;
1088 pab->password = proxy_password ? proxy_password : "";
1090 ne_set_proxy_auth(sess, proxy_auth, pab);
1091 ne_set_proxy_auth(sess2, proxy_auth, pab);
1093 #ifdef SVN_NEON_0_26
1094 else
1096 /* Enable (only) the Negotiate scheme for proxy
1097 authentication, if no username/password is
1098 configured. */
1099 ne_add_proxy_auth(sess, NE_AUTH_NEGOTIATE, NULL, NULL);
1100 ne_add_proxy_auth(sess2, NE_AUTH_NEGOTIATE, NULL, NULL);
1102 #endif
1105 if (!timeout)
1106 timeout = DEFAULT_HTTP_TIMEOUT;
1107 ne_set_read_timeout(sess, timeout);
1108 ne_set_read_timeout(sess2, timeout);
1110 #ifdef SVN_NEON_0_27
1111 ne_set_connect_timeout(sess, timeout);
1112 ne_set_connect_timeout(sess2, timeout);
1113 #endif
1116 if (useragent)
1118 ne_set_useragent(sess, useragent);
1119 ne_set_useragent(sess2, useragent);
1121 else
1123 ne_set_useragent(sess, "SVN/" SVN_VERSION);
1124 ne_set_useragent(sess2, "SVN/" SVN_VERSION);
1127 /* clean up trailing slashes from the URL */
1128 len = strlen(uri->path);
1129 if (len > 1 && (uri->path)[len - 1] == '/')
1130 (uri->path)[len - 1] = '\0';
1132 /* Create and fill a session_baton. */
1133 ras = apr_pcalloc(pool, sizeof(*ras));
1134 ras->pool = pool;
1135 ras->url = svn_stringbuf_create(repos_URL, pool);
1136 /* copies uri pointer members, they get free'd in __close. */
1137 ras->root = *uri;
1138 ras->ne_sess = sess;
1139 ras->ne_sess2 = sess2;
1140 ras->callbacks = callbacks;
1141 ras->callback_baton = callback_baton;
1142 ras->compression = compression;
1143 ras->progress_baton = callbacks->progress_baton;
1144 ras->progress_func = callbacks->progress_func;
1145 ras->capabilities = apr_hash_make(ras->pool);
1146 ras->vcc = NULL;
1147 ras->uuid = NULL;
1148 /* save config and server group in the auth parameter hash */
1149 svn_auth_set_parameter(ras->callbacks->auth_baton,
1150 SVN_AUTH_PARAM_CONFIG, cfg);
1151 svn_auth_set_parameter(ras->callbacks->auth_baton,
1152 SVN_AUTH_PARAM_SERVER_GROUP, server_group);
1154 /* note that ras->username and ras->password are still NULL at this
1155 point. */
1157 /* Register an authentication 'pull' callback with the neon sessions */
1158 #ifdef SVN_NEON_0_26
1159 ne_add_server_auth(sess, neon_auth_types, request_auth, ras);
1160 ne_add_server_auth(sess2, neon_auth_types, request_auth, ras);
1161 #else
1162 ne_set_server_auth(sess, request_auth, ras);
1163 ne_set_server_auth(sess2, request_auth, ras);
1164 #endif
1166 if (is_ssl_session)
1168 const char *authorities, *trust_default_ca;
1169 authorities = svn_config_get_server_setting(
1170 cfg, server_group,
1171 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
1172 NULL);
1174 if (authorities != NULL)
1176 char *files, *file, *last;
1177 files = apr_pstrdup(pool, authorities);
1179 while ((file = apr_strtok(files, ";", &last)) != NULL)
1181 ne_ssl_certificate *ca_cert;
1182 files = NULL;
1183 ca_cert = ne_ssl_cert_read(file);
1184 if (ca_cert == NULL)
1186 return svn_error_createf
1187 (SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
1188 _("Invalid config: unable to load certificate file '%s'"),
1189 svn_path_local_style(file, pool));
1191 ne_ssl_trust_cert(sess, ca_cert);
1192 ne_ssl_trust_cert(sess2, ca_cert);
1196 /* When the CA certificate or server certificate has
1197 verification problems, neon will call our verify function before
1198 outright rejection of the connection.*/
1199 ne_ssl_set_verify(sess, server_ssl_callback, ras);
1200 ne_ssl_set_verify(sess2, server_ssl_callback, ras);
1201 /* For client connections, we register a callback for if the server
1202 wants to authenticate the client via client certificate. */
1204 #ifdef SVN_NEON_0_28
1205 if (pkcs11_provider)
1207 ne_ssl_pkcs11_provider *provider;
1208 int rv;
1210 /* Initialize the PKCS#11 provider. */
1211 rv = ne_ssl_pkcs11_provider_init(&provider, pkcs11_provider);
1212 if (rv != NE_PK11_OK)
1214 return svn_error_createf
1215 (SVN_ERR_RA_DAV_INVALID_CONFIG_VALUE, NULL,
1216 _("Invalid config: unable to load PKCS#11 provider '%s'"),
1217 pkcs11_provider);
1220 /* Share the provider between the two sessions. */
1221 ne_ssl_set_pkcs11_provider(sess, provider);
1222 ne_ssl_set_pkcs11_provider(sess2, provider);
1224 ne_ssl_pkcs11_provider_pin(provider, client_ssl_pkcs11_pin_entry,
1225 ras);
1227 apr_pool_cleanup_register(pool, provider, cleanup_p11provider,
1228 apr_pool_cleanup_null);
1230 /* Note the "else"; if a PKCS#11 provider is set up, a client
1231 cert callback is already configured, so don't displace it
1232 with the normal one here. */
1233 else
1234 #endif
1236 ne_ssl_provide_clicert(sess, client_ssl_callback, ras);
1237 ne_ssl_provide_clicert(sess2, client_ssl_callback, ras);
1240 /* See if the user wants us to trust "default" openssl CAs. */
1241 trust_default_ca = svn_config_get_server_setting(
1242 cfg, server_group,
1243 SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
1244 "true");
1246 if (svn_cstring_casecmp(trust_default_ca, "true") == 0)
1248 ne_ssl_trust_default_ca(sess);
1249 ne_ssl_trust_default_ca(sess2);
1252 neonprogress_baton->pool = pool;
1253 neonprogress_baton->progress_baton = callbacks->progress_baton;
1254 neonprogress_baton->progress_func = callbacks->progress_func;
1255 ne_set_progress(sess, ra_neon_neonprogress, neonprogress_baton);
1256 ne_set_progress(sess2, ra_neon_neonprogress, neonprogress_baton);
1257 session->priv = ras;
1259 SVN_ERR(exchange_capabilities(ras, pool));
1261 return SVN_NO_ERROR;
1265 static svn_error_t *svn_ra_neon__reparent(svn_ra_session_t *session,
1266 const char *url,
1267 apr_pool_t *pool)
1269 svn_ra_neon__session_t *ras = session->priv;
1270 ne_uri *uri = apr_pcalloc(session->pool, sizeof(*uri));
1272 SVN_ERR(parse_url(uri, url));
1273 apr_pool_cleanup_register(session->pool, uri, cleanup_uri,
1274 apr_pool_cleanup_null);
1276 ras->root = *uri;
1277 svn_stringbuf_set(ras->url, url);
1278 return SVN_NO_ERROR;
1281 static svn_error_t *svn_ra_neon__get_session_url(svn_ra_session_t *session,
1282 const char **url,
1283 apr_pool_t *pool)
1285 svn_ra_neon__session_t *ras = session->priv;
1286 *url = apr_pstrmemdup(pool, ras->url->data, ras->url->len);
1287 return SVN_NO_ERROR;
1290 static svn_error_t *svn_ra_neon__get_repos_root(svn_ra_session_t *session,
1291 const char **url,
1292 apr_pool_t *pool)
1294 svn_ra_neon__session_t *ras = session->priv;
1296 if (! ras->repos_root)
1298 svn_string_t bc_relative;
1299 svn_stringbuf_t *url_buf;
1301 SVN_ERR(svn_ra_neon__get_baseline_info(NULL, NULL, &bc_relative,
1302 NULL, ras, ras->url->data,
1303 SVN_INVALID_REVNUM, pool));
1305 /* Remove as many path components from the URL as there are components
1306 in bc_relative. */
1307 url_buf = svn_stringbuf_dup(ras->url, pool);
1308 svn_path_remove_components
1309 (url_buf, svn_path_component_count(bc_relative.data));
1310 ras->repos_root = apr_pstrdup(ras->pool, url_buf->data);
1313 *url = ras->repos_root;
1314 return SVN_NO_ERROR;
1318 static svn_error_t *svn_ra_neon__do_get_uuid(svn_ra_session_t *session,
1319 const char **uuid,
1320 apr_pool_t *pool)
1322 svn_ra_neon__session_t *ras = session->priv;
1324 if (! ras->uuid)
1326 svn_ra_neon__resource_t *rsrc;
1327 const char *lopped_path;
1329 SVN_ERR(svn_ra_neon__search_for_starting_props(&rsrc, &lopped_path,
1330 ras, ras->url->data,
1331 pool));
1332 SVN_ERR(svn_ra_neon__maybe_store_auth_info(ras, pool));
1334 if (! ras->uuid)
1336 /* ### better error reporting... */
1337 return svn_error_create(APR_EGENERAL, NULL,
1338 _("The UUID property was not found on the "
1339 "resource or any of its parents"));
1343 /* search_for_starting_props() filled it. */
1344 *uuid = ras->uuid;
1346 return SVN_NO_ERROR;
1350 static const svn_version_t *
1351 ra_neon_version(void)
1353 SVN_VERSION_BODY;
1356 static const svn_ra__vtable_t neon_vtable = {
1357 ra_neon_version,
1358 ra_neon_get_description,
1359 ra_neon_get_schemes,
1360 svn_ra_neon__open,
1361 svn_ra_neon__reparent,
1362 svn_ra_neon__get_session_url,
1363 svn_ra_neon__get_latest_revnum,
1364 svn_ra_neon__get_dated_revision,
1365 svn_ra_neon__change_rev_prop,
1366 svn_ra_neon__rev_proplist,
1367 svn_ra_neon__rev_prop,
1368 svn_ra_neon__get_commit_editor,
1369 svn_ra_neon__get_file,
1370 svn_ra_neon__get_dir,
1371 svn_ra_neon__get_mergeinfo,
1372 svn_ra_neon__do_update,
1373 svn_ra_neon__do_switch,
1374 svn_ra_neon__do_status,
1375 svn_ra_neon__do_diff,
1376 svn_ra_neon__get_log,
1377 svn_ra_neon__do_check_path,
1378 svn_ra_neon__do_stat,
1379 svn_ra_neon__do_get_uuid,
1380 svn_ra_neon__get_repos_root,
1381 svn_ra_neon__get_locations,
1382 svn_ra_neon__get_location_segments,
1383 svn_ra_neon__get_file_revs,
1384 svn_ra_neon__lock,
1385 svn_ra_neon__unlock,
1386 svn_ra_neon__get_lock,
1387 svn_ra_neon__get_locks,
1388 svn_ra_neon__replay,
1389 svn_ra_neon__has_capability,
1390 svn_ra_neon__replay_range
1393 svn_error_t *
1394 svn_ra_neon__init(const svn_version_t *loader_version,
1395 const svn_ra__vtable_t **vtable,
1396 apr_pool_t *pool)
1398 static const svn_version_checklist_t checklist[] =
1400 { "svn_subr", svn_subr_version },
1401 { "svn_delta", svn_delta_version },
1402 { NULL, NULL }
1405 SVN_ERR(svn_ver_check_list(ra_neon_version(), checklist));
1407 /* Simplified version check to make sure we can safely use the
1408 VTABLE parameter. The RA loader does a more exhaustive check. */
1409 if (loader_version->major != SVN_VER_MAJOR)
1411 return svn_error_createf
1412 (SVN_ERR_VERSION_MISMATCH, NULL,
1413 _("Unsupported RA loader version (%d) for ra_neon"),
1414 loader_version->major);
1417 *vtable = &neon_vtable;
1419 return SVN_NO_ERROR;
1422 /* Compatibility wrapper for the 1.1 and before API. */
1423 #define NAME "ra_neon"
1424 #define DESCRIPTION RA_NEON_DESCRIPTION
1425 #define VTBL neon_vtable
1426 #define INITFUNC svn_ra_neon__init
1427 #define COMPAT_INITFUNC svn_ra_dav_init
1428 #include "../libsvn_ra/wrapper_template.h"