]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_ra_serf/serf.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_ra_serf / serf.c
1 /*
2  * serf.c :  entry point for ra_serf
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 \f
26 #define APR_WANT_STRFUNC
27 #include <apr_want.h>
28
29 #include <apr_uri.h>
30 #include <serf.h>
31
32 #include "svn_pools.h"
33 #include "svn_ra.h"
34 #include "svn_dav.h"
35 #include "svn_xml.h"
36 #include "../libsvn_ra/ra_loader.h"
37 #include "svn_config.h"
38 #include "svn_delta.h"
39 #include "svn_dirent_uri.h"
40 #include "svn_hash.h"
41 #include "svn_path.h"
42 #include "svn_time.h"
43 #include "svn_version.h"
44
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"
50
51 #include "ra_serf.h"
52
53 \f
54 /* Implements svn_ra__vtable_t.get_version(). */
55 static const svn_version_t *
56 ra_serf_version(void)
57 {
58   SVN_VERSION_BODY;
59 }
60
61 #define RA_SERF_DESCRIPTION \
62     N_("Module for accessing a repository via WebDAV protocol using serf.")
63
64 #define RA_SERF_DESCRIPTION_VER \
65     N_("Module for accessing a repository via WebDAV protocol using serf.\n" \
66        "  - using serf %d.%d.%d")
67
68 /* Implements svn_ra__vtable_t.get_description(). */
69 static const char *
70 ra_serf_get_description(apr_pool_t *pool)
71 {
72   int major, minor, patch;
73
74   serf_lib_version(&major, &minor, &patch);
75   return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), major, minor, patch);
76 }
77
78 /* Implements svn_ra__vtable_t.get_schemes(). */
79 static const char * const *
80 ra_serf_get_schemes(apr_pool_t *pool)
81 {
82   static const char *serf_ssl[] = { "http", "https", NULL };
83 #if 0
84   /* ### Temporary: to shut up a warning. */
85   static const char *serf_no_ssl[] = { "http", NULL };
86 #endif
87
88   /* TODO: Runtime detection. */
89   return serf_ssl;
90 }
91
92 /* Load the setting http-auth-types from the global or server specific
93    section, parse its value and set the types of authentication we should
94    accept from the server. */
95 static svn_error_t *
96 load_http_auth_types(apr_pool_t *pool, svn_config_t *config,
97                      const char *server_group,
98                      int *authn_types)
99 {
100   const char *http_auth_types = NULL;
101   *authn_types = SERF_AUTHN_NONE;
102
103   svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
104                SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
105
106   if (server_group)
107     {
108       svn_config_get(config, &http_auth_types, server_group,
109                      SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
110     }
111
112   if (http_auth_types)
113     {
114       char *token;
115       char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
116       apr_collapse_spaces(auth_types_list, http_auth_types);
117       while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
118         {
119           if (svn_cstring_casecmp("basic", token) == 0)
120             *authn_types |= SERF_AUTHN_BASIC;
121           else if (svn_cstring_casecmp("digest", token) == 0)
122             *authn_types |= SERF_AUTHN_DIGEST;
123           else if (svn_cstring_casecmp("ntlm", token) == 0)
124             *authn_types |= SERF_AUTHN_NTLM;
125           else if (svn_cstring_casecmp("negotiate", token) == 0)
126             *authn_types |= SERF_AUTHN_NEGOTIATE;
127           else
128             return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
129                                      _("Invalid config: unknown %s "
130                                        "'%s'"),
131                                      SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
132       }
133     }
134   else
135     {
136       /* Nothing specified by the user, so accept all types. */
137       *authn_types = SERF_AUTHN_ALL;
138     }
139
140   return SVN_NO_ERROR;
141 }
142
143 /* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
144    runtime configuration variable. */
145 #define DEFAULT_HTTP_TIMEOUT 600
146
147 /* Private symbol for the 1.9-public SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS */
148 #define OPTION_HTTP_CHUNKED_REQUESTS "http-chunked-requests"
149
150
151 static svn_error_t *
152 load_config(svn_ra_serf__session_t *session,
153             apr_hash_t *config_hash,
154             apr_pool_t *pool)
155 {
156   svn_config_t *config, *config_client;
157   const char *server_group;
158   const char *proxy_host = NULL;
159   const char *port_str = NULL;
160   const char *timeout_str = NULL;
161   const char *exceptions;
162   apr_port_t proxy_port;
163   svn_tristate_t chunked_requests;
164
165   if (config_hash)
166     {
167       config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
168       config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
169     }
170   else
171     {
172       config = NULL;
173       config_client = NULL;
174     }
175
176   SVN_ERR(svn_config_get_bool(config, &session->using_compression,
177                               SVN_CONFIG_SECTION_GLOBAL,
178                               SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
179   svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
180                  SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
181
182   if (session->wc_callbacks->auth_baton)
183     {
184       if (config_client)
185         {
186           svn_auth_set_parameter(session->wc_callbacks->auth_baton,
187                                  SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
188                                  config_client);
189         }
190       if (config)
191         {
192           svn_auth_set_parameter(session->wc_callbacks->auth_baton,
193                                  SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS,
194                                  config);
195         }
196     }
197
198   /* Use the default proxy-specific settings if and only if
199      "http-proxy-exceptions" is not set to exclude this host. */
200   svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
201                  SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
202   if (! svn_cstring_match_glob_list(session->session_url.hostname,
203                                     svn_cstring_split(exceptions, ",",
204                                                       TRUE, pool)))
205     {
206       svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
207                      SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
208       svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
209                      SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
210       svn_config_get(config, &session->proxy_username,
211                      SVN_CONFIG_SECTION_GLOBAL,
212                      SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
213       svn_config_get(config, &session->proxy_password,
214                      SVN_CONFIG_SECTION_GLOBAL,
215                      SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
216     }
217
218   /* Load the global ssl settings, if set. */
219   SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
220                               SVN_CONFIG_SECTION_GLOBAL,
221                               SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
222                               TRUE));
223   svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
224                  SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
225
226   /* If set, read the flag that tells us to do bulk updates or not. Defaults
227      to skelta updates. */
228   SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
229                                   SVN_CONFIG_SECTION_GLOBAL,
230                                   SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
231                                   "auto",
232                                   svn_tristate_unknown));
233
234   /* Load the maximum number of parallel session connections. */
235   SVN_ERR(svn_config_get_int64(config, &session->max_connections,
236                                SVN_CONFIG_SECTION_GLOBAL,
237                                SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
238                                SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
239
240   /* Should we use chunked transfer encoding. */ 
241   SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
242                                   SVN_CONFIG_SECTION_GLOBAL,
243                                   OPTION_HTTP_CHUNKED_REQUESTS,
244                                   "auto", svn_tristate_unknown));
245
246   if (config)
247     server_group = svn_config_find_group(config,
248                                          session->session_url.hostname,
249                                          SVN_CONFIG_SECTION_GROUPS, pool);
250   else
251     server_group = NULL;
252
253   if (server_group)
254     {
255       SVN_ERR(svn_config_get_bool(config, &session->using_compression,
256                                   server_group,
257                                   SVN_CONFIG_OPTION_HTTP_COMPRESSION,
258                                   session->using_compression));
259       svn_config_get(config, &timeout_str, server_group,
260                      SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
261
262       svn_auth_set_parameter(session->wc_callbacks->auth_baton,
263                              SVN_AUTH_PARAM_SERVER_GROUP, server_group);
264
265       /* Load the group proxy server settings, overriding global
266          settings.  We intentionally ignore 'http-proxy-exceptions'
267          here because, well, if this site was an exception, why is
268          there a per-server proxy configuration for it?  */
269       svn_config_get(config, &proxy_host, server_group,
270                      SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
271       svn_config_get(config, &port_str, server_group,
272                      SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
273       svn_config_get(config, &session->proxy_username, server_group,
274                      SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
275                      session->proxy_username);
276       svn_config_get(config, &session->proxy_password, server_group,
277                      SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
278                      session->proxy_password);
279
280       /* Load the group ssl settings. */
281       SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
282                                   server_group,
283                                   SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
284                                   session->trust_default_ca));
285       svn_config_get(config, &session->ssl_authorities, server_group,
286                      SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
287                      session->ssl_authorities);
288
289       /* Load the group bulk updates flag. */
290       SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
291                                       server_group,
292                                       SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
293                                       "auto",
294                                       session->bulk_updates));
295
296       /* Load the maximum number of parallel session connections,
297          overriding global values. */
298       SVN_ERR(svn_config_get_int64(config, &session->max_connections,
299                                    server_group,
300                                    SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
301                                    session->max_connections));
302
303       /* Should we use chunked transfer encoding. */ 
304       SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
305                                       server_group,
306                                       OPTION_HTTP_CHUNKED_REQUESTS,
307                                       "auto", chunked_requests));
308     }
309
310   /* Don't allow the http-max-connections value to be larger than our
311      compiled-in limit, or to be too small to operate.  Broken
312      functionality and angry administrators are equally undesirable. */
313   if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
314     session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
315   if (session->max_connections < 2)
316     session->max_connections = 2;
317
318   /* Parse the connection timeout value, if any. */
319   session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
320   if (timeout_str)
321     {
322       char *endstr;
323       const long int timeout = strtol(timeout_str, &endstr, 10);
324
325       if (*endstr)
326         return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
327                                 _("Invalid config: illegal character in "
328                                   "timeout value"));
329       if (timeout < 0)
330         return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
331                                 _("Invalid config: negative timeout value"));
332       session->timeout = apr_time_from_sec(timeout);
333     }
334   SVN_ERR_ASSERT(session->timeout >= 0);
335
336   /* Convert the proxy port value, if any. */
337   if (port_str)
338     {
339       char *endstr;
340       const long int port = strtol(port_str, &endstr, 10);
341
342       if (*endstr)
343         return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
344                                 _("Invalid URL: illegal character in proxy "
345                                   "port number"));
346       if (port < 0)
347         return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
348                                 _("Invalid URL: negative proxy port number"));
349       if (port > 65535)
350         return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
351                                 _("Invalid URL: proxy port number greater "
352                                   "than maximum TCP port number 65535"));
353       proxy_port = (apr_port_t) port;
354     }
355   else
356     {
357       proxy_port = 80;
358     }
359
360   if (proxy_host)
361     {
362       apr_sockaddr_t *proxy_addr;
363       apr_status_t status;
364
365       status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
366                                      APR_UNSPEC, proxy_port, 0,
367                                      session->pool);
368       if (status)
369         {
370           return svn_ra_serf__wrap_err(
371                    status, _("Could not resolve proxy server '%s'"),
372                    proxy_host);
373         }
374       session->using_proxy = TRUE;
375       serf_config_proxy(session->context, proxy_addr);
376     }
377   else
378     {
379       session->using_proxy = FALSE;
380     }
381
382   /* Setup detect_chunking and using_chunked_requests based on
383    * the chunked_requests tristate */
384   if (chunked_requests == svn_tristate_unknown)
385     {
386       session->detect_chunking = TRUE;
387       session->using_chunked_requests = TRUE;
388     }
389   else if (chunked_requests == svn_tristate_true)
390     {
391       session->detect_chunking = FALSE;
392       session->using_chunked_requests = TRUE;
393     }
394   else /* chunked_requests == svn_tristate_false */
395     {
396       session->detect_chunking = FALSE;
397       session->using_chunked_requests = FALSE;
398     }
399
400   /* Setup authentication. */
401   SVN_ERR(load_http_auth_types(pool, config, server_group,
402                                &session->authn_types));
403   serf_config_authn_types(session->context, session->authn_types);
404   serf_config_credentials_callback(session->context,
405                                    svn_ra_serf__credentials_callback);
406
407   return SVN_NO_ERROR;
408 }
409 #undef DEFAULT_HTTP_TIMEOUT
410
411 static void
412 svn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
413 {
414   const svn_ra_serf__session_t *serf_sess = progress_baton;
415   if (serf_sess->progress_func)
416     {
417       serf_sess->progress_func(read + written, -1,
418                                serf_sess->progress_baton,
419                                serf_sess->pool);
420     }
421 }
422
423 /** Our User-Agent string. */
424 static const char *
425 get_user_agent_string(apr_pool_t *pool)
426 {
427   int major, minor, patch;
428   serf_lib_version(&major, &minor, &patch);
429
430   return apr_psprintf(pool, "SVN/%s (%s) serf/%d.%d.%d",
431                       SVN_VER_NUMBER, SVN_BUILD_TARGET,
432                       major, minor, patch);
433 }
434
435 /* Implements svn_ra__vtable_t.open_session(). */
436 static svn_error_t *
437 svn_ra_serf__open(svn_ra_session_t *session,
438                   const char **corrected_url,
439                   const char *session_URL,
440                   const svn_ra_callbacks2_t *callbacks,
441                   void *callback_baton,
442                   apr_hash_t *config,
443                   apr_pool_t *pool)
444 {
445   apr_status_t status;
446   svn_ra_serf__session_t *serf_sess;
447   apr_uri_t url;
448   const char *client_string = NULL;
449   svn_error_t *err;
450
451   if (corrected_url)
452     *corrected_url = NULL;
453
454   serf_sess = apr_pcalloc(pool, sizeof(*serf_sess));
455   serf_sess->pool = svn_pool_create(pool);
456   serf_sess->wc_callbacks = callbacks;
457   serf_sess->wc_callback_baton = callback_baton;
458   serf_sess->progress_func = callbacks->progress_func;
459   serf_sess->progress_baton = callbacks->progress_baton;
460   serf_sess->cancel_func = callbacks->cancel_func;
461   serf_sess->cancel_baton = callback_baton;
462
463   /* todo: reuse serf context across sessions */
464   serf_sess->context = serf_context_create(serf_sess->pool);
465
466   SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
467                                        serf_sess->pool));
468
469
470   status = apr_uri_parse(serf_sess->pool, session_URL, &url);
471   if (status)
472     {
473       return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
474                                _("Illegal URL '%s'"),
475                                session_URL);
476     }
477   /* Depending the version of apr-util in use, for root paths url.path
478      will be NULL or "", where serf requires "/". */
479   if (url.path == NULL || url.path[0] == '\0')
480     {
481       url.path = apr_pstrdup(serf_sess->pool, "/");
482     }
483   if (!url.port)
484     {
485       url.port = apr_uri_port_of_scheme(url.scheme);
486     }
487   serf_sess->session_url = url;
488   serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
489   serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
490
491   serf_sess->supports_deadprop_count = svn_tristate_unknown;
492
493   serf_sess->capabilities = apr_hash_make(serf_sess->pool);
494
495   /* We have to assume that the server only supports HTTP/1.0. Once it's clear
496      HTTP/1.1 is supported, we can upgrade. */
497   serf_sess->http10 = TRUE;
498
499   /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable
500      this, if we find an intervening proxy does not support chunked requests.  */
501   serf_sess->using_chunked_requests = TRUE;
502
503   SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
504
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;
511
512   /* create the user agent string */
513   if (callbacks->get_client_string)
514     SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool));
515
516   if (client_string)
517     serf_sess->useragent = apr_pstrcat(pool, get_user_agent_string(pool), " ",
518                                        client_string, (char *)NULL);
519   else
520     serf_sess->useragent = get_user_agent_string(pool);
521
522   /* go ahead and tell serf about the connection. */
523   status =
524     serf_connection_create2(&serf_sess->conns[0]->conn,
525                             serf_sess->context,
526                             url,
527                             svn_ra_serf__conn_setup, serf_sess->conns[0],
528                             svn_ra_serf__conn_closed, serf_sess->conns[0],
529                             serf_sess->pool);
530   if (status)
531     return svn_ra_serf__wrap_err(status, NULL);
532
533   /* Set the progress callback. */
534   serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
535                                serf_sess);
536
537   serf_sess->num_conns = 1;
538
539   session->priv = serf_sess;
540
541   err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
542
543   /* serf should produce a usable error code instead of APR_EGENERAL */
544   if (err && err->apr_err == APR_EGENERAL)
545     err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
546                             _("Connection to '%s' failed"), session_URL);
547   SVN_ERR(err);
548
549   /* We have set up a useful connection (that doesn't indication a redirect).
550      If we've been told there is possibly a worrisome proxy in our path to the
551      server AND we switched to HTTP/1.1 (chunked requests), then probe for
552      problems in any proxy.  */
553   if ((corrected_url == NULL || *corrected_url == NULL)
554       && serf_sess->detect_chunking && !serf_sess->http10)
555     SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, pool));
556
557   return SVN_NO_ERROR;
558 }
559
560 /* Implements svn_ra__vtable_t.reparent(). */
561 static svn_error_t *
562 svn_ra_serf__reparent(svn_ra_session_t *ra_session,
563                       const char *url,
564                       apr_pool_t *pool)
565 {
566   svn_ra_serf__session_t *session = ra_session->priv;
567   apr_uri_t new_url;
568   apr_status_t status;
569
570   /* If it's the URL we already have, wave our hands and do nothing. */
571   if (strcmp(session->session_url_str, url) == 0)
572     {
573       return SVN_NO_ERROR;
574     }
575
576   if (!session->repos_root_str)
577     {
578       const char *vcc_url;
579       SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
580     }
581
582   if (!svn_uri__is_ancestor(session->repos_root_str, url))
583     {
584       return svn_error_createf(
585           SVN_ERR_RA_ILLEGAL_URL, NULL,
586           _("URL '%s' is not a child of the session's repository root "
587             "URL '%s'"), url, session->repos_root_str);
588     }
589
590   status = apr_uri_parse(pool, url, &new_url);
591   if (status)
592     {
593       return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
594                                _("Illegal repository URL '%s'"), url);
595     }
596
597   /* Depending the version of apr-util in use, for root paths url.path
598      will be NULL or "", where serf requires "/". */
599   /* ### Maybe we should use a string buffer for these strings so we
600      ### don't allocate memory in the session on every reparent? */
601   if (new_url.path == NULL || new_url.path[0] == '\0')
602     {
603       session->session_url.path = apr_pstrdup(session->pool, "/");
604     }
605   else
606     {
607       session->session_url.path = apr_pstrdup(session->pool, new_url.path);
608     }
609   session->session_url_str = apr_pstrdup(session->pool, url);
610
611   return SVN_NO_ERROR;
612 }
613
614 /* Implements svn_ra__vtable_t.get_session_url(). */
615 static svn_error_t *
616 svn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
617                              const char **url,
618                              apr_pool_t *pool)
619 {
620   svn_ra_serf__session_t *session = ra_session->priv;
621   *url = apr_pstrdup(pool, session->session_url_str);
622   return SVN_NO_ERROR;
623 }
624
625 /* Implements svn_ra__vtable_t.get_latest_revnum(). */
626 static svn_error_t *
627 svn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
628                                svn_revnum_t *latest_revnum,
629                                apr_pool_t *pool)
630 {
631   svn_ra_serf__session_t *session = ra_session->priv;
632
633   return svn_error_trace(svn_ra_serf__get_youngest_revnum(
634                            latest_revnum, session, pool));
635 }
636
637 /* Implements svn_ra__vtable_t.rev_proplist(). */
638 static svn_error_t *
639 svn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
640                           svn_revnum_t rev,
641                           apr_hash_t **ret_props,
642                           apr_pool_t *pool)
643 {
644   svn_ra_serf__session_t *session = ra_session->priv;
645   apr_hash_t *props;
646   const char *propfind_path;
647
648   if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
649     {
650       propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
651
652       /* svn_ra_serf__retrieve_props() wants to added the revision as
653          a Label to the PROPFIND, which isn't really necessary when
654          querying a rev-stub URI.  *Shrug*  Probably okay to leave the
655          Label, but whatever. */
656       rev = SVN_INVALID_REVNUM;
657     }
658   else
659     {
660       /* Use the VCC as the propfind target path. */
661       SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool));
662     }
663
664   /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash.  */
665   SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
666                                       propfind_path, rev, "0", all_props,
667                                       pool, pool));
668
669   SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props,
670                                        pool, pool));
671
672   return SVN_NO_ERROR;
673 }
674
675 /* Implements svn_ra__vtable_t.rev_prop(). */
676 static svn_error_t *
677 svn_ra_serf__rev_prop(svn_ra_session_t *session,
678                       svn_revnum_t rev,
679                       const char *name,
680                       svn_string_t **value,
681                       apr_pool_t *pool)
682 {
683   apr_hash_t *props;
684
685   SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
686
687   *value = svn_hash_gets(props, name);
688
689   return SVN_NO_ERROR;
690 }
691
692 static svn_error_t *
693 fetch_path_props(apr_hash_t **props,
694                  svn_ra_serf__session_t *session,
695                  const char *session_relpath,
696                  svn_revnum_t revision,
697                  const svn_ra_serf__dav_props_t *desired_props,
698                  apr_pool_t *result_pool,
699                  apr_pool_t *scratch_pool)
700 {
701   const char *url;
702
703   url = session->session_url.path;
704
705   /* If we have a relative path, append it. */
706   if (session_relpath)
707     url = svn_path_url_add_component2(url, session_relpath, scratch_pool);
708
709   /* If we were given a specific revision, get a URL that refers to that
710      specific revision (rather than floating with HEAD).  */
711   if (SVN_IS_VALID_REVNUM(revision))
712     {
713       SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
714                                           session, NULL /* conn */,
715                                           url, revision,
716                                           scratch_pool, scratch_pool));
717     }
718
719   /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
720      Or we started with SVN_INVALID_REVNUM and URL may be floating.  */
721   SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0],
722                                         url, SVN_INVALID_REVNUM,
723                                         desired_props,
724                                         result_pool, scratch_pool));
725
726   return SVN_NO_ERROR;
727 }
728
729 /* Implements svn_ra__vtable_t.check_path(). */
730 static svn_error_t *
731 svn_ra_serf__check_path(svn_ra_session_t *ra_session,
732                         const char *rel_path,
733                         svn_revnum_t revision,
734                         svn_node_kind_t *kind,
735                         apr_pool_t *pool)
736 {
737   svn_ra_serf__session_t *session = ra_session->priv;
738   apr_hash_t *props;
739
740   svn_error_t *err = fetch_path_props(&props, session, rel_path,
741                                       revision, check_path_props,
742                                       pool, pool);
743
744   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
745     {
746       svn_error_clear(err);
747       *kind = svn_node_none;
748     }
749   else
750     {
751       /* Any other error, raise to caller. */
752       if (err)
753         return svn_error_trace(err);
754
755       SVN_ERR(svn_ra_serf__get_resource_type(kind, props));
756     }
757
758   return SVN_NO_ERROR;
759 }
760
761
762 struct dirent_walker_baton_t {
763   /* Update the fields in this entry.  */
764   svn_dirent_t *entry;
765
766   svn_tristate_t *supports_deadprop_count;
767
768   /* If allocations are necessary, then use this pool.  */
769   apr_pool_t *result_pool;
770 };
771
772 static svn_error_t *
773 dirent_walker(void *baton,
774               const char *ns,
775               const char *name,
776               const svn_string_t *val,
777               apr_pool_t *scratch_pool)
778 {
779   struct dirent_walker_baton_t *dwb = baton;
780
781   if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
782     {
783       dwb->entry->has_props = TRUE;
784     }
785   else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
786     {
787       dwb->entry->has_props = TRUE;
788     }
789   else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
790     {
791       if(strcmp(name, "deadprop-count") == 0)
792         {
793           if (*val->data)
794             {
795               apr_int64_t deadprop_count;
796               SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
797               dwb->entry->has_props = deadprop_count > 0;
798               if (dwb->supports_deadprop_count)
799                 *dwb->supports_deadprop_count = svn_tristate_true;
800             }
801           else if (dwb->supports_deadprop_count)
802             *dwb->supports_deadprop_count = svn_tristate_false;
803         }
804     }
805   else if (strcmp(ns, "DAV:") == 0)
806     {
807       if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
808         {
809           dwb->entry->created_rev = SVN_STR_TO_REV(val->data);
810         }
811       else if (strcmp(name, "creator-displayname") == 0)
812         {
813           dwb->entry->last_author = val->data;
814         }
815       else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
816         {
817           SVN_ERR(svn_time_from_cstring(&dwb->entry->time,
818                                         val->data,
819                                         dwb->result_pool));
820         }
821       else if (strcmp(name, "getcontentlength") == 0)
822         {
823           /* 'getcontentlength' property is empty for directories. */
824           if (val->len)
825             {
826               SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data));
827             }
828         }
829       else if (strcmp(name, "resourcetype") == 0)
830         {
831           if (strcmp(val->data, "collection") == 0)
832             {
833               dwb->entry->kind = svn_node_dir;
834             }
835           else
836             {
837               dwb->entry->kind = svn_node_file;
838             }
839         }
840     }
841
842   return SVN_NO_ERROR;
843 }
844
845 struct path_dirent_visitor_t {
846   apr_hash_t *full_paths;
847   apr_hash_t *base_paths;
848   const char *orig_path;
849   svn_tristate_t supports_deadprop_count;
850   apr_pool_t *result_pool;
851 };
852
853 static svn_error_t *
854 path_dirent_walker(void *baton,
855                    const char *path, apr_ssize_t path_len,
856                    const char *ns, apr_ssize_t ns_len,
857                    const char *name, apr_ssize_t name_len,
858                    const svn_string_t *val,
859                    apr_pool_t *pool)
860 {
861   struct path_dirent_visitor_t *dirents = baton;
862   struct dirent_walker_baton_t dwb;
863   svn_dirent_t *entry;
864
865   /* Skip our original path. */
866   if (strcmp(path, dirents->orig_path) == 0)
867     {
868       return SVN_NO_ERROR;
869     }
870
871   entry = apr_hash_get(dirents->full_paths, path, path_len);
872
873   if (!entry)
874     {
875       const char *base_name;
876
877       entry = svn_dirent_create(pool);
878
879       apr_hash_set(dirents->full_paths, path, path_len, entry);
880
881       base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool),
882                                       pool);
883
884       svn_hash_sets(dirents->base_paths, base_name, entry);
885     }
886
887   dwb.entry = entry;
888   dwb.supports_deadprop_count = &dirents->supports_deadprop_count;
889   dwb.result_pool = dirents->result_pool;
890   return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool));
891 }
892
893 static const svn_ra_serf__dav_props_t *
894 get_dirent_props(apr_uint32_t dirent_fields,
895                  svn_ra_serf__session_t *session,
896                  apr_pool_t *pool)
897 {
898   svn_ra_serf__dav_props_t *prop;
899   apr_array_header_t *props = apr_array_make
900     (pool, 7, sizeof(svn_ra_serf__dav_props_t));
901
902   if (session->supports_deadprop_count != svn_tristate_false
903       || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
904     {
905       if (dirent_fields & SVN_DIRENT_KIND)
906         {
907           prop = apr_array_push(props);
908           prop->namespace = "DAV:";
909           prop->name = "resourcetype";
910         }
911
912       if (dirent_fields & SVN_DIRENT_SIZE)
913         {
914           prop = apr_array_push(props);
915           prop->namespace = "DAV:";
916           prop->name = "getcontentlength";
917         }
918
919       if (dirent_fields & SVN_DIRENT_HAS_PROPS)
920         {
921           prop = apr_array_push(props);
922           prop->namespace = SVN_DAV_PROP_NS_DAV;
923           prop->name = "deadprop-count";
924         }
925
926       if (dirent_fields & SVN_DIRENT_CREATED_REV)
927         {
928           svn_ra_serf__dav_props_t *p = apr_array_push(props);
929           p->namespace = "DAV:";
930           p->name = SVN_DAV__VERSION_NAME;
931         }
932
933       if (dirent_fields & SVN_DIRENT_TIME)
934         {
935           prop = apr_array_push(props);
936           prop->namespace = "DAV:";
937           prop->name = SVN_DAV__CREATIONDATE;
938         }
939
940       if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
941         {
942           prop = apr_array_push(props);
943           prop->namespace = "DAV:";
944           prop->name = "creator-displayname";
945         }
946     }
947   else
948     {
949       /* We found an old subversion server that can't handle
950          the deadprop-count property in the way we expect.
951
952          The neon behavior is to retrieve all properties in this case */
953       prop = apr_array_push(props);
954       prop->namespace = "DAV:";
955       prop->name = "allprop";
956     }
957
958   prop = apr_array_push(props);
959   prop->namespace = NULL;
960   prop->name = NULL;
961
962   return (svn_ra_serf__dav_props_t *) props->elts;
963 }
964
965 /* Implements svn_ra__vtable_t.stat(). */
966 static svn_error_t *
967 svn_ra_serf__stat(svn_ra_session_t *ra_session,
968                   const char *rel_path,
969                   svn_revnum_t revision,
970                   svn_dirent_t **dirent,
971                   apr_pool_t *pool)
972 {
973   svn_ra_serf__session_t *session = ra_session->priv;
974   apr_hash_t *props;
975   svn_error_t *err;
976   struct dirent_walker_baton_t dwb;
977   svn_tristate_t deadprop_count = svn_tristate_unknown;
978
979   err = fetch_path_props(&props,
980                          session, rel_path, revision,
981                          get_dirent_props(SVN_DIRENT_ALL, session, pool),
982                          pool, pool);
983   if (err)
984     {
985       if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
986         {
987           svn_error_clear(err);
988           *dirent = NULL;
989           return SVN_NO_ERROR;
990         }
991       else
992         return svn_error_trace(err);
993     }
994
995   dwb.entry = svn_dirent_create(pool);
996   dwb.supports_deadprop_count = &deadprop_count;
997   dwb.result_pool = pool;
998   SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
999
1000   if (deadprop_count == svn_tristate_false
1001       && session->supports_deadprop_count == svn_tristate_unknown
1002       && !dwb.entry->has_props)
1003     {
1004       /* We have to requery as the server didn't give us the right
1005          information */
1006       session->supports_deadprop_count = svn_tristate_false;
1007
1008       SVN_ERR(fetch_path_props(&props,
1009                                session, rel_path, SVN_INVALID_REVNUM,
1010                                get_dirent_props(SVN_DIRENT_ALL, session, pool),
1011                                pool, pool));
1012
1013       SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
1014     }
1015
1016   if (deadprop_count != svn_tristate_unknown)
1017     session->supports_deadprop_count = deadprop_count;
1018
1019   *dirent = dwb.entry;
1020
1021   return SVN_NO_ERROR;
1022 }
1023
1024 /* Reads the 'resourcetype' property from the list PROPS and checks if the
1025  * resource at PATH@REVISION really is a directory. Returns
1026  * SVN_ERR_FS_NOT_DIRECTORY if not.
1027  */
1028 static svn_error_t *
1029 resource_is_directory(apr_hash_t *props)
1030 {
1031   svn_node_kind_t kind;
1032
1033   SVN_ERR(svn_ra_serf__get_resource_type(&kind, props));
1034
1035   if (kind != svn_node_dir)
1036     {
1037       return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1038                               _("Can't get entries of non-directory"));
1039     }
1040
1041   return SVN_NO_ERROR;
1042 }
1043
1044 /* Implements svn_ra__vtable_t.get_dir(). */
1045 static svn_error_t *
1046 svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
1047                      apr_hash_t **dirents,
1048                      svn_revnum_t *fetched_rev,
1049                      apr_hash_t **ret_props,
1050                      const char *rel_path,
1051                      svn_revnum_t revision,
1052                      apr_uint32_t dirent_fields,
1053                      apr_pool_t *pool)
1054 {
1055   svn_ra_serf__session_t *session = ra_session->priv;
1056   const char *path;
1057
1058   path = session->session_url.path;
1059
1060   /* If we have a relative path, URI encode and append it. */
1061   if (rel_path)
1062     {
1063       path = svn_path_url_add_component2(path, rel_path, pool);
1064     }
1065
1066   /* If the user specified a peg revision other than HEAD, we have to fetch
1067      the baseline collection url for that revision. If not, we can use the
1068      public url. */
1069   if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
1070     {
1071       SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
1072                                           session, NULL /* conn */,
1073                                           path, revision,
1074                                           pool, pool));
1075       revision = SVN_INVALID_REVNUM;
1076     }
1077   /* REVISION is always SVN_INVALID_REVNUM  */
1078   SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
1079
1080   /* If we're asked for children, fetch them now. */
1081   if (dirents)
1082     {
1083       struct path_dirent_visitor_t dirent_walk;
1084       apr_hash_t *props;
1085       const char *rtype;
1086
1087       /* Always request node kind to check that path is really a
1088        * directory.
1089        */
1090       dirent_fields |= SVN_DIRENT_KIND;
1091       SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
1092                                           path, SVN_INVALID_REVNUM, "1",
1093                                           get_dirent_props(dirent_fields,
1094                                                            session, pool),
1095                                           pool, pool));
1096
1097       /* Check if the path is really a directory. */
1098       rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype");
1099       if (rtype == NULL || strcmp(rtype, "collection") != 0)
1100         return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1101                                 _("Can't get entries of non-directory"));
1102
1103       /* We're going to create two hashes to help the walker along.
1104        * We're going to return the 2nd one back to the caller as it
1105        * will have the basenames it expects.
1106        */
1107       dirent_walk.full_paths = apr_hash_make(pool);
1108       dirent_walk.base_paths = apr_hash_make(pool);
1109       dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool);
1110       dirent_walk.supports_deadprop_count = svn_tristate_unknown;
1111       dirent_walk.result_pool = pool;
1112
1113       SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
1114                                           path_dirent_walker, &dirent_walk,
1115                                           pool));
1116
1117       if (dirent_walk.supports_deadprop_count == svn_tristate_false
1118           && session->supports_deadprop_count == svn_tristate_unknown
1119           && dirent_fields & SVN_DIRENT_HAS_PROPS)
1120         {
1121           /* We have to requery as the server didn't give us the right
1122              information */
1123           session->supports_deadprop_count = svn_tristate_false;
1124           SVN_ERR(svn_ra_serf__retrieve_props(&props, session,
1125                                               session->conns[0],
1126                                               path, SVN_INVALID_REVNUM, "1",
1127                                               get_dirent_props(dirent_fields,
1128                                                                session, pool),
1129                                               pool, pool));
1130
1131           apr_hash_clear(dirent_walk.full_paths);
1132           apr_hash_clear(dirent_walk.base_paths);
1133
1134           SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
1135                                               path_dirent_walker,
1136                                               &dirent_walk, pool));
1137         }
1138
1139       *dirents = dirent_walk.base_paths;
1140
1141       if (dirent_walk.supports_deadprop_count != svn_tristate_unknown)
1142         session->supports_deadprop_count = dirent_walk.supports_deadprop_count;
1143     }
1144
1145   /* If we're asked for the directory properties, fetch them too. */
1146   if (ret_props)
1147     {
1148       apr_hash_t *props;
1149
1150       SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0],
1151                                             path, SVN_INVALID_REVNUM,
1152                                             all_props,
1153                                             pool, pool));
1154
1155       /* Check if the path is really a directory. */
1156       SVN_ERR(resource_is_directory(props));
1157
1158       /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
1159          ### put them into POOL, so we're okay.  */
1160       SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool));
1161     }
1162
1163   return SVN_NO_ERROR;
1164 }
1165
1166 svn_error_t *
1167 svn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
1168                             const char **url,
1169                             apr_pool_t *pool)
1170 {
1171   svn_ra_serf__session_t *session = ra_session->priv;
1172
1173   if (!session->repos_root_str)
1174     {
1175       const char *vcc_url;
1176       SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
1177     }
1178
1179   *url = session->repos_root_str;
1180   return SVN_NO_ERROR;
1181 }
1182
1183 /* TODO: to fetch the uuid from the repository, we need:
1184    1. a path that exists in HEAD
1185    2. a path that's readable
1186
1187    get_uuid handles the case where a path doesn't exist in HEAD and also the
1188    case where the root of the repository is not readable.
1189    However, it does not handle the case where we're fetching path not existing
1190    in HEAD of a repository with unreadable root directory.
1191
1192    Implements svn_ra__vtable_t.get_uuid().
1193  */
1194 static svn_error_t *
1195 svn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
1196                       const char **uuid,
1197                       apr_pool_t *pool)
1198 {
1199   svn_ra_serf__session_t *session = ra_session->priv;
1200
1201   if (!session->uuid)
1202     {
1203       const char *vcc_url;
1204
1205       /* We should never get here if we have HTTP v2 support, because
1206          any server with that support should be transmitting the
1207          UUID in the initial OPTIONS response.  */
1208       SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1209
1210       /* We're not interested in vcc_url and relative_url, but this call also
1211          stores the repository's uuid in the session. */
1212       SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
1213       if (!session->uuid)
1214         {
1215           return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
1216                                   _("The UUID property was not found on the "
1217                                     "resource or any of its parents"));
1218         }
1219     }
1220
1221   *uuid = session->uuid;
1222
1223   return SVN_NO_ERROR;
1224 }
1225
1226
1227 static const svn_ra__vtable_t serf_vtable = {
1228   ra_serf_version,
1229   ra_serf_get_description,
1230   ra_serf_get_schemes,
1231   svn_ra_serf__open,
1232   svn_ra_serf__reparent,
1233   svn_ra_serf__get_session_url,
1234   svn_ra_serf__get_latest_revnum,
1235   svn_ra_serf__get_dated_revision,
1236   svn_ra_serf__change_rev_prop,
1237   svn_ra_serf__rev_proplist,
1238   svn_ra_serf__rev_prop,
1239   svn_ra_serf__get_commit_editor,
1240   svn_ra_serf__get_file,
1241   svn_ra_serf__get_dir,
1242   svn_ra_serf__get_mergeinfo,
1243   svn_ra_serf__do_update,
1244   svn_ra_serf__do_switch,
1245   svn_ra_serf__do_status,
1246   svn_ra_serf__do_diff,
1247   svn_ra_serf__get_log,
1248   svn_ra_serf__check_path,
1249   svn_ra_serf__stat,
1250   svn_ra_serf__get_uuid,
1251   svn_ra_serf__get_repos_root,
1252   svn_ra_serf__get_locations,
1253   svn_ra_serf__get_location_segments,
1254   svn_ra_serf__get_file_revs,
1255   svn_ra_serf__lock,
1256   svn_ra_serf__unlock,
1257   svn_ra_serf__get_lock,
1258   svn_ra_serf__get_locks,
1259   svn_ra_serf__replay,
1260   svn_ra_serf__has_capability,
1261   svn_ra_serf__replay_range,
1262   svn_ra_serf__get_deleted_rev,
1263   svn_ra_serf__register_editor_shim_callbacks,
1264   svn_ra_serf__get_inherited_props
1265 };
1266
1267 svn_error_t *
1268 svn_ra_serf__init(const svn_version_t *loader_version,
1269                   const svn_ra__vtable_t **vtable,
1270                   apr_pool_t *pool)
1271 {
1272   static const svn_version_checklist_t checklist[] =
1273     {
1274       { "svn_subr",  svn_subr_version },
1275       { "svn_delta", svn_delta_version },
1276       { NULL, NULL }
1277     };
1278   int serf_major;
1279   int serf_minor;
1280   int serf_patch;
1281
1282   SVN_ERR(svn_ver_check_list2(ra_serf_version(), checklist, svn_ver_equal));
1283
1284   /* Simplified version check to make sure we can safely use the
1285      VTABLE parameter. The RA loader does a more exhaustive check. */
1286   if (loader_version->major != SVN_VER_MAJOR)
1287     {
1288       return svn_error_createf(
1289          SVN_ERR_VERSION_MISMATCH, NULL,
1290          _("Unsupported RA loader version (%d) for ra_serf"),
1291          loader_version->major);
1292     }
1293
1294   /* Make sure that we have loaded a compatible library: the MAJOR must
1295      match, and the minor must be at *least* what we compiled against.
1296      The patch level is simply ignored.  */
1297   serf_lib_version(&serf_major, &serf_minor, &serf_patch);
1298   if (serf_major != SERF_MAJOR_VERSION
1299       || serf_minor < SERF_MINOR_VERSION)
1300     {
1301       return svn_error_createf(
1302          /* ### should return a unique error  */
1303          SVN_ERR_VERSION_MISMATCH, NULL,
1304          _("ra_serf was compiled for serf %d.%d.%d but loaded "
1305            "an incompatible %d.%d.%d library"),
1306          SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
1307          serf_major, serf_minor, serf_patch);
1308     }
1309
1310   *vtable = &serf_vtable;
1311
1312   return SVN_NO_ERROR;
1313 }
1314
1315 /* Compatibility wrapper for pre-1.2 subversions.  Needed? */
1316 #define NAME "ra_serf"
1317 #define DESCRIPTION RA_SERF_DESCRIPTION
1318 #define VTBL serf_vtable
1319 #define INITFUNC svn_ra_serf__init
1320 #define COMPAT_INITFUNC svn_ra_serf_init
1321 #include "../libsvn_ra/wrapper_template.h"