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