1251881Speter/*
2251881Speter * serf.c :  entry point for ra_serf
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#define APR_WANT_STRFUNC
27251881Speter#include <apr_want.h>
28251881Speter
29251881Speter#include <apr_uri.h>
30251881Speter#include <serf.h>
31251881Speter
32251881Speter#include "svn_pools.h"
33251881Speter#include "svn_ra.h"
34251881Speter#include "svn_dav.h"
35251881Speter#include "svn_xml.h"
36251881Speter#include "../libsvn_ra/ra_loader.h"
37251881Speter#include "svn_config.h"
38251881Speter#include "svn_delta.h"
39251881Speter#include "svn_dirent_uri.h"
40251881Speter#include "svn_hash.h"
41251881Speter#include "svn_path.h"
42299742Sdim#include "svn_props.h"
43251881Speter#include "svn_time.h"
44251881Speter#include "svn_version.h"
45251881Speter
46251881Speter#include "private/svn_dav_protocol.h"
47251881Speter#include "private/svn_dep_compat.h"
48251881Speter#include "private/svn_fspath.h"
49251881Speter#include "private/svn_subr_private.h"
50251881Speter#include "svn_private_config.h"
51251881Speter
52251881Speter#include "ra_serf.h"
53251881Speter
54251881Speter
55251881Speter/* Implements svn_ra__vtable_t.get_version(). */
56251881Speterstatic const svn_version_t *
57251881Speterra_serf_version(void)
58251881Speter{
59251881Speter  SVN_VERSION_BODY;
60251881Speter}
61251881Speter
62251881Speter#define RA_SERF_DESCRIPTION \
63251881Speter    N_("Module for accessing a repository via WebDAV protocol using serf.")
64251881Speter
65262253Speter#define RA_SERF_DESCRIPTION_VER \
66262253Speter    N_("Module for accessing a repository via WebDAV protocol using serf.\n" \
67299742Sdim       "  - using serf %d.%d.%d (compiled with %d.%d.%d)")
68262253Speter
69251881Speter/* Implements svn_ra__vtable_t.get_description(). */
70251881Speterstatic const char *
71262253Speterra_serf_get_description(apr_pool_t *pool)
72251881Speter{
73262253Speter  int major, minor, patch;
74262253Speter
75262253Speter  serf_lib_version(&major, &minor, &patch);
76299742Sdim  return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER),
77299742Sdim                      major, minor, patch,
78299742Sdim                      SERF_MAJOR_VERSION,
79299742Sdim                      SERF_MINOR_VERSION,
80299742Sdim                      SERF_PATCH_VERSION
81299742Sdim                      );
82251881Speter}
83251881Speter
84251881Speter/* Implements svn_ra__vtable_t.get_schemes(). */
85251881Speterstatic const char * const *
86251881Speterra_serf_get_schemes(apr_pool_t *pool)
87251881Speter{
88251881Speter  static const char *serf_ssl[] = { "http", "https", NULL };
89251881Speter#if 0
90251881Speter  /* ### Temporary: to shut up a warning. */
91251881Speter  static const char *serf_no_ssl[] = { "http", NULL };
92251881Speter#endif
93251881Speter
94251881Speter  /* TODO: Runtime detection. */
95251881Speter  return serf_ssl;
96251881Speter}
97251881Speter
98251881Speter/* Load the setting http-auth-types from the global or server specific
99251881Speter   section, parse its value and set the types of authentication we should
100251881Speter   accept from the server. */
101251881Speterstatic svn_error_t *
102251881Speterload_http_auth_types(apr_pool_t *pool, svn_config_t *config,
103251881Speter                     const char *server_group,
104251881Speter                     int *authn_types)
105251881Speter{
106251881Speter  const char *http_auth_types = NULL;
107251881Speter  *authn_types = SERF_AUTHN_NONE;
108251881Speter
109251881Speter  svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
110251881Speter               SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
111251881Speter
112251881Speter  if (server_group)
113251881Speter    {
114251881Speter      svn_config_get(config, &http_auth_types, server_group,
115251881Speter                     SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
116251881Speter    }
117251881Speter
118251881Speter  if (http_auth_types)
119251881Speter    {
120251881Speter      char *token;
121251881Speter      char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
122251881Speter      apr_collapse_spaces(auth_types_list, http_auth_types);
123251881Speter      while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
124251881Speter        {
125251881Speter          if (svn_cstring_casecmp("basic", token) == 0)
126251881Speter            *authn_types |= SERF_AUTHN_BASIC;
127251881Speter          else if (svn_cstring_casecmp("digest", token) == 0)
128251881Speter            *authn_types |= SERF_AUTHN_DIGEST;
129251881Speter          else if (svn_cstring_casecmp("ntlm", token) == 0)
130251881Speter            *authn_types |= SERF_AUTHN_NTLM;
131251881Speter          else if (svn_cstring_casecmp("negotiate", token) == 0)
132251881Speter            *authn_types |= SERF_AUTHN_NEGOTIATE;
133251881Speter          else
134251881Speter            return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
135251881Speter                                     _("Invalid config: unknown %s "
136251881Speter                                       "'%s'"),
137251881Speter                                     SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
138251881Speter      }
139251881Speter    }
140251881Speter  else
141251881Speter    {
142251881Speter      /* Nothing specified by the user, so accept all types. */
143251881Speter      *authn_types = SERF_AUTHN_ALL;
144251881Speter    }
145251881Speter
146251881Speter  return SVN_NO_ERROR;
147251881Speter}
148251881Speter
149251881Speter/* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
150251881Speter   runtime configuration variable. */
151251881Speter#define DEFAULT_HTTP_TIMEOUT 600
152251881Speter
153251881Speterstatic svn_error_t *
154251881Speterload_config(svn_ra_serf__session_t *session,
155251881Speter            apr_hash_t *config_hash,
156251881Speter            apr_pool_t *pool)
157251881Speter{
158251881Speter  svn_config_t *config, *config_client;
159251881Speter  const char *server_group;
160251881Speter  const char *proxy_host = NULL;
161251881Speter  const char *port_str = NULL;
162251881Speter  const char *timeout_str = NULL;
163251881Speter  const char *exceptions;
164251881Speter  apr_port_t proxy_port;
165253734Speter  svn_tristate_t chunked_requests;
166299742Sdim#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING)
167299742Sdim  apr_int64_t log_components;
168299742Sdim  apr_int64_t log_level;
169299742Sdim#endif
170251881Speter
171251881Speter  if (config_hash)
172251881Speter    {
173251881Speter      config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
174251881Speter      config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
175251881Speter    }
176251881Speter  else
177251881Speter    {
178251881Speter      config = NULL;
179251881Speter      config_client = NULL;
180251881Speter    }
181251881Speter
182251881Speter  SVN_ERR(svn_config_get_bool(config, &session->using_compression,
183251881Speter                              SVN_CONFIG_SECTION_GLOBAL,
184251881Speter                              SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
185251881Speter  svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
186251881Speter                 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
187251881Speter
188299742Sdim  if (session->auth_baton)
189251881Speter    {
190251881Speter      if (config_client)
191251881Speter        {
192299742Sdim          svn_auth_set_parameter(session->auth_baton,
193251881Speter                                 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
194251881Speter                                 config_client);
195251881Speter        }
196251881Speter      if (config)
197251881Speter        {
198299742Sdim          svn_auth_set_parameter(session->auth_baton,
199251881Speter                                 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS,
200251881Speter                                 config);
201251881Speter        }
202251881Speter    }
203251881Speter
204251881Speter  /* Use the default proxy-specific settings if and only if
205251881Speter     "http-proxy-exceptions" is not set to exclude this host. */
206251881Speter  svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
207251881Speter                 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
208251881Speter  if (! svn_cstring_match_glob_list(session->session_url.hostname,
209251881Speter                                    svn_cstring_split(exceptions, ",",
210251881Speter                                                      TRUE, pool)))
211251881Speter    {
212251881Speter      svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
213251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
214251881Speter      svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
215251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
216251881Speter      svn_config_get(config, &session->proxy_username,
217251881Speter                     SVN_CONFIG_SECTION_GLOBAL,
218251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
219251881Speter      svn_config_get(config, &session->proxy_password,
220251881Speter                     SVN_CONFIG_SECTION_GLOBAL,
221251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
222251881Speter    }
223251881Speter
224251881Speter  /* Load the global ssl settings, if set. */
225251881Speter  SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
226251881Speter                              SVN_CONFIG_SECTION_GLOBAL,
227251881Speter                              SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
228251881Speter                              TRUE));
229251881Speter  svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
230251881Speter                 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
231251881Speter
232251881Speter  /* If set, read the flag that tells us to do bulk updates or not. Defaults
233251881Speter     to skelta updates. */
234251881Speter  SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
235251881Speter                                  SVN_CONFIG_SECTION_GLOBAL,
236251881Speter                                  SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
237251881Speter                                  "auto",
238251881Speter                                  svn_tristate_unknown));
239251881Speter
240251881Speter  /* Load the maximum number of parallel session connections. */
241251881Speter  SVN_ERR(svn_config_get_int64(config, &session->max_connections,
242251881Speter                               SVN_CONFIG_SECTION_GLOBAL,
243251881Speter                               SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
244251881Speter                               SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
245251881Speter
246299742Sdim  /* Should we use chunked transfer encoding. */
247253734Speter  SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
248253734Speter                                  SVN_CONFIG_SECTION_GLOBAL,
249299742Sdim                                  SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS,
250253734Speter                                  "auto", svn_tristate_unknown));
251253734Speter
252299742Sdim#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING)
253299742Sdim  SVN_ERR(svn_config_get_int64(config, &log_components,
254299742Sdim                               SVN_CONFIG_SECTION_GLOBAL,
255299742Sdim                               SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS,
256299742Sdim                               SERF_LOGCOMP_NONE));
257299742Sdim  SVN_ERR(svn_config_get_int64(config, &log_level,
258299742Sdim                               SVN_CONFIG_SECTION_GLOBAL,
259299742Sdim                               SVN_CONFIG_OPTION_SERF_LOG_LEVEL,
260299742Sdim                               SERF_LOG_INFO));
261299742Sdim#endif
262251881Speter
263299742Sdim  server_group = svn_auth_get_parameter(session->auth_baton,
264299742Sdim                                        SVN_AUTH_PARAM_SERVER_GROUP);
265299742Sdim
266251881Speter  if (server_group)
267251881Speter    {
268251881Speter      SVN_ERR(svn_config_get_bool(config, &session->using_compression,
269251881Speter                                  server_group,
270251881Speter                                  SVN_CONFIG_OPTION_HTTP_COMPRESSION,
271251881Speter                                  session->using_compression));
272251881Speter      svn_config_get(config, &timeout_str, server_group,
273251881Speter                     SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
274251881Speter
275251881Speter      /* Load the group proxy server settings, overriding global
276251881Speter         settings.  We intentionally ignore 'http-proxy-exceptions'
277251881Speter         here because, well, if this site was an exception, why is
278251881Speter         there a per-server proxy configuration for it?  */
279251881Speter      svn_config_get(config, &proxy_host, server_group,
280251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
281251881Speter      svn_config_get(config, &port_str, server_group,
282251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
283251881Speter      svn_config_get(config, &session->proxy_username, server_group,
284251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
285251881Speter                     session->proxy_username);
286251881Speter      svn_config_get(config, &session->proxy_password, server_group,
287251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
288251881Speter                     session->proxy_password);
289251881Speter
290251881Speter      /* Load the group ssl settings. */
291251881Speter      SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
292251881Speter                                  server_group,
293251881Speter                                  SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
294251881Speter                                  session->trust_default_ca));
295251881Speter      svn_config_get(config, &session->ssl_authorities, server_group,
296251881Speter                     SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
297251881Speter                     session->ssl_authorities);
298251881Speter
299251881Speter      /* Load the group bulk updates flag. */
300251881Speter      SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
301251881Speter                                      server_group,
302251881Speter                                      SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
303251881Speter                                      "auto",
304251881Speter                                      session->bulk_updates));
305251881Speter
306251881Speter      /* Load the maximum number of parallel session connections,
307251881Speter         overriding global values. */
308251881Speter      SVN_ERR(svn_config_get_int64(config, &session->max_connections,
309251881Speter                                   server_group,
310251881Speter                                   SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
311251881Speter                                   session->max_connections));
312253734Speter
313299742Sdim      /* Should we use chunked transfer encoding. */
314253734Speter      SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
315253734Speter                                      server_group,
316299742Sdim                                      SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS,
317253734Speter                                      "auto", chunked_requests));
318299742Sdim
319299742Sdim#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING)
320299742Sdim      SVN_ERR(svn_config_get_int64(config, &log_components,
321299742Sdim                                   server_group,
322299742Sdim                                   SVN_CONFIG_OPTION_SERF_LOG_COMPONENTS,
323299742Sdim                                   log_components));
324299742Sdim       SVN_ERR(svn_config_get_int64(config, &log_level,
325299742Sdim                                    server_group,
326299742Sdim                                    SVN_CONFIG_OPTION_SERF_LOG_LEVEL,
327299742Sdim                                    log_level));
328299742Sdim#endif
329251881Speter    }
330251881Speter
331299742Sdim#if SERF_VERSION_AT_LEAST(1, 4, 0) && !defined(SVN_SERF_NO_LOGGING)
332299742Sdim  if (log_components != SERF_LOGCOMP_NONE)
333299742Sdim    {
334299742Sdim      serf_log_output_t *output;
335299742Sdim      apr_status_t status;
336299742Sdim
337299742Sdim      status = serf_logging_create_stream_output(&output,
338299742Sdim                                                 session->context,
339299742Sdim                                                 (apr_uint32_t)log_level,
340299742Sdim                                                 (apr_uint32_t)log_components,
341299742Sdim                                                 SERF_LOG_DEFAULT_LAYOUT,
342299742Sdim                                                 stderr,
343299742Sdim                                                 pool);
344299742Sdim
345299742Sdim      if (!status)
346299742Sdim          serf_logging_add_output(session->context, output);
347299742Sdim    }
348299742Sdim#endif
349299742Sdim
350251881Speter  /* Don't allow the http-max-connections value to be larger than our
351251881Speter     compiled-in limit, or to be too small to operate.  Broken
352251881Speter     functionality and angry administrators are equally undesirable. */
353251881Speter  if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
354251881Speter    session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
355251881Speter  if (session->max_connections < 2)
356251881Speter    session->max_connections = 2;
357251881Speter
358251881Speter  /* Parse the connection timeout value, if any. */
359251881Speter  session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
360251881Speter  if (timeout_str)
361251881Speter    {
362251881Speter      char *endstr;
363251881Speter      const long int timeout = strtol(timeout_str, &endstr, 10);
364251881Speter
365251881Speter      if (*endstr)
366251881Speter        return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
367251881Speter                                _("Invalid config: illegal character in "
368251881Speter                                  "timeout value"));
369251881Speter      if (timeout < 0)
370251881Speter        return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
371251881Speter                                _("Invalid config: negative timeout value"));
372251881Speter      session->timeout = apr_time_from_sec(timeout);
373251881Speter    }
374251881Speter  SVN_ERR_ASSERT(session->timeout >= 0);
375251881Speter
376251881Speter  /* Convert the proxy port value, if any. */
377251881Speter  if (port_str)
378251881Speter    {
379251881Speter      char *endstr;
380251881Speter      const long int port = strtol(port_str, &endstr, 10);
381251881Speter
382251881Speter      if (*endstr)
383251881Speter        return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
384251881Speter                                _("Invalid URL: illegal character in proxy "
385251881Speter                                  "port number"));
386251881Speter      if (port < 0)
387251881Speter        return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
388251881Speter                                _("Invalid URL: negative proxy port number"));
389251881Speter      if (port > 65535)
390251881Speter        return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
391251881Speter                                _("Invalid URL: proxy port number greater "
392251881Speter                                  "than maximum TCP port number 65535"));
393251881Speter      proxy_port = (apr_port_t) port;
394251881Speter    }
395251881Speter  else
396251881Speter    {
397251881Speter      proxy_port = 80;
398251881Speter    }
399251881Speter
400251881Speter  if (proxy_host)
401251881Speter    {
402251881Speter      apr_sockaddr_t *proxy_addr;
403251881Speter      apr_status_t status;
404251881Speter
405251881Speter      status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
406251881Speter                                     APR_UNSPEC, proxy_port, 0,
407251881Speter                                     session->pool);
408251881Speter      if (status)
409251881Speter        {
410251881Speter          return svn_ra_serf__wrap_err(
411251881Speter                   status, _("Could not resolve proxy server '%s'"),
412251881Speter                   proxy_host);
413251881Speter        }
414251881Speter      session->using_proxy = TRUE;
415251881Speter      serf_config_proxy(session->context, proxy_addr);
416251881Speter    }
417251881Speter  else
418251881Speter    {
419251881Speter      session->using_proxy = FALSE;
420251881Speter    }
421251881Speter
422253734Speter  /* Setup detect_chunking and using_chunked_requests based on
423253734Speter   * the chunked_requests tristate */
424253734Speter  if (chunked_requests == svn_tristate_unknown)
425253734Speter    {
426253734Speter      session->detect_chunking = TRUE;
427253734Speter      session->using_chunked_requests = TRUE;
428253734Speter    }
429253734Speter  else if (chunked_requests == svn_tristate_true)
430253734Speter    {
431253734Speter      session->detect_chunking = FALSE;
432253734Speter      session->using_chunked_requests = TRUE;
433253734Speter    }
434253734Speter  else /* chunked_requests == svn_tristate_false */
435253734Speter    {
436253734Speter      session->detect_chunking = FALSE;
437253734Speter      session->using_chunked_requests = FALSE;
438253734Speter    }
439253734Speter
440251881Speter  /* Setup authentication. */
441251881Speter  SVN_ERR(load_http_auth_types(pool, config, server_group,
442251881Speter                               &session->authn_types));
443251881Speter  serf_config_authn_types(session->context, session->authn_types);
444251881Speter  serf_config_credentials_callback(session->context,
445251881Speter                                   svn_ra_serf__credentials_callback);
446251881Speter
447251881Speter  return SVN_NO_ERROR;
448251881Speter}
449251881Speter#undef DEFAULT_HTTP_TIMEOUT
450251881Speter
451251881Speterstatic void
452251881Spetersvn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
453251881Speter{
454251881Speter  const svn_ra_serf__session_t *serf_sess = progress_baton;
455251881Speter  if (serf_sess->progress_func)
456251881Speter    {
457251881Speter      serf_sess->progress_func(read + written, -1,
458251881Speter                               serf_sess->progress_baton,
459251881Speter                               serf_sess->pool);
460251881Speter    }
461251881Speter}
462251881Speter
463262253Speter/** Our User-Agent string. */
464262253Speterstatic const char *
465262253Speterget_user_agent_string(apr_pool_t *pool)
466262253Speter{
467262253Speter  int major, minor, patch;
468262253Speter  serf_lib_version(&major, &minor, &patch);
469262253Speter
470262253Speter  return apr_psprintf(pool, "SVN/%s (%s) serf/%d.%d.%d",
471262253Speter                      SVN_VER_NUMBER, SVN_BUILD_TARGET,
472262253Speter                      major, minor, patch);
473262253Speter}
474262253Speter
475251881Speter/* Implements svn_ra__vtable_t.open_session(). */
476251881Speterstatic svn_error_t *
477251881Spetersvn_ra_serf__open(svn_ra_session_t *session,
478251881Speter                  const char **corrected_url,
479251881Speter                  const char *session_URL,
480251881Speter                  const svn_ra_callbacks2_t *callbacks,
481251881Speter                  void *callback_baton,
482299742Sdim                  svn_auth_baton_t *auth_baton,
483251881Speter                  apr_hash_t *config,
484299742Sdim                  apr_pool_t *result_pool,
485299742Sdim                  apr_pool_t *scratch_pool)
486251881Speter{
487251881Speter  apr_status_t status;
488251881Speter  svn_ra_serf__session_t *serf_sess;
489251881Speter  apr_uri_t url;
490251881Speter  const char *client_string = NULL;
491253734Speter  svn_error_t *err;
492251881Speter
493251881Speter  if (corrected_url)
494251881Speter    *corrected_url = NULL;
495251881Speter
496299742Sdim  serf_sess = apr_pcalloc(result_pool, sizeof(*serf_sess));
497299742Sdim  serf_sess->pool = result_pool;
498299742Sdim  if (config)
499299742Sdim    SVN_ERR(svn_config_copy_config(&serf_sess->config, config, result_pool));
500299742Sdim  else
501299742Sdim    serf_sess->config = NULL;
502251881Speter  serf_sess->wc_callbacks = callbacks;
503251881Speter  serf_sess->wc_callback_baton = callback_baton;
504299742Sdim  serf_sess->auth_baton = auth_baton;
505251881Speter  serf_sess->progress_func = callbacks->progress_func;
506251881Speter  serf_sess->progress_baton = callbacks->progress_baton;
507251881Speter  serf_sess->cancel_func = callbacks->cancel_func;
508251881Speter  serf_sess->cancel_baton = callback_baton;
509251881Speter
510251881Speter  /* todo: reuse serf context across sessions */
511251881Speter  serf_sess->context = serf_context_create(serf_sess->pool);
512251881Speter
513251881Speter  SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
514251881Speter                                       serf_sess->pool));
515251881Speter
516251881Speter
517299742Sdim  SVN_ERR(svn_ra_serf__uri_parse(&url, session_URL, serf_sess->pool));
518299742Sdim
519251881Speter  if (!url.port)
520251881Speter    {
521251881Speter      url.port = apr_uri_port_of_scheme(url.scheme);
522251881Speter    }
523251881Speter  serf_sess->session_url = url;
524251881Speter  serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
525251881Speter  serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
526251881Speter
527251881Speter  serf_sess->supports_deadprop_count = svn_tristate_unknown;
528251881Speter
529251881Speter  serf_sess->capabilities = apr_hash_make(serf_sess->pool);
530251881Speter
531251881Speter  /* We have to assume that the server only supports HTTP/1.0. Once it's clear
532251881Speter     HTTP/1.1 is supported, we can upgrade. */
533251881Speter  serf_sess->http10 = TRUE;
534251881Speter
535253734Speter  /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable
536253734Speter     this, if we find an intervening proxy does not support chunked requests.  */
537253734Speter  serf_sess->using_chunked_requests = TRUE;
538253734Speter
539251881Speter  SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
540251881Speter
541251881Speter  serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
542251881Speter                                    sizeof(*serf_sess->conns[0]));
543251881Speter  serf_sess->conns[0]->bkt_alloc =
544251881Speter          serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
545251881Speter  serf_sess->conns[0]->session = serf_sess;
546251881Speter  serf_sess->conns[0]->last_status_code = -1;
547251881Speter
548251881Speter  /* create the user agent string */
549251881Speter  if (callbacks->get_client_string)
550299742Sdim    SVN_ERR(callbacks->get_client_string(callback_baton, &client_string,
551299742Sdim                                         scratch_pool));
552251881Speter
553251881Speter  if (client_string)
554299742Sdim    serf_sess->useragent = apr_pstrcat(result_pool,
555299742Sdim                                       get_user_agent_string(scratch_pool),
556299742Sdim                                       " ",
557299742Sdim                                       client_string, SVN_VA_NULL);
558251881Speter  else
559299742Sdim    serf_sess->useragent = get_user_agent_string(result_pool);
560251881Speter
561251881Speter  /* go ahead and tell serf about the connection. */
562251881Speter  status =
563251881Speter    serf_connection_create2(&serf_sess->conns[0]->conn,
564251881Speter                            serf_sess->context,
565251881Speter                            url,
566251881Speter                            svn_ra_serf__conn_setup, serf_sess->conns[0],
567251881Speter                            svn_ra_serf__conn_closed, serf_sess->conns[0],
568251881Speter                            serf_sess->pool);
569251881Speter  if (status)
570251881Speter    return svn_ra_serf__wrap_err(status, NULL);
571251881Speter
572251881Speter  /* Set the progress callback. */
573251881Speter  serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
574251881Speter                               serf_sess);
575251881Speter
576251881Speter  serf_sess->num_conns = 1;
577251881Speter
578251881Speter  session->priv = serf_sess;
579251881Speter
580299742Sdim  /* The following code explicitly works around a bug in serf <= r2319 / 1.3.8
581299742Sdim     where serf doesn't report the request as failed/cancelled when the
582299742Sdim     authorization request handler fails to handle the request.
583253734Speter
584299742Sdim     As long as we allocate the request in a subpool of the serf connection
585299742Sdim     pool, we know that the handler is always cleaned before the connection.
586299742Sdim
587299742Sdim     Luckily our caller now passes us two pools which handle this case.
588299742Sdim   */
589299742Sdim#if defined(SVN_DEBUG) && !SERF_VERSION_AT_LEAST(1,4,0)
590299742Sdim  /* Currently ensured by svn_ra_open4().
591299742Sdim     If failing causes segfault in basic_tests.py 48, "basic auth test" */
592299742Sdim  SVN_ERR_ASSERT((serf_sess->pool != scratch_pool)
593299742Sdim                 && apr_pool_is_ancestor(serf_sess->pool, scratch_pool));
594299742Sdim#endif
595299742Sdim
596299742Sdim  err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url,
597299742Sdim                                            result_pool, scratch_pool);
598299742Sdim
599253734Speter  /* serf should produce a usable error code instead of APR_EGENERAL */
600253734Speter  if (err && err->apr_err == APR_EGENERAL)
601253734Speter    err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
602253734Speter                            _("Connection to '%s' failed"), session_URL);
603253734Speter  SVN_ERR(err);
604253734Speter
605253734Speter  /* We have set up a useful connection (that doesn't indication a redirect).
606253734Speter     If we've been told there is possibly a worrisome proxy in our path to the
607253734Speter     server AND we switched to HTTP/1.1 (chunked requests), then probe for
608253734Speter     problems in any proxy.  */
609253734Speter  if ((corrected_url == NULL || *corrected_url == NULL)
610253734Speter      && serf_sess->detect_chunking && !serf_sess->http10)
611299742Sdim    SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, scratch_pool));
612253734Speter
613253734Speter  return SVN_NO_ERROR;
614251881Speter}
615251881Speter
616299742Sdim/* Implements svn_ra__vtable_t.dup_session */
617299742Sdimstatic svn_error_t *
618299742Sdimra_serf_dup_session(svn_ra_session_t *new_session,
619299742Sdim                    svn_ra_session_t *old_session,
620299742Sdim                    const char *new_session_url,
621299742Sdim                    apr_pool_t *result_pool,
622299742Sdim                    apr_pool_t *scratch_pool)
623299742Sdim{
624299742Sdim  svn_ra_serf__session_t *old_sess = old_session->priv;
625299742Sdim  svn_ra_serf__session_t *new_sess;
626299742Sdim  apr_status_t status;
627299742Sdim
628299742Sdim  new_sess = apr_pmemdup(result_pool, old_sess, sizeof(*new_sess));
629299742Sdim
630299742Sdim  new_sess->pool = result_pool;
631299742Sdim
632299742Sdim  if (new_sess->config)
633299742Sdim    SVN_ERR(svn_config_copy_config(&new_sess->config, new_sess->config,
634299742Sdim                                   result_pool));
635299742Sdim
636299742Sdim  /* max_connections */
637299742Sdim  /* using_ssl */
638299742Sdim  /* using_compression */
639299742Sdim  /* http10 */
640299742Sdim  /* using_chunked_requests */
641299742Sdim  /* detect_chunking */
642299742Sdim
643299742Sdim  if (new_sess->useragent)
644299742Sdim    new_sess->useragent = apr_pstrdup(result_pool, new_sess->useragent);
645299742Sdim
646299742Sdim  if (new_sess->vcc_url)
647299742Sdim    new_sess->vcc_url = apr_pstrdup(result_pool, new_sess->vcc_url);
648299742Sdim
649299742Sdim  new_sess->auth_state = NULL;
650299742Sdim  new_sess->auth_attempts = 0;
651299742Sdim
652299742Sdim  /* Callback functions to get info from WC */
653299742Sdim  /* wc_callbacks */
654299742Sdim  /* wc_callback_baton */
655299742Sdim
656299742Sdim  /* progress_func */
657299742Sdim  /* progress_baton */
658299742Sdim
659299742Sdim  /* cancel_func */
660299742Sdim  /* cancel_baton */
661299742Sdim
662299742Sdim  /* shim_callbacks */
663299742Sdim
664299742Sdim  new_sess->pending_error = NULL;
665299742Sdim
666299742Sdim  /* authn_types */
667299742Sdim
668299742Sdim  /* Keys and values are static */
669299742Sdim  if (new_sess->capabilities)
670299742Sdim    new_sess->capabilities = apr_hash_copy(result_pool, new_sess->capabilities);
671299742Sdim
672299742Sdim  if (new_sess->activity_collection_url)
673299742Sdim    {
674299742Sdim      new_sess->activity_collection_url
675299742Sdim                = apr_pstrdup(result_pool, new_sess->activity_collection_url);
676299742Sdim    }
677299742Sdim
678299742Sdim   /* using_proxy */
679299742Sdim
680299742Sdim  if (new_sess->proxy_username)
681299742Sdim    {
682299742Sdim      new_sess->proxy_username
683299742Sdim                = apr_pstrdup(result_pool, new_sess->proxy_username);
684299742Sdim    }
685299742Sdim
686299742Sdim  if (new_sess->proxy_password)
687299742Sdim    {
688299742Sdim      new_sess->proxy_username
689299742Sdim                = apr_pstrdup(result_pool, new_sess->proxy_password);
690299742Sdim    }
691299742Sdim
692299742Sdim  new_sess->proxy_auth_attempts = 0;
693299742Sdim
694299742Sdim  /* trust_default_ca */
695299742Sdim
696299742Sdim  if (new_sess->ssl_authorities)
697299742Sdim    {
698299742Sdim      new_sess->ssl_authorities = apr_pstrdup(result_pool,
699299742Sdim                                              new_sess->ssl_authorities);
700299742Sdim    }
701299742Sdim
702299742Sdim  if (new_sess->uuid)
703299742Sdim    new_sess->uuid = apr_pstrdup(result_pool, new_sess->uuid);
704299742Sdim
705299742Sdim  /* timeout */
706299742Sdim  /* supports_deadprop_count */
707299742Sdim
708299742Sdim  if (new_sess->me_resource)
709299742Sdim    new_sess->me_resource = apr_pstrdup(result_pool, new_sess->me_resource);
710299742Sdim  if (new_sess->rev_stub)
711299742Sdim    new_sess->rev_stub = apr_pstrdup(result_pool, new_sess->rev_stub);
712299742Sdim  if (new_sess->txn_stub)
713299742Sdim    new_sess->txn_stub = apr_pstrdup(result_pool, new_sess->txn_stub);
714299742Sdim  if (new_sess->txn_root_stub)
715299742Sdim    new_sess->txn_root_stub = apr_pstrdup(result_pool,
716299742Sdim                                          new_sess->txn_root_stub);
717299742Sdim  if (new_sess->vtxn_stub)
718299742Sdim    new_sess->vtxn_stub = apr_pstrdup(result_pool, new_sess->vtxn_stub);
719299742Sdim  if (new_sess->vtxn_root_stub)
720299742Sdim    new_sess->vtxn_root_stub = apr_pstrdup(result_pool,
721299742Sdim                                           new_sess->vtxn_root_stub);
722299742Sdim
723299742Sdim  /* Keys and values are static */
724299742Sdim  if (new_sess->supported_posts)
725299742Sdim    new_sess->supported_posts = apr_hash_copy(result_pool,
726299742Sdim                                              new_sess->supported_posts);
727299742Sdim
728299742Sdim  /* ### Can we copy this? */
729299742Sdim  SVN_ERR(svn_ra_serf__blncache_create(&new_sess->blncache,
730299742Sdim                                       new_sess->pool));
731299742Sdim
732299742Sdim  if (new_sess->server_allows_bulk)
733299742Sdim    new_sess->server_allows_bulk = apr_pstrdup(result_pool,
734299742Sdim                                               new_sess->server_allows_bulk);
735299742Sdim
736299742Sdim  new_sess->repos_root_str = apr_pstrdup(result_pool,
737299742Sdim                                         new_sess->repos_root_str);
738299742Sdim  SVN_ERR(svn_ra_serf__uri_parse(&new_sess->repos_root,
739299742Sdim                                 new_sess->repos_root_str,
740299742Sdim                                 result_pool));
741299742Sdim
742299742Sdim  new_sess->session_url_str = apr_pstrdup(result_pool, new_session_url);
743299742Sdim
744299742Sdim  SVN_ERR(svn_ra_serf__uri_parse(&new_sess->session_url,
745299742Sdim                                 new_sess->session_url_str,
746299742Sdim                                 result_pool));
747299742Sdim
748299742Sdim  /* svn_boolean_t supports_inline_props */
749299742Sdim  /* supports_rev_rsrc_replay */
750299742Sdim
751299742Sdim  new_sess->context = serf_context_create(result_pool);
752299742Sdim
753299742Sdim  SVN_ERR(load_config(new_sess, old_sess->config, result_pool));
754299742Sdim
755299742Sdim  new_sess->conns[0] = apr_pcalloc(result_pool,
756299742Sdim                                   sizeof(*new_sess->conns[0]));
757299742Sdim  new_sess->conns[0]->bkt_alloc =
758299742Sdim          serf_bucket_allocator_create(result_pool, NULL, NULL);
759299742Sdim  new_sess->conns[0]->session = new_sess;
760299742Sdim  new_sess->conns[0]->last_status_code = -1;
761299742Sdim
762299742Sdim  /* go ahead and tell serf about the connection. */
763299742Sdim  status =
764299742Sdim    serf_connection_create2(&new_sess->conns[0]->conn,
765299742Sdim                            new_sess->context,
766299742Sdim                            new_sess->session_url,
767299742Sdim                            svn_ra_serf__conn_setup, new_sess->conns[0],
768299742Sdim                            svn_ra_serf__conn_closed, new_sess->conns[0],
769299742Sdim                            result_pool);
770299742Sdim  if (status)
771299742Sdim    return svn_ra_serf__wrap_err(status, NULL);
772299742Sdim
773299742Sdim  /* Set the progress callback. */
774299742Sdim  serf_context_set_progress_cb(new_sess->context, svn_ra_serf__progress,
775299742Sdim                               new_sess);
776299742Sdim
777299742Sdim  new_sess->num_conns = 1;
778299742Sdim  new_sess->cur_conn = 0;
779299742Sdim
780299742Sdim  new_session->priv = new_sess;
781299742Sdim
782299742Sdim  return SVN_NO_ERROR;
783299742Sdim}
784299742Sdim
785251881Speter/* Implements svn_ra__vtable_t.reparent(). */
786299742Sdimsvn_error_t *
787251881Spetersvn_ra_serf__reparent(svn_ra_session_t *ra_session,
788251881Speter                      const char *url,
789251881Speter                      apr_pool_t *pool)
790251881Speter{
791251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
792251881Speter  apr_uri_t new_url;
793251881Speter
794251881Speter  /* If it's the URL we already have, wave our hands and do nothing. */
795251881Speter  if (strcmp(session->session_url_str, url) == 0)
796251881Speter    {
797251881Speter      return SVN_NO_ERROR;
798251881Speter    }
799251881Speter
800251881Speter  if (!session->repos_root_str)
801251881Speter    {
802251881Speter      const char *vcc_url;
803299742Sdim      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
804251881Speter    }
805251881Speter
806251881Speter  if (!svn_uri__is_ancestor(session->repos_root_str, url))
807251881Speter    {
808251881Speter      return svn_error_createf(
809251881Speter          SVN_ERR_RA_ILLEGAL_URL, NULL,
810251881Speter          _("URL '%s' is not a child of the session's repository root "
811251881Speter            "URL '%s'"), url, session->repos_root_str);
812251881Speter    }
813251881Speter
814299742Sdim  SVN_ERR(svn_ra_serf__uri_parse(&new_url, url, pool));
815251881Speter
816251881Speter  /* ### Maybe we should use a string buffer for these strings so we
817251881Speter     ### don't allocate memory in the session on every reparent? */
818299742Sdim  session->session_url.path = apr_pstrdup(session->pool, new_url.path);
819251881Speter  session->session_url_str = apr_pstrdup(session->pool, url);
820251881Speter
821251881Speter  return SVN_NO_ERROR;
822251881Speter}
823251881Speter
824251881Speter/* Implements svn_ra__vtable_t.get_session_url(). */
825251881Speterstatic svn_error_t *
826251881Spetersvn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
827251881Speter                             const char **url,
828251881Speter                             apr_pool_t *pool)
829251881Speter{
830251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
831251881Speter  *url = apr_pstrdup(pool, session->session_url_str);
832251881Speter  return SVN_NO_ERROR;
833251881Speter}
834251881Speter
835251881Speter/* Implements svn_ra__vtable_t.get_latest_revnum(). */
836251881Speterstatic svn_error_t *
837251881Spetersvn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
838251881Speter                               svn_revnum_t *latest_revnum,
839251881Speter                               apr_pool_t *pool)
840251881Speter{
841251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
842251881Speter
843251881Speter  return svn_error_trace(svn_ra_serf__get_youngest_revnum(
844251881Speter                           latest_revnum, session, pool));
845251881Speter}
846251881Speter
847299742Sdim/* Implementation of svn_ra_serf__rev_proplist(). */
848251881Speterstatic svn_error_t *
849299742Sdimserf__rev_proplist(svn_ra_session_t *ra_session,
850299742Sdim                   svn_revnum_t rev,
851299742Sdim                   const svn_ra_serf__dav_props_t *fetch_props,
852299742Sdim                   apr_hash_t **ret_props,
853299742Sdim                   apr_pool_t *result_pool,
854299742Sdim                   apr_pool_t *scratch_pool)
855251881Speter{
856251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
857251881Speter  apr_hash_t *props;
858251881Speter  const char *propfind_path;
859299742Sdim  svn_ra_serf__handler_t *handler;
860251881Speter
861251881Speter  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
862251881Speter    {
863299742Sdim      propfind_path = apr_psprintf(scratch_pool, "%s/%ld", session->rev_stub,
864299742Sdim                                   rev);
865251881Speter
866251881Speter      /* svn_ra_serf__retrieve_props() wants to added the revision as
867251881Speter         a Label to the PROPFIND, which isn't really necessary when
868251881Speter         querying a rev-stub URI.  *Shrug*  Probably okay to leave the
869251881Speter         Label, but whatever. */
870251881Speter      rev = SVN_INVALID_REVNUM;
871251881Speter    }
872251881Speter  else
873251881Speter    {
874251881Speter      /* Use the VCC as the propfind target path. */
875299742Sdim      SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session,
876299742Sdim                                        scratch_pool));
877251881Speter    }
878251881Speter
879299742Sdim  props = apr_hash_make(result_pool);
880299742Sdim  SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
881299742Sdim                                               propfind_path, rev, "0",
882299742Sdim                                               fetch_props,
883299742Sdim                                               svn_ra_serf__deliver_svn_props,
884299742Sdim                                               props,
885299742Sdim                                               scratch_pool));
886251881Speter
887299742Sdim  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
888251881Speter
889299742Sdim  svn_ra_serf__keep_only_regular_props(props, scratch_pool);
890251881Speter
891299742Sdim  *ret_props = props;
892251881Speter
893251881Speter  return SVN_NO_ERROR;
894251881Speter}
895251881Speter
896299742Sdim/* Implements svn_ra__vtable_t.rev_proplist(). */
897251881Speterstatic svn_error_t *
898299742Sdimsvn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
899299742Sdim                          svn_revnum_t rev,
900299742Sdim                          apr_hash_t **ret_props,
901299742Sdim                          apr_pool_t *result_pool)
902251881Speter{
903299742Sdim  apr_pool_t *scratch_pool = svn_pool_create(result_pool);
904299742Sdim  svn_error_t *err;
905251881Speter
906299742Sdim  err = serf__rev_proplist(ra_session, rev, all_props, ret_props,
907299742Sdim                           result_pool, scratch_pool);
908251881Speter
909299742Sdim  svn_pool_destroy(scratch_pool);
910299742Sdim  return svn_error_trace(err);
911251881Speter}
912251881Speter
913251881Speter
914299742Sdim/* Implements svn_ra__vtable_t.rev_prop(). */
915299742Sdimsvn_error_t *
916299742Sdimsvn_ra_serf__rev_prop(svn_ra_session_t *session,
917299742Sdim                      svn_revnum_t rev,
918299742Sdim                      const char *name,
919299742Sdim                      svn_string_t **value,
920299742Sdim                      apr_pool_t *result_pool)
921251881Speter{
922299742Sdim  apr_pool_t *scratch_pool = svn_pool_create(result_pool);
923251881Speter  apr_hash_t *props;
924299742Sdim  svn_ra_serf__dav_props_t specific_props[2];
925299742Sdim  const svn_ra_serf__dav_props_t *fetch_props = all_props;
926251881Speter
927299742Sdim  /* The DAV propfind doesn't allow property fetches for any property name
928299742Sdim     as there is no defined way to quote values. If we are just fetching a
929299742Sdim     "svn:property" we can safely do this. In other cases we just fetch all
930299742Sdim     revision properties and filter the right one out */
931299742Sdim  if (strncmp(name, SVN_PROP_PREFIX, sizeof(SVN_PROP_PREFIX)-1) == 0
932299742Sdim      && !strchr(name + sizeof(SVN_PROP_PREFIX)-1, ':'))
933251881Speter    {
934299742Sdim      specific_props[0].xmlns = SVN_DAV_PROP_NS_SVN;
935299742Sdim      specific_props[0].name = name + sizeof(SVN_PROP_PREFIX)-1;
936299742Sdim      specific_props[1].xmlns = NULL;
937299742Sdim      specific_props[1].name = NULL;
938251881Speter
939299742Sdim      fetch_props = specific_props;
940251881Speter    }
941251881Speter
942299742Sdim  SVN_ERR(serf__rev_proplist(session, rev, fetch_props, &props,
943299742Sdim                             result_pool, scratch_pool));
944251881Speter
945299742Sdim  *value = svn_hash_gets(props, name);
946251881Speter
947299742Sdim  svn_pool_destroy(scratch_pool);
948251881Speter
949251881Speter  return SVN_NO_ERROR;
950251881Speter}
951251881Speter
952251881Spetersvn_error_t *
953251881Spetersvn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
954251881Speter                            const char **url,
955251881Speter                            apr_pool_t *pool)
956251881Speter{
957251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
958251881Speter
959251881Speter  if (!session->repos_root_str)
960251881Speter    {
961251881Speter      const char *vcc_url;
962299742Sdim      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
963251881Speter    }
964251881Speter
965251881Speter  *url = session->repos_root_str;
966251881Speter  return SVN_NO_ERROR;
967251881Speter}
968251881Speter
969251881Speter/* TODO: to fetch the uuid from the repository, we need:
970251881Speter   1. a path that exists in HEAD
971251881Speter   2. a path that's readable
972251881Speter
973251881Speter   get_uuid handles the case where a path doesn't exist in HEAD and also the
974251881Speter   case where the root of the repository is not readable.
975251881Speter   However, it does not handle the case where we're fetching path not existing
976251881Speter   in HEAD of a repository with unreadable root directory.
977251881Speter
978251881Speter   Implements svn_ra__vtable_t.get_uuid().
979251881Speter */
980251881Speterstatic svn_error_t *
981251881Spetersvn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
982251881Speter                      const char **uuid,
983251881Speter                      apr_pool_t *pool)
984251881Speter{
985251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
986251881Speter
987251881Speter  if (!session->uuid)
988251881Speter    {
989251881Speter      const char *vcc_url;
990251881Speter
991251881Speter      /* We should never get here if we have HTTP v2 support, because
992251881Speter         any server with that support should be transmitting the
993251881Speter         UUID in the initial OPTIONS response.  */
994251881Speter      SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
995251881Speter
996251881Speter      /* We're not interested in vcc_url and relative_url, but this call also
997251881Speter         stores the repository's uuid in the session. */
998299742Sdim      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, pool));
999251881Speter      if (!session->uuid)
1000251881Speter        {
1001251881Speter          return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
1002251881Speter                                  _("The UUID property was not found on the "
1003251881Speter                                    "resource or any of its parents"));
1004251881Speter        }
1005251881Speter    }
1006251881Speter
1007251881Speter  *uuid = session->uuid;
1008251881Speter
1009251881Speter  return SVN_NO_ERROR;
1010251881Speter}
1011251881Speter
1012251881Speter
1013251881Speterstatic const svn_ra__vtable_t serf_vtable = {
1014251881Speter  ra_serf_version,
1015251881Speter  ra_serf_get_description,
1016251881Speter  ra_serf_get_schemes,
1017251881Speter  svn_ra_serf__open,
1018299742Sdim  ra_serf_dup_session,
1019251881Speter  svn_ra_serf__reparent,
1020251881Speter  svn_ra_serf__get_session_url,
1021251881Speter  svn_ra_serf__get_latest_revnum,
1022251881Speter  svn_ra_serf__get_dated_revision,
1023251881Speter  svn_ra_serf__change_rev_prop,
1024251881Speter  svn_ra_serf__rev_proplist,
1025251881Speter  svn_ra_serf__rev_prop,
1026251881Speter  svn_ra_serf__get_commit_editor,
1027251881Speter  svn_ra_serf__get_file,
1028251881Speter  svn_ra_serf__get_dir,
1029251881Speter  svn_ra_serf__get_mergeinfo,
1030251881Speter  svn_ra_serf__do_update,
1031251881Speter  svn_ra_serf__do_switch,
1032251881Speter  svn_ra_serf__do_status,
1033251881Speter  svn_ra_serf__do_diff,
1034251881Speter  svn_ra_serf__get_log,
1035251881Speter  svn_ra_serf__check_path,
1036251881Speter  svn_ra_serf__stat,
1037251881Speter  svn_ra_serf__get_uuid,
1038251881Speter  svn_ra_serf__get_repos_root,
1039251881Speter  svn_ra_serf__get_locations,
1040251881Speter  svn_ra_serf__get_location_segments,
1041251881Speter  svn_ra_serf__get_file_revs,
1042251881Speter  svn_ra_serf__lock,
1043251881Speter  svn_ra_serf__unlock,
1044251881Speter  svn_ra_serf__get_lock,
1045251881Speter  svn_ra_serf__get_locks,
1046251881Speter  svn_ra_serf__replay,
1047251881Speter  svn_ra_serf__has_capability,
1048251881Speter  svn_ra_serf__replay_range,
1049251881Speter  svn_ra_serf__get_deleted_rev,
1050251881Speter  svn_ra_serf__register_editor_shim_callbacks,
1051251881Speter  svn_ra_serf__get_inherited_props
1052251881Speter};
1053251881Speter
1054251881Spetersvn_error_t *
1055251881Spetersvn_ra_serf__init(const svn_version_t *loader_version,
1056251881Speter                  const svn_ra__vtable_t **vtable,
1057251881Speter                  apr_pool_t *pool)
1058251881Speter{
1059251881Speter  static const svn_version_checklist_t checklist[] =
1060251881Speter    {
1061251881Speter      { "svn_subr",  svn_subr_version },
1062251881Speter      { "svn_delta", svn_delta_version },
1063251881Speter      { NULL, NULL }
1064251881Speter    };
1065251881Speter  int serf_major;
1066251881Speter  int serf_minor;
1067251881Speter  int serf_patch;
1068251881Speter
1069262253Speter  SVN_ERR(svn_ver_check_list2(ra_serf_version(), checklist, svn_ver_equal));
1070251881Speter
1071251881Speter  /* Simplified version check to make sure we can safely use the
1072251881Speter     VTABLE parameter. The RA loader does a more exhaustive check. */
1073251881Speter  if (loader_version->major != SVN_VER_MAJOR)
1074251881Speter    {
1075251881Speter      return svn_error_createf(
1076251881Speter         SVN_ERR_VERSION_MISMATCH, NULL,
1077251881Speter         _("Unsupported RA loader version (%d) for ra_serf"),
1078251881Speter         loader_version->major);
1079251881Speter    }
1080251881Speter
1081251881Speter  /* Make sure that we have loaded a compatible library: the MAJOR must
1082251881Speter     match, and the minor must be at *least* what we compiled against.
1083251881Speter     The patch level is simply ignored.  */
1084251881Speter  serf_lib_version(&serf_major, &serf_minor, &serf_patch);
1085251881Speter  if (serf_major != SERF_MAJOR_VERSION
1086251881Speter      || serf_minor < SERF_MINOR_VERSION)
1087251881Speter    {
1088251881Speter      return svn_error_createf(
1089251881Speter         /* ### should return a unique error  */
1090251881Speter         SVN_ERR_VERSION_MISMATCH, NULL,
1091251881Speter         _("ra_serf was compiled for serf %d.%d.%d but loaded "
1092251881Speter           "an incompatible %d.%d.%d library"),
1093251881Speter         SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
1094251881Speter         serf_major, serf_minor, serf_patch);
1095251881Speter    }
1096251881Speter
1097251881Speter  *vtable = &serf_vtable;
1098251881Speter
1099251881Speter  return SVN_NO_ERROR;
1100251881Speter}
1101251881Speter
1102251881Speter/* Compatibility wrapper for pre-1.2 subversions.  Needed? */
1103251881Speter#define NAME "ra_serf"
1104251881Speter#define DESCRIPTION RA_SERF_DESCRIPTION
1105251881Speter#define VTBL serf_vtable
1106251881Speter#define INITFUNC svn_ra_serf__init
1107251881Speter#define COMPAT_INITFUNC svn_ra_serf_init
1108251881Speter#include "../libsvn_ra/wrapper_template.h"
1109