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"
42251881Speter#include "svn_time.h"
43251881Speter#include "svn_version.h"
44251881Speter
45251881Speter#include "private/svn_dav_protocol.h"
46251881Speter#include "private/svn_dep_compat.h"
47251881Speter#include "private/svn_fspath.h"
48251881Speter#include "private/svn_subr_private.h"
49251881Speter#include "svn_private_config.h"
50251881Speter
51251881Speter#include "ra_serf.h"
52251881Speter
53251881Speter
54251881Speter/* Implements svn_ra__vtable_t.get_version(). */
55251881Speterstatic const svn_version_t *
56251881Speterra_serf_version(void)
57251881Speter{
58251881Speter  SVN_VERSION_BODY;
59251881Speter}
60251881Speter
61251881Speter#define RA_SERF_DESCRIPTION \
62251881Speter    N_("Module for accessing a repository via WebDAV protocol using serf.")
63251881Speter
64262253Speter#define RA_SERF_DESCRIPTION_VER \
65262253Speter    N_("Module for accessing a repository via WebDAV protocol using serf.\n" \
66262253Speter       "  - using serf %d.%d.%d")
67262253Speter
68251881Speter/* Implements svn_ra__vtable_t.get_description(). */
69251881Speterstatic const char *
70262253Speterra_serf_get_description(apr_pool_t *pool)
71251881Speter{
72262253Speter  int major, minor, patch;
73262253Speter
74262253Speter  serf_lib_version(&major, &minor, &patch);
75262253Speter  return apr_psprintf(pool, _(RA_SERF_DESCRIPTION_VER), major, minor, patch);
76251881Speter}
77251881Speter
78251881Speter/* Implements svn_ra__vtable_t.get_schemes(). */
79251881Speterstatic const char * const *
80251881Speterra_serf_get_schemes(apr_pool_t *pool)
81251881Speter{
82251881Speter  static const char *serf_ssl[] = { "http", "https", NULL };
83251881Speter#if 0
84251881Speter  /* ### Temporary: to shut up a warning. */
85251881Speter  static const char *serf_no_ssl[] = { "http", NULL };
86251881Speter#endif
87251881Speter
88251881Speter  /* TODO: Runtime detection. */
89251881Speter  return serf_ssl;
90251881Speter}
91251881Speter
92251881Speter/* Load the setting http-auth-types from the global or server specific
93251881Speter   section, parse its value and set the types of authentication we should
94251881Speter   accept from the server. */
95251881Speterstatic svn_error_t *
96251881Speterload_http_auth_types(apr_pool_t *pool, svn_config_t *config,
97251881Speter                     const char *server_group,
98251881Speter                     int *authn_types)
99251881Speter{
100251881Speter  const char *http_auth_types = NULL;
101251881Speter  *authn_types = SERF_AUTHN_NONE;
102251881Speter
103251881Speter  svn_config_get(config, &http_auth_types, SVN_CONFIG_SECTION_GLOBAL,
104251881Speter               SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, NULL);
105251881Speter
106251881Speter  if (server_group)
107251881Speter    {
108251881Speter      svn_config_get(config, &http_auth_types, server_group,
109251881Speter                     SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, http_auth_types);
110251881Speter    }
111251881Speter
112251881Speter  if (http_auth_types)
113251881Speter    {
114251881Speter      char *token;
115251881Speter      char *auth_types_list = apr_palloc(pool, strlen(http_auth_types) + 1);
116251881Speter      apr_collapse_spaces(auth_types_list, http_auth_types);
117251881Speter      while ((token = svn_cstring_tokenize(";", &auth_types_list)) != NULL)
118251881Speter        {
119251881Speter          if (svn_cstring_casecmp("basic", token) == 0)
120251881Speter            *authn_types |= SERF_AUTHN_BASIC;
121251881Speter          else if (svn_cstring_casecmp("digest", token) == 0)
122251881Speter            *authn_types |= SERF_AUTHN_DIGEST;
123251881Speter          else if (svn_cstring_casecmp("ntlm", token) == 0)
124251881Speter            *authn_types |= SERF_AUTHN_NTLM;
125251881Speter          else if (svn_cstring_casecmp("negotiate", token) == 0)
126251881Speter            *authn_types |= SERF_AUTHN_NEGOTIATE;
127251881Speter          else
128251881Speter            return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
129251881Speter                                     _("Invalid config: unknown %s "
130251881Speter                                       "'%s'"),
131251881Speter                                     SVN_CONFIG_OPTION_HTTP_AUTH_TYPES, token);
132251881Speter      }
133251881Speter    }
134251881Speter  else
135251881Speter    {
136251881Speter      /* Nothing specified by the user, so accept all types. */
137251881Speter      *authn_types = SERF_AUTHN_ALL;
138251881Speter    }
139251881Speter
140251881Speter  return SVN_NO_ERROR;
141251881Speter}
142251881Speter
143251881Speter/* Default HTTP timeout (in seconds); overridden by the 'http-timeout'
144251881Speter   runtime configuration variable. */
145251881Speter#define DEFAULT_HTTP_TIMEOUT 600
146251881Speter
147253734Speter/* Private symbol for the 1.9-public SVN_CONFIG_OPTION_HTTP_CHUNKED_REQUESTS */
148253734Speter#define OPTION_HTTP_CHUNKED_REQUESTS "http-chunked-requests"
149253734Speter
150253734Speter
151251881Speterstatic svn_error_t *
152251881Speterload_config(svn_ra_serf__session_t *session,
153251881Speter            apr_hash_t *config_hash,
154251881Speter            apr_pool_t *pool)
155251881Speter{
156251881Speter  svn_config_t *config, *config_client;
157251881Speter  const char *server_group;
158251881Speter  const char *proxy_host = NULL;
159251881Speter  const char *port_str = NULL;
160251881Speter  const char *timeout_str = NULL;
161251881Speter  const char *exceptions;
162251881Speter  apr_port_t proxy_port;
163253734Speter  svn_tristate_t chunked_requests;
164251881Speter
165251881Speter  if (config_hash)
166251881Speter    {
167251881Speter      config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_SERVERS);
168251881Speter      config_client = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
169251881Speter    }
170251881Speter  else
171251881Speter    {
172251881Speter      config = NULL;
173251881Speter      config_client = NULL;
174251881Speter    }
175251881Speter
176251881Speter  SVN_ERR(svn_config_get_bool(config, &session->using_compression,
177251881Speter                              SVN_CONFIG_SECTION_GLOBAL,
178251881Speter                              SVN_CONFIG_OPTION_HTTP_COMPRESSION, TRUE));
179251881Speter  svn_config_get(config, &timeout_str, SVN_CONFIG_SECTION_GLOBAL,
180251881Speter                 SVN_CONFIG_OPTION_HTTP_TIMEOUT, NULL);
181251881Speter
182251881Speter  if (session->wc_callbacks->auth_baton)
183251881Speter    {
184251881Speter      if (config_client)
185251881Speter        {
186251881Speter          svn_auth_set_parameter(session->wc_callbacks->auth_baton,
187251881Speter                                 SVN_AUTH_PARAM_CONFIG_CATEGORY_CONFIG,
188251881Speter                                 config_client);
189251881Speter        }
190251881Speter      if (config)
191251881Speter        {
192251881Speter          svn_auth_set_parameter(session->wc_callbacks->auth_baton,
193251881Speter                                 SVN_AUTH_PARAM_CONFIG_CATEGORY_SERVERS,
194251881Speter                                 config);
195251881Speter        }
196251881Speter    }
197251881Speter
198251881Speter  /* Use the default proxy-specific settings if and only if
199251881Speter     "http-proxy-exceptions" is not set to exclude this host. */
200251881Speter  svn_config_get(config, &exceptions, SVN_CONFIG_SECTION_GLOBAL,
201251881Speter                 SVN_CONFIG_OPTION_HTTP_PROXY_EXCEPTIONS, "");
202251881Speter  if (! svn_cstring_match_glob_list(session->session_url.hostname,
203251881Speter                                    svn_cstring_split(exceptions, ",",
204251881Speter                                                      TRUE, pool)))
205251881Speter    {
206251881Speter      svn_config_get(config, &proxy_host, SVN_CONFIG_SECTION_GLOBAL,
207251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_HOST, NULL);
208251881Speter      svn_config_get(config, &port_str, SVN_CONFIG_SECTION_GLOBAL,
209251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_PORT, NULL);
210251881Speter      svn_config_get(config, &session->proxy_username,
211251881Speter                     SVN_CONFIG_SECTION_GLOBAL,
212251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME, NULL);
213251881Speter      svn_config_get(config, &session->proxy_password,
214251881Speter                     SVN_CONFIG_SECTION_GLOBAL,
215251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD, NULL);
216251881Speter    }
217251881Speter
218251881Speter  /* Load the global ssl settings, if set. */
219251881Speter  SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
220251881Speter                              SVN_CONFIG_SECTION_GLOBAL,
221251881Speter                              SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
222251881Speter                              TRUE));
223251881Speter  svn_config_get(config, &session->ssl_authorities, SVN_CONFIG_SECTION_GLOBAL,
224251881Speter                 SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES, NULL);
225251881Speter
226251881Speter  /* If set, read the flag that tells us to do bulk updates or not. Defaults
227251881Speter     to skelta updates. */
228251881Speter  SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
229251881Speter                                  SVN_CONFIG_SECTION_GLOBAL,
230251881Speter                                  SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
231251881Speter                                  "auto",
232251881Speter                                  svn_tristate_unknown));
233251881Speter
234251881Speter  /* Load the maximum number of parallel session connections. */
235251881Speter  SVN_ERR(svn_config_get_int64(config, &session->max_connections,
236251881Speter                               SVN_CONFIG_SECTION_GLOBAL,
237251881Speter                               SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
238251881Speter                               SVN_CONFIG_DEFAULT_OPTION_HTTP_MAX_CONNECTIONS));
239251881Speter
240253734Speter  /* Should we use chunked transfer encoding. */
241253734Speter  SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
242253734Speter                                  SVN_CONFIG_SECTION_GLOBAL,
243253734Speter                                  OPTION_HTTP_CHUNKED_REQUESTS,
244253734Speter                                  "auto", svn_tristate_unknown));
245253734Speter
246251881Speter  if (config)
247251881Speter    server_group = svn_config_find_group(config,
248251881Speter                                         session->session_url.hostname,
249251881Speter                                         SVN_CONFIG_SECTION_GROUPS, pool);
250251881Speter  else
251251881Speter    server_group = NULL;
252251881Speter
253251881Speter  if (server_group)
254251881Speter    {
255251881Speter      SVN_ERR(svn_config_get_bool(config, &session->using_compression,
256251881Speter                                  server_group,
257251881Speter                                  SVN_CONFIG_OPTION_HTTP_COMPRESSION,
258251881Speter                                  session->using_compression));
259251881Speter      svn_config_get(config, &timeout_str, server_group,
260251881Speter                     SVN_CONFIG_OPTION_HTTP_TIMEOUT, timeout_str);
261251881Speter
262251881Speter      svn_auth_set_parameter(session->wc_callbacks->auth_baton,
263251881Speter                             SVN_AUTH_PARAM_SERVER_GROUP, server_group);
264251881Speter
265251881Speter      /* Load the group proxy server settings, overriding global
266251881Speter         settings.  We intentionally ignore 'http-proxy-exceptions'
267251881Speter         here because, well, if this site was an exception, why is
268251881Speter         there a per-server proxy configuration for it?  */
269251881Speter      svn_config_get(config, &proxy_host, server_group,
270251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_HOST, proxy_host);
271251881Speter      svn_config_get(config, &port_str, server_group,
272251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_PORT, port_str);
273251881Speter      svn_config_get(config, &session->proxy_username, server_group,
274251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_USERNAME,
275251881Speter                     session->proxy_username);
276251881Speter      svn_config_get(config, &session->proxy_password, server_group,
277251881Speter                     SVN_CONFIG_OPTION_HTTP_PROXY_PASSWORD,
278251881Speter                     session->proxy_password);
279251881Speter
280251881Speter      /* Load the group ssl settings. */
281251881Speter      SVN_ERR(svn_config_get_bool(config, &session->trust_default_ca,
282251881Speter                                  server_group,
283251881Speter                                  SVN_CONFIG_OPTION_SSL_TRUST_DEFAULT_CA,
284251881Speter                                  session->trust_default_ca));
285251881Speter      svn_config_get(config, &session->ssl_authorities, server_group,
286251881Speter                     SVN_CONFIG_OPTION_SSL_AUTHORITY_FILES,
287251881Speter                     session->ssl_authorities);
288251881Speter
289251881Speter      /* Load the group bulk updates flag. */
290251881Speter      SVN_ERR(svn_config_get_tristate(config, &session->bulk_updates,
291251881Speter                                      server_group,
292251881Speter                                      SVN_CONFIG_OPTION_HTTP_BULK_UPDATES,
293251881Speter                                      "auto",
294251881Speter                                      session->bulk_updates));
295251881Speter
296251881Speter      /* Load the maximum number of parallel session connections,
297251881Speter         overriding global values. */
298251881Speter      SVN_ERR(svn_config_get_int64(config, &session->max_connections,
299251881Speter                                   server_group,
300251881Speter                                   SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS,
301251881Speter                                   session->max_connections));
302253734Speter
303253734Speter      /* Should we use chunked transfer encoding. */
304253734Speter      SVN_ERR(svn_config_get_tristate(config, &chunked_requests,
305253734Speter                                      server_group,
306253734Speter                                      OPTION_HTTP_CHUNKED_REQUESTS,
307253734Speter                                      "auto", chunked_requests));
308251881Speter    }
309251881Speter
310251881Speter  /* Don't allow the http-max-connections value to be larger than our
311251881Speter     compiled-in limit, or to be too small to operate.  Broken
312251881Speter     functionality and angry administrators are equally undesirable. */
313251881Speter  if (session->max_connections > SVN_RA_SERF__MAX_CONNECTIONS_LIMIT)
314251881Speter    session->max_connections = SVN_RA_SERF__MAX_CONNECTIONS_LIMIT;
315251881Speter  if (session->max_connections < 2)
316251881Speter    session->max_connections = 2;
317251881Speter
318251881Speter  /* Parse the connection timeout value, if any. */
319251881Speter  session->timeout = apr_time_from_sec(DEFAULT_HTTP_TIMEOUT);
320251881Speter  if (timeout_str)
321251881Speter    {
322251881Speter      char *endstr;
323251881Speter      const long int timeout = strtol(timeout_str, &endstr, 10);
324251881Speter
325251881Speter      if (*endstr)
326251881Speter        return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
327251881Speter                                _("Invalid config: illegal character in "
328251881Speter                                  "timeout value"));
329251881Speter      if (timeout < 0)
330251881Speter        return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
331251881Speter                                _("Invalid config: negative timeout value"));
332251881Speter      session->timeout = apr_time_from_sec(timeout);
333251881Speter    }
334251881Speter  SVN_ERR_ASSERT(session->timeout >= 0);
335251881Speter
336251881Speter  /* Convert the proxy port value, if any. */
337251881Speter  if (port_str)
338251881Speter    {
339251881Speter      char *endstr;
340251881Speter      const long int port = strtol(port_str, &endstr, 10);
341251881Speter
342251881Speter      if (*endstr)
343251881Speter        return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
344251881Speter                                _("Invalid URL: illegal character in proxy "
345251881Speter                                  "port number"));
346251881Speter      if (port < 0)
347251881Speter        return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
348251881Speter                                _("Invalid URL: negative proxy port number"));
349251881Speter      if (port > 65535)
350251881Speter        return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, NULL,
351251881Speter                                _("Invalid URL: proxy port number greater "
352251881Speter                                  "than maximum TCP port number 65535"));
353251881Speter      proxy_port = (apr_port_t) port;
354251881Speter    }
355251881Speter  else
356251881Speter    {
357251881Speter      proxy_port = 80;
358251881Speter    }
359251881Speter
360251881Speter  if (proxy_host)
361251881Speter    {
362251881Speter      apr_sockaddr_t *proxy_addr;
363251881Speter      apr_status_t status;
364251881Speter
365251881Speter      status = apr_sockaddr_info_get(&proxy_addr, proxy_host,
366251881Speter                                     APR_UNSPEC, proxy_port, 0,
367251881Speter                                     session->pool);
368251881Speter      if (status)
369251881Speter        {
370251881Speter          return svn_ra_serf__wrap_err(
371251881Speter                   status, _("Could not resolve proxy server '%s'"),
372251881Speter                   proxy_host);
373251881Speter        }
374251881Speter      session->using_proxy = TRUE;
375251881Speter      serf_config_proxy(session->context, proxy_addr);
376251881Speter    }
377251881Speter  else
378251881Speter    {
379251881Speter      session->using_proxy = FALSE;
380251881Speter    }
381251881Speter
382253734Speter  /* Setup detect_chunking and using_chunked_requests based on
383253734Speter   * the chunked_requests tristate */
384253734Speter  if (chunked_requests == svn_tristate_unknown)
385253734Speter    {
386253734Speter      session->detect_chunking = TRUE;
387253734Speter      session->using_chunked_requests = TRUE;
388253734Speter    }
389253734Speter  else if (chunked_requests == svn_tristate_true)
390253734Speter    {
391253734Speter      session->detect_chunking = FALSE;
392253734Speter      session->using_chunked_requests = TRUE;
393253734Speter    }
394253734Speter  else /* chunked_requests == svn_tristate_false */
395253734Speter    {
396253734Speter      session->detect_chunking = FALSE;
397253734Speter      session->using_chunked_requests = FALSE;
398253734Speter    }
399253734Speter
400251881Speter  /* Setup authentication. */
401251881Speter  SVN_ERR(load_http_auth_types(pool, config, server_group,
402251881Speter                               &session->authn_types));
403251881Speter  serf_config_authn_types(session->context, session->authn_types);
404251881Speter  serf_config_credentials_callback(session->context,
405251881Speter                                   svn_ra_serf__credentials_callback);
406251881Speter
407251881Speter  return SVN_NO_ERROR;
408251881Speter}
409251881Speter#undef DEFAULT_HTTP_TIMEOUT
410251881Speter
411251881Speterstatic void
412251881Spetersvn_ra_serf__progress(void *progress_baton, apr_off_t read, apr_off_t written)
413251881Speter{
414251881Speter  const svn_ra_serf__session_t *serf_sess = progress_baton;
415251881Speter  if (serf_sess->progress_func)
416251881Speter    {
417251881Speter      serf_sess->progress_func(read + written, -1,
418251881Speter                               serf_sess->progress_baton,
419251881Speter                               serf_sess->pool);
420251881Speter    }
421251881Speter}
422251881Speter
423262253Speter/** Our User-Agent string. */
424262253Speterstatic const char *
425262253Speterget_user_agent_string(apr_pool_t *pool)
426262253Speter{
427262253Speter  int major, minor, patch;
428262253Speter  serf_lib_version(&major, &minor, &patch);
429262253Speter
430262253Speter  return apr_psprintf(pool, "SVN/%s (%s) serf/%d.%d.%d",
431262253Speter                      SVN_VER_NUMBER, SVN_BUILD_TARGET,
432262253Speter                      major, minor, patch);
433262253Speter}
434262253Speter
435251881Speter/* Implements svn_ra__vtable_t.open_session(). */
436251881Speterstatic svn_error_t *
437251881Spetersvn_ra_serf__open(svn_ra_session_t *session,
438251881Speter                  const char **corrected_url,
439251881Speter                  const char *session_URL,
440251881Speter                  const svn_ra_callbacks2_t *callbacks,
441251881Speter                  void *callback_baton,
442251881Speter                  apr_hash_t *config,
443251881Speter                  apr_pool_t *pool)
444251881Speter{
445251881Speter  apr_status_t status;
446251881Speter  svn_ra_serf__session_t *serf_sess;
447251881Speter  apr_uri_t url;
448251881Speter  const char *client_string = NULL;
449253734Speter  svn_error_t *err;
450251881Speter
451251881Speter  if (corrected_url)
452251881Speter    *corrected_url = NULL;
453251881Speter
454251881Speter  serf_sess = apr_pcalloc(pool, sizeof(*serf_sess));
455251881Speter  serf_sess->pool = svn_pool_create(pool);
456251881Speter  serf_sess->wc_callbacks = callbacks;
457251881Speter  serf_sess->wc_callback_baton = callback_baton;
458251881Speter  serf_sess->progress_func = callbacks->progress_func;
459251881Speter  serf_sess->progress_baton = callbacks->progress_baton;
460251881Speter  serf_sess->cancel_func = callbacks->cancel_func;
461251881Speter  serf_sess->cancel_baton = callback_baton;
462251881Speter
463251881Speter  /* todo: reuse serf context across sessions */
464251881Speter  serf_sess->context = serf_context_create(serf_sess->pool);
465251881Speter
466251881Speter  SVN_ERR(svn_ra_serf__blncache_create(&serf_sess->blncache,
467251881Speter                                       serf_sess->pool));
468251881Speter
469251881Speter
470251881Speter  status = apr_uri_parse(serf_sess->pool, session_URL, &url);
471251881Speter  if (status)
472251881Speter    {
473251881Speter      return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
474251881Speter                               _("Illegal URL '%s'"),
475251881Speter                               session_URL);
476251881Speter    }
477251881Speter  /* Depending the version of apr-util in use, for root paths url.path
478251881Speter     will be NULL or "", where serf requires "/". */
479251881Speter  if (url.path == NULL || url.path[0] == '\0')
480251881Speter    {
481251881Speter      url.path = apr_pstrdup(serf_sess->pool, "/");
482251881Speter    }
483251881Speter  if (!url.port)
484251881Speter    {
485251881Speter      url.port = apr_uri_port_of_scheme(url.scheme);
486251881Speter    }
487251881Speter  serf_sess->session_url = url;
488251881Speter  serf_sess->session_url_str = apr_pstrdup(serf_sess->pool, session_URL);
489251881Speter  serf_sess->using_ssl = (svn_cstring_casecmp(url.scheme, "https") == 0);
490251881Speter
491251881Speter  serf_sess->supports_deadprop_count = svn_tristate_unknown;
492251881Speter
493251881Speter  serf_sess->capabilities = apr_hash_make(serf_sess->pool);
494251881Speter
495251881Speter  /* We have to assume that the server only supports HTTP/1.0. Once it's clear
496251881Speter     HTTP/1.1 is supported, we can upgrade. */
497251881Speter  serf_sess->http10 = TRUE;
498251881Speter
499253734Speter  /* If we switch to HTTP/1.1, then we will use chunked requests. We may disable
500253734Speter     this, if we find an intervening proxy does not support chunked requests.  */
501253734Speter  serf_sess->using_chunked_requests = TRUE;
502253734Speter
503251881Speter  SVN_ERR(load_config(serf_sess, config, serf_sess->pool));
504251881Speter
505251881Speter  serf_sess->conns[0] = apr_pcalloc(serf_sess->pool,
506251881Speter                                    sizeof(*serf_sess->conns[0]));
507251881Speter  serf_sess->conns[0]->bkt_alloc =
508251881Speter          serf_bucket_allocator_create(serf_sess->pool, NULL, NULL);
509251881Speter  serf_sess->conns[0]->session = serf_sess;
510251881Speter  serf_sess->conns[0]->last_status_code = -1;
511251881Speter
512251881Speter  /* create the user agent string */
513251881Speter  if (callbacks->get_client_string)
514251881Speter    SVN_ERR(callbacks->get_client_string(callback_baton, &client_string, pool));
515251881Speter
516251881Speter  if (client_string)
517262253Speter    serf_sess->useragent = apr_pstrcat(pool, get_user_agent_string(pool), " ",
518251881Speter                                       client_string, (char *)NULL);
519251881Speter  else
520262253Speter    serf_sess->useragent = get_user_agent_string(pool);
521251881Speter
522251881Speter  /* go ahead and tell serf about the connection. */
523251881Speter  status =
524251881Speter    serf_connection_create2(&serf_sess->conns[0]->conn,
525251881Speter                            serf_sess->context,
526251881Speter                            url,
527251881Speter                            svn_ra_serf__conn_setup, serf_sess->conns[0],
528251881Speter                            svn_ra_serf__conn_closed, serf_sess->conns[0],
529251881Speter                            serf_sess->pool);
530251881Speter  if (status)
531251881Speter    return svn_ra_serf__wrap_err(status, NULL);
532251881Speter
533251881Speter  /* Set the progress callback. */
534251881Speter  serf_context_set_progress_cb(serf_sess->context, svn_ra_serf__progress,
535251881Speter                               serf_sess);
536251881Speter
537251881Speter  serf_sess->num_conns = 1;
538251881Speter
539251881Speter  session->priv = serf_sess;
540251881Speter
541253734Speter  err = svn_ra_serf__exchange_capabilities(serf_sess, corrected_url, pool);
542253734Speter
543253734Speter  /* serf should produce a usable error code instead of APR_EGENERAL */
544253734Speter  if (err && err->apr_err == APR_EGENERAL)
545253734Speter    err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, err,
546253734Speter                            _("Connection to '%s' failed"), session_URL);
547253734Speter  SVN_ERR(err);
548253734Speter
549253734Speter  /* We have set up a useful connection (that doesn't indication a redirect).
550253734Speter     If we've been told there is possibly a worrisome proxy in our path to the
551253734Speter     server AND we switched to HTTP/1.1 (chunked requests), then probe for
552253734Speter     problems in any proxy.  */
553253734Speter  if ((corrected_url == NULL || *corrected_url == NULL)
554253734Speter      && serf_sess->detect_chunking && !serf_sess->http10)
555253734Speter    SVN_ERR(svn_ra_serf__probe_proxy(serf_sess, pool));
556253734Speter
557253734Speter  return SVN_NO_ERROR;
558251881Speter}
559251881Speter
560251881Speter/* Implements svn_ra__vtable_t.reparent(). */
561251881Speterstatic svn_error_t *
562251881Spetersvn_ra_serf__reparent(svn_ra_session_t *ra_session,
563251881Speter                      const char *url,
564251881Speter                      apr_pool_t *pool)
565251881Speter{
566251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
567251881Speter  apr_uri_t new_url;
568251881Speter  apr_status_t status;
569251881Speter
570251881Speter  /* If it's the URL we already have, wave our hands and do nothing. */
571251881Speter  if (strcmp(session->session_url_str, url) == 0)
572251881Speter    {
573251881Speter      return SVN_NO_ERROR;
574251881Speter    }
575251881Speter
576251881Speter  if (!session->repos_root_str)
577251881Speter    {
578251881Speter      const char *vcc_url;
579251881Speter      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
580251881Speter    }
581251881Speter
582251881Speter  if (!svn_uri__is_ancestor(session->repos_root_str, url))
583251881Speter    {
584251881Speter      return svn_error_createf(
585251881Speter          SVN_ERR_RA_ILLEGAL_URL, NULL,
586251881Speter          _("URL '%s' is not a child of the session's repository root "
587251881Speter            "URL '%s'"), url, session->repos_root_str);
588251881Speter    }
589251881Speter
590251881Speter  status = apr_uri_parse(pool, url, &new_url);
591251881Speter  if (status)
592251881Speter    {
593251881Speter      return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
594251881Speter                               _("Illegal repository URL '%s'"), url);
595251881Speter    }
596251881Speter
597251881Speter  /* Depending the version of apr-util in use, for root paths url.path
598251881Speter     will be NULL or "", where serf requires "/". */
599251881Speter  /* ### Maybe we should use a string buffer for these strings so we
600251881Speter     ### don't allocate memory in the session on every reparent? */
601251881Speter  if (new_url.path == NULL || new_url.path[0] == '\0')
602251881Speter    {
603251881Speter      session->session_url.path = apr_pstrdup(session->pool, "/");
604251881Speter    }
605251881Speter  else
606251881Speter    {
607251881Speter      session->session_url.path = apr_pstrdup(session->pool, new_url.path);
608251881Speter    }
609251881Speter  session->session_url_str = apr_pstrdup(session->pool, url);
610251881Speter
611251881Speter  return SVN_NO_ERROR;
612251881Speter}
613251881Speter
614251881Speter/* Implements svn_ra__vtable_t.get_session_url(). */
615251881Speterstatic svn_error_t *
616251881Spetersvn_ra_serf__get_session_url(svn_ra_session_t *ra_session,
617251881Speter                             const char **url,
618251881Speter                             apr_pool_t *pool)
619251881Speter{
620251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
621251881Speter  *url = apr_pstrdup(pool, session->session_url_str);
622251881Speter  return SVN_NO_ERROR;
623251881Speter}
624251881Speter
625251881Speter/* Implements svn_ra__vtable_t.get_latest_revnum(). */
626251881Speterstatic svn_error_t *
627251881Spetersvn_ra_serf__get_latest_revnum(svn_ra_session_t *ra_session,
628251881Speter                               svn_revnum_t *latest_revnum,
629251881Speter                               apr_pool_t *pool)
630251881Speter{
631251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
632251881Speter
633251881Speter  return svn_error_trace(svn_ra_serf__get_youngest_revnum(
634251881Speter                           latest_revnum, session, pool));
635251881Speter}
636251881Speter
637251881Speter/* Implements svn_ra__vtable_t.rev_proplist(). */
638251881Speterstatic svn_error_t *
639251881Spetersvn_ra_serf__rev_proplist(svn_ra_session_t *ra_session,
640251881Speter                          svn_revnum_t rev,
641251881Speter                          apr_hash_t **ret_props,
642251881Speter                          apr_pool_t *pool)
643251881Speter{
644251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
645251881Speter  apr_hash_t *props;
646251881Speter  const char *propfind_path;
647251881Speter
648251881Speter  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
649251881Speter    {
650251881Speter      propfind_path = apr_psprintf(pool, "%s/%ld", session->rev_stub, rev);
651251881Speter
652251881Speter      /* svn_ra_serf__retrieve_props() wants to added the revision as
653251881Speter         a Label to the PROPFIND, which isn't really necessary when
654251881Speter         querying a rev-stub URI.  *Shrug*  Probably okay to leave the
655251881Speter         Label, but whatever. */
656251881Speter      rev = SVN_INVALID_REVNUM;
657251881Speter    }
658251881Speter  else
659251881Speter    {
660251881Speter      /* Use the VCC as the propfind target path. */
661251881Speter      SVN_ERR(svn_ra_serf__discover_vcc(&propfind_path, session, NULL, pool));
662251881Speter    }
663251881Speter
664251881Speter  /* ### fix: fetch hash of *just* the PATH@REV props. no nested hash.  */
665251881Speter  SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
666251881Speter                                      propfind_path, rev, "0", all_props,
667251881Speter                                      pool, pool));
668251881Speter
669251881Speter  SVN_ERR(svn_ra_serf__select_revprops(ret_props, propfind_path, rev, props,
670251881Speter                                       pool, pool));
671251881Speter
672251881Speter  return SVN_NO_ERROR;
673251881Speter}
674251881Speter
675251881Speter/* Implements svn_ra__vtable_t.rev_prop(). */
676251881Speterstatic svn_error_t *
677251881Spetersvn_ra_serf__rev_prop(svn_ra_session_t *session,
678251881Speter                      svn_revnum_t rev,
679251881Speter                      const char *name,
680251881Speter                      svn_string_t **value,
681251881Speter                      apr_pool_t *pool)
682251881Speter{
683251881Speter  apr_hash_t *props;
684251881Speter
685251881Speter  SVN_ERR(svn_ra_serf__rev_proplist(session, rev, &props, pool));
686251881Speter
687251881Speter  *value = svn_hash_gets(props, name);
688251881Speter
689251881Speter  return SVN_NO_ERROR;
690251881Speter}
691251881Speter
692251881Speterstatic svn_error_t *
693251881Speterfetch_path_props(apr_hash_t **props,
694251881Speter                 svn_ra_serf__session_t *session,
695251881Speter                 const char *session_relpath,
696251881Speter                 svn_revnum_t revision,
697251881Speter                 const svn_ra_serf__dav_props_t *desired_props,
698251881Speter                 apr_pool_t *result_pool,
699251881Speter                 apr_pool_t *scratch_pool)
700251881Speter{
701251881Speter  const char *url;
702251881Speter
703251881Speter  url = session->session_url.path;
704251881Speter
705251881Speter  /* If we have a relative path, append it. */
706251881Speter  if (session_relpath)
707251881Speter    url = svn_path_url_add_component2(url, session_relpath, scratch_pool);
708251881Speter
709251881Speter  /* If we were given a specific revision, get a URL that refers to that
710251881Speter     specific revision (rather than floating with HEAD).  */
711251881Speter  if (SVN_IS_VALID_REVNUM(revision))
712251881Speter    {
713251881Speter      SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
714251881Speter                                          session, NULL /* conn */,
715251881Speter                                          url, revision,
716251881Speter                                          scratch_pool, scratch_pool));
717251881Speter    }
718251881Speter
719251881Speter  /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
720251881Speter     Or we started with SVN_INVALID_REVNUM and URL may be floating.  */
721251881Speter  SVN_ERR(svn_ra_serf__fetch_node_props(props, session->conns[0],
722251881Speter                                        url, SVN_INVALID_REVNUM,
723251881Speter                                        desired_props,
724251881Speter                                        result_pool, scratch_pool));
725251881Speter
726251881Speter  return SVN_NO_ERROR;
727251881Speter}
728251881Speter
729251881Speter/* Implements svn_ra__vtable_t.check_path(). */
730251881Speterstatic svn_error_t *
731251881Spetersvn_ra_serf__check_path(svn_ra_session_t *ra_session,
732251881Speter                        const char *rel_path,
733251881Speter                        svn_revnum_t revision,
734251881Speter                        svn_node_kind_t *kind,
735251881Speter                        apr_pool_t *pool)
736251881Speter{
737251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
738251881Speter  apr_hash_t *props;
739251881Speter
740251881Speter  svn_error_t *err = fetch_path_props(&props, session, rel_path,
741251881Speter                                      revision, check_path_props,
742251881Speter                                      pool, pool);
743251881Speter
744251881Speter  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
745251881Speter    {
746251881Speter      svn_error_clear(err);
747251881Speter      *kind = svn_node_none;
748251881Speter    }
749251881Speter  else
750251881Speter    {
751251881Speter      /* Any other error, raise to caller. */
752251881Speter      if (err)
753251881Speter        return svn_error_trace(err);
754251881Speter
755251881Speter      SVN_ERR(svn_ra_serf__get_resource_type(kind, props));
756251881Speter    }
757251881Speter
758251881Speter  return SVN_NO_ERROR;
759251881Speter}
760251881Speter
761251881Speter
762251881Speterstruct dirent_walker_baton_t {
763251881Speter  /* Update the fields in this entry.  */
764251881Speter  svn_dirent_t *entry;
765251881Speter
766251881Speter  svn_tristate_t *supports_deadprop_count;
767251881Speter
768251881Speter  /* If allocations are necessary, then use this pool.  */
769251881Speter  apr_pool_t *result_pool;
770251881Speter};
771251881Speter
772251881Speterstatic svn_error_t *
773251881Speterdirent_walker(void *baton,
774251881Speter              const char *ns,
775251881Speter              const char *name,
776251881Speter              const svn_string_t *val,
777251881Speter              apr_pool_t *scratch_pool)
778251881Speter{
779251881Speter  struct dirent_walker_baton_t *dwb = baton;
780251881Speter
781251881Speter  if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
782251881Speter    {
783251881Speter      dwb->entry->has_props = TRUE;
784251881Speter    }
785251881Speter  else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
786251881Speter    {
787251881Speter      dwb->entry->has_props = TRUE;
788251881Speter    }
789251881Speter  else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
790251881Speter    {
791251881Speter      if(strcmp(name, "deadprop-count") == 0)
792251881Speter        {
793251881Speter          if (*val->data)
794251881Speter            {
795251881Speter              apr_int64_t deadprop_count;
796251881Speter              SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
797251881Speter              dwb->entry->has_props = deadprop_count > 0;
798251881Speter              if (dwb->supports_deadprop_count)
799251881Speter                *dwb->supports_deadprop_count = svn_tristate_true;
800251881Speter            }
801251881Speter          else if (dwb->supports_deadprop_count)
802251881Speter            *dwb->supports_deadprop_count = svn_tristate_false;
803251881Speter        }
804251881Speter    }
805251881Speter  else if (strcmp(ns, "DAV:") == 0)
806251881Speter    {
807251881Speter      if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
808251881Speter        {
809251881Speter          dwb->entry->created_rev = SVN_STR_TO_REV(val->data);
810251881Speter        }
811251881Speter      else if (strcmp(name, "creator-displayname") == 0)
812251881Speter        {
813251881Speter          dwb->entry->last_author = val->data;
814251881Speter        }
815251881Speter      else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
816251881Speter        {
817251881Speter          SVN_ERR(svn_time_from_cstring(&dwb->entry->time,
818251881Speter                                        val->data,
819251881Speter                                        dwb->result_pool));
820251881Speter        }
821251881Speter      else if (strcmp(name, "getcontentlength") == 0)
822251881Speter        {
823251881Speter          /* 'getcontentlength' property is empty for directories. */
824251881Speter          if (val->len)
825251881Speter            {
826251881Speter              SVN_ERR(svn_cstring_atoi64(&dwb->entry->size, val->data));
827251881Speter            }
828251881Speter        }
829251881Speter      else if (strcmp(name, "resourcetype") == 0)
830251881Speter        {
831251881Speter          if (strcmp(val->data, "collection") == 0)
832251881Speter            {
833251881Speter              dwb->entry->kind = svn_node_dir;
834251881Speter            }
835251881Speter          else
836251881Speter            {
837251881Speter              dwb->entry->kind = svn_node_file;
838251881Speter            }
839251881Speter        }
840251881Speter    }
841251881Speter
842251881Speter  return SVN_NO_ERROR;
843251881Speter}
844251881Speter
845251881Speterstruct path_dirent_visitor_t {
846251881Speter  apr_hash_t *full_paths;
847251881Speter  apr_hash_t *base_paths;
848251881Speter  const char *orig_path;
849251881Speter  svn_tristate_t supports_deadprop_count;
850251881Speter  apr_pool_t *result_pool;
851251881Speter};
852251881Speter
853251881Speterstatic svn_error_t *
854251881Speterpath_dirent_walker(void *baton,
855251881Speter                   const char *path, apr_ssize_t path_len,
856251881Speter                   const char *ns, apr_ssize_t ns_len,
857251881Speter                   const char *name, apr_ssize_t name_len,
858251881Speter                   const svn_string_t *val,
859251881Speter                   apr_pool_t *pool)
860251881Speter{
861251881Speter  struct path_dirent_visitor_t *dirents = baton;
862251881Speter  struct dirent_walker_baton_t dwb;
863251881Speter  svn_dirent_t *entry;
864251881Speter
865251881Speter  /* Skip our original path. */
866251881Speter  if (strcmp(path, dirents->orig_path) == 0)
867251881Speter    {
868251881Speter      return SVN_NO_ERROR;
869251881Speter    }
870251881Speter
871251881Speter  entry = apr_hash_get(dirents->full_paths, path, path_len);
872251881Speter
873251881Speter  if (!entry)
874251881Speter    {
875251881Speter      const char *base_name;
876251881Speter
877251881Speter      entry = svn_dirent_create(pool);
878251881Speter
879251881Speter      apr_hash_set(dirents->full_paths, path, path_len, entry);
880251881Speter
881251881Speter      base_name = svn_path_uri_decode(svn_urlpath__basename(path, pool),
882251881Speter                                      pool);
883251881Speter
884251881Speter      svn_hash_sets(dirents->base_paths, base_name, entry);
885251881Speter    }
886251881Speter
887251881Speter  dwb.entry = entry;
888251881Speter  dwb.supports_deadprop_count = &dirents->supports_deadprop_count;
889251881Speter  dwb.result_pool = dirents->result_pool;
890251881Speter  return svn_error_trace(dirent_walker(&dwb, ns, name, val, pool));
891251881Speter}
892251881Speter
893251881Speterstatic const svn_ra_serf__dav_props_t *
894251881Speterget_dirent_props(apr_uint32_t dirent_fields,
895251881Speter                 svn_ra_serf__session_t *session,
896251881Speter                 apr_pool_t *pool)
897251881Speter{
898251881Speter  svn_ra_serf__dav_props_t *prop;
899251881Speter  apr_array_header_t *props = apr_array_make
900251881Speter    (pool, 7, sizeof(svn_ra_serf__dav_props_t));
901251881Speter
902251881Speter  if (session->supports_deadprop_count != svn_tristate_false
903251881Speter      || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
904251881Speter    {
905251881Speter      if (dirent_fields & SVN_DIRENT_KIND)
906251881Speter        {
907251881Speter          prop = apr_array_push(props);
908251881Speter          prop->namespace = "DAV:";
909251881Speter          prop->name = "resourcetype";
910251881Speter        }
911251881Speter
912251881Speter      if (dirent_fields & SVN_DIRENT_SIZE)
913251881Speter        {
914251881Speter          prop = apr_array_push(props);
915251881Speter          prop->namespace = "DAV:";
916251881Speter          prop->name = "getcontentlength";
917251881Speter        }
918251881Speter
919251881Speter      if (dirent_fields & SVN_DIRENT_HAS_PROPS)
920251881Speter        {
921251881Speter          prop = apr_array_push(props);
922251881Speter          prop->namespace = SVN_DAV_PROP_NS_DAV;
923251881Speter          prop->name = "deadprop-count";
924251881Speter        }
925251881Speter
926251881Speter      if (dirent_fields & SVN_DIRENT_CREATED_REV)
927251881Speter        {
928251881Speter          svn_ra_serf__dav_props_t *p = apr_array_push(props);
929251881Speter          p->namespace = "DAV:";
930251881Speter          p->name = SVN_DAV__VERSION_NAME;
931251881Speter        }
932251881Speter
933251881Speter      if (dirent_fields & SVN_DIRENT_TIME)
934251881Speter        {
935251881Speter          prop = apr_array_push(props);
936251881Speter          prop->namespace = "DAV:";
937251881Speter          prop->name = SVN_DAV__CREATIONDATE;
938251881Speter        }
939251881Speter
940251881Speter      if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
941251881Speter        {
942251881Speter          prop = apr_array_push(props);
943251881Speter          prop->namespace = "DAV:";
944251881Speter          prop->name = "creator-displayname";
945251881Speter        }
946251881Speter    }
947251881Speter  else
948251881Speter    {
949251881Speter      /* We found an old subversion server that can't handle
950251881Speter         the deadprop-count property in the way we expect.
951251881Speter
952251881Speter         The neon behavior is to retrieve all properties in this case */
953251881Speter      prop = apr_array_push(props);
954251881Speter      prop->namespace = "DAV:";
955251881Speter      prop->name = "allprop";
956251881Speter    }
957251881Speter
958251881Speter  prop = apr_array_push(props);
959251881Speter  prop->namespace = NULL;
960251881Speter  prop->name = NULL;
961251881Speter
962251881Speter  return (svn_ra_serf__dav_props_t *) props->elts;
963251881Speter}
964251881Speter
965251881Speter/* Implements svn_ra__vtable_t.stat(). */
966251881Speterstatic svn_error_t *
967251881Spetersvn_ra_serf__stat(svn_ra_session_t *ra_session,
968251881Speter                  const char *rel_path,
969251881Speter                  svn_revnum_t revision,
970251881Speter                  svn_dirent_t **dirent,
971251881Speter                  apr_pool_t *pool)
972251881Speter{
973251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
974251881Speter  apr_hash_t *props;
975251881Speter  svn_error_t *err;
976251881Speter  struct dirent_walker_baton_t dwb;
977251881Speter  svn_tristate_t deadprop_count = svn_tristate_unknown;
978251881Speter
979251881Speter  err = fetch_path_props(&props,
980251881Speter                         session, rel_path, revision,
981251881Speter                         get_dirent_props(SVN_DIRENT_ALL, session, pool),
982251881Speter                         pool, pool);
983251881Speter  if (err)
984251881Speter    {
985251881Speter      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
986251881Speter        {
987251881Speter          svn_error_clear(err);
988251881Speter          *dirent = NULL;
989251881Speter          return SVN_NO_ERROR;
990251881Speter        }
991251881Speter      else
992251881Speter        return svn_error_trace(err);
993251881Speter    }
994251881Speter
995251881Speter  dwb.entry = svn_dirent_create(pool);
996251881Speter  dwb.supports_deadprop_count = &deadprop_count;
997251881Speter  dwb.result_pool = pool;
998251881Speter  SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
999251881Speter
1000251881Speter  if (deadprop_count == svn_tristate_false
1001251881Speter      && session->supports_deadprop_count == svn_tristate_unknown
1002251881Speter      && !dwb.entry->has_props)
1003251881Speter    {
1004251881Speter      /* We have to requery as the server didn't give us the right
1005251881Speter         information */
1006251881Speter      session->supports_deadprop_count = svn_tristate_false;
1007251881Speter
1008251881Speter      SVN_ERR(fetch_path_props(&props,
1009251881Speter                               session, rel_path, SVN_INVALID_REVNUM,
1010251881Speter                               get_dirent_props(SVN_DIRENT_ALL, session, pool),
1011251881Speter                               pool, pool));
1012251881Speter
1013251881Speter      SVN_ERR(svn_ra_serf__walk_node_props(props, dirent_walker, &dwb, pool));
1014251881Speter    }
1015251881Speter
1016251881Speter  if (deadprop_count != svn_tristate_unknown)
1017251881Speter    session->supports_deadprop_count = deadprop_count;
1018251881Speter
1019251881Speter  *dirent = dwb.entry;
1020251881Speter
1021251881Speter  return SVN_NO_ERROR;
1022251881Speter}
1023251881Speter
1024251881Speter/* Reads the 'resourcetype' property from the list PROPS and checks if the
1025251881Speter * resource at PATH@REVISION really is a directory. Returns
1026251881Speter * SVN_ERR_FS_NOT_DIRECTORY if not.
1027251881Speter */
1028251881Speterstatic svn_error_t *
1029251881Speterresource_is_directory(apr_hash_t *props)
1030251881Speter{
1031251881Speter  svn_node_kind_t kind;
1032251881Speter
1033251881Speter  SVN_ERR(svn_ra_serf__get_resource_type(&kind, props));
1034251881Speter
1035251881Speter  if (kind != svn_node_dir)
1036251881Speter    {
1037251881Speter      return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1038251881Speter                              _("Can't get entries of non-directory"));
1039251881Speter    }
1040251881Speter
1041251881Speter  return SVN_NO_ERROR;
1042251881Speter}
1043251881Speter
1044251881Speter/* Implements svn_ra__vtable_t.get_dir(). */
1045251881Speterstatic svn_error_t *
1046251881Spetersvn_ra_serf__get_dir(svn_ra_session_t *ra_session,
1047251881Speter                     apr_hash_t **dirents,
1048251881Speter                     svn_revnum_t *fetched_rev,
1049251881Speter                     apr_hash_t **ret_props,
1050251881Speter                     const char *rel_path,
1051251881Speter                     svn_revnum_t revision,
1052251881Speter                     apr_uint32_t dirent_fields,
1053251881Speter                     apr_pool_t *pool)
1054251881Speter{
1055251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
1056251881Speter  const char *path;
1057251881Speter
1058251881Speter  path = session->session_url.path;
1059251881Speter
1060251881Speter  /* If we have a relative path, URI encode and append it. */
1061251881Speter  if (rel_path)
1062251881Speter    {
1063251881Speter      path = svn_path_url_add_component2(path, rel_path, pool);
1064251881Speter    }
1065251881Speter
1066251881Speter  /* If the user specified a peg revision other than HEAD, we have to fetch
1067251881Speter     the baseline collection url for that revision. If not, we can use the
1068251881Speter     public url. */
1069251881Speter  if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
1070251881Speter    {
1071251881Speter      SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
1072251881Speter                                          session, NULL /* conn */,
1073251881Speter                                          path, revision,
1074251881Speter                                          pool, pool));
1075251881Speter      revision = SVN_INVALID_REVNUM;
1076251881Speter    }
1077251881Speter  /* REVISION is always SVN_INVALID_REVNUM  */
1078251881Speter  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
1079251881Speter
1080251881Speter  /* If we're asked for children, fetch them now. */
1081251881Speter  if (dirents)
1082251881Speter    {
1083251881Speter      struct path_dirent_visitor_t dirent_walk;
1084251881Speter      apr_hash_t *props;
1085251881Speter      const char *rtype;
1086251881Speter
1087251881Speter      /* Always request node kind to check that path is really a
1088251881Speter       * directory.
1089251881Speter       */
1090251881Speter      dirent_fields |= SVN_DIRENT_KIND;
1091251881Speter      SVN_ERR(svn_ra_serf__retrieve_props(&props, session, session->conns[0],
1092251881Speter                                          path, SVN_INVALID_REVNUM, "1",
1093251881Speter                                          get_dirent_props(dirent_fields,
1094251881Speter                                                           session, pool),
1095251881Speter                                          pool, pool));
1096251881Speter
1097251881Speter      /* Check if the path is really a directory. */
1098251881Speter      rtype = svn_ra_serf__get_prop(props, path, "DAV:", "resourcetype");
1099251881Speter      if (rtype == NULL || strcmp(rtype, "collection") != 0)
1100251881Speter        return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
1101251881Speter                                _("Can't get entries of non-directory"));
1102251881Speter
1103251881Speter      /* We're going to create two hashes to help the walker along.
1104251881Speter       * We're going to return the 2nd one back to the caller as it
1105251881Speter       * will have the basenames it expects.
1106251881Speter       */
1107251881Speter      dirent_walk.full_paths = apr_hash_make(pool);
1108251881Speter      dirent_walk.base_paths = apr_hash_make(pool);
1109251881Speter      dirent_walk.orig_path = svn_urlpath__canonicalize(path, pool);
1110251881Speter      dirent_walk.supports_deadprop_count = svn_tristate_unknown;
1111251881Speter      dirent_walk.result_pool = pool;
1112251881Speter
1113251881Speter      SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
1114251881Speter                                          path_dirent_walker, &dirent_walk,
1115251881Speter                                          pool));
1116251881Speter
1117251881Speter      if (dirent_walk.supports_deadprop_count == svn_tristate_false
1118251881Speter          && session->supports_deadprop_count == svn_tristate_unknown
1119251881Speter          && dirent_fields & SVN_DIRENT_HAS_PROPS)
1120251881Speter        {
1121251881Speter          /* We have to requery as the server didn't give us the right
1122251881Speter             information */
1123251881Speter          session->supports_deadprop_count = svn_tristate_false;
1124251881Speter          SVN_ERR(svn_ra_serf__retrieve_props(&props, session,
1125251881Speter                                              session->conns[0],
1126251881Speter                                              path, SVN_INVALID_REVNUM, "1",
1127251881Speter                                              get_dirent_props(dirent_fields,
1128251881Speter                                                               session, pool),
1129251881Speter                                              pool, pool));
1130251881Speter
1131251881Speter          apr_hash_clear(dirent_walk.full_paths);
1132251881Speter          apr_hash_clear(dirent_walk.base_paths);
1133251881Speter
1134251881Speter          SVN_ERR(svn_ra_serf__walk_all_paths(props, SVN_INVALID_REVNUM,
1135251881Speter                                              path_dirent_walker,
1136251881Speter                                              &dirent_walk, pool));
1137251881Speter        }
1138251881Speter
1139251881Speter      *dirents = dirent_walk.base_paths;
1140251881Speter
1141251881Speter      if (dirent_walk.supports_deadprop_count != svn_tristate_unknown)
1142251881Speter        session->supports_deadprop_count = dirent_walk.supports_deadprop_count;
1143251881Speter    }
1144251881Speter
1145251881Speter  /* If we're asked for the directory properties, fetch them too. */
1146251881Speter  if (ret_props)
1147251881Speter    {
1148251881Speter      apr_hash_t *props;
1149251881Speter
1150251881Speter      SVN_ERR(svn_ra_serf__fetch_node_props(&props, session->conns[0],
1151251881Speter                                            path, SVN_INVALID_REVNUM,
1152251881Speter                                            all_props,
1153251881Speter                                            pool, pool));
1154251881Speter
1155251881Speter      /* Check if the path is really a directory. */
1156251881Speter      SVN_ERR(resource_is_directory(props));
1157251881Speter
1158251881Speter      /* ### flatten_props() does not copy PROPVALUE, but fetch_node_props()
1159251881Speter         ### put them into POOL, so we're okay.  */
1160251881Speter      SVN_ERR(svn_ra_serf__flatten_props(ret_props, props, pool, pool));
1161251881Speter    }
1162251881Speter
1163251881Speter  return SVN_NO_ERROR;
1164251881Speter}
1165251881Speter
1166251881Spetersvn_error_t *
1167251881Spetersvn_ra_serf__get_repos_root(svn_ra_session_t *ra_session,
1168251881Speter                            const char **url,
1169251881Speter                            apr_pool_t *pool)
1170251881Speter{
1171251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
1172251881Speter
1173251881Speter  if (!session->repos_root_str)
1174251881Speter    {
1175251881Speter      const char *vcc_url;
1176251881Speter      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
1177251881Speter    }
1178251881Speter
1179251881Speter  *url = session->repos_root_str;
1180251881Speter  return SVN_NO_ERROR;
1181251881Speter}
1182251881Speter
1183251881Speter/* TODO: to fetch the uuid from the repository, we need:
1184251881Speter   1. a path that exists in HEAD
1185251881Speter   2. a path that's readable
1186251881Speter
1187251881Speter   get_uuid handles the case where a path doesn't exist in HEAD and also the
1188251881Speter   case where the root of the repository is not readable.
1189251881Speter   However, it does not handle the case where we're fetching path not existing
1190251881Speter   in HEAD of a repository with unreadable root directory.
1191251881Speter
1192251881Speter   Implements svn_ra__vtable_t.get_uuid().
1193251881Speter */
1194251881Speterstatic svn_error_t *
1195251881Spetersvn_ra_serf__get_uuid(svn_ra_session_t *ra_session,
1196251881Speter                      const char **uuid,
1197251881Speter                      apr_pool_t *pool)
1198251881Speter{
1199251881Speter  svn_ra_serf__session_t *session = ra_session->priv;
1200251881Speter
1201251881Speter  if (!session->uuid)
1202251881Speter    {
1203251881Speter      const char *vcc_url;
1204251881Speter
1205251881Speter      /* We should never get here if we have HTTP v2 support, because
1206251881Speter         any server with that support should be transmitting the
1207251881Speter         UUID in the initial OPTIONS response.  */
1208251881Speter      SVN_ERR_ASSERT(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1209251881Speter
1210251881Speter      /* We're not interested in vcc_url and relative_url, but this call also
1211251881Speter         stores the repository's uuid in the session. */
1212251881Speter      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, pool));
1213251881Speter      if (!session->uuid)
1214251881Speter        {
1215251881Speter          return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS, NULL,
1216251881Speter                                  _("The UUID property was not found on the "
1217251881Speter                                    "resource or any of its parents"));
1218251881Speter        }
1219251881Speter    }
1220251881Speter
1221251881Speter  *uuid = session->uuid;
1222251881Speter
1223251881Speter  return SVN_NO_ERROR;
1224251881Speter}
1225251881Speter
1226251881Speter
1227251881Speterstatic const svn_ra__vtable_t serf_vtable = {
1228251881Speter  ra_serf_version,
1229251881Speter  ra_serf_get_description,
1230251881Speter  ra_serf_get_schemes,
1231251881Speter  svn_ra_serf__open,
1232251881Speter  svn_ra_serf__reparent,
1233251881Speter  svn_ra_serf__get_session_url,
1234251881Speter  svn_ra_serf__get_latest_revnum,
1235251881Speter  svn_ra_serf__get_dated_revision,
1236251881Speter  svn_ra_serf__change_rev_prop,
1237251881Speter  svn_ra_serf__rev_proplist,
1238251881Speter  svn_ra_serf__rev_prop,
1239251881Speter  svn_ra_serf__get_commit_editor,
1240251881Speter  svn_ra_serf__get_file,
1241251881Speter  svn_ra_serf__get_dir,
1242251881Speter  svn_ra_serf__get_mergeinfo,
1243251881Speter  svn_ra_serf__do_update,
1244251881Speter  svn_ra_serf__do_switch,
1245251881Speter  svn_ra_serf__do_status,
1246251881Speter  svn_ra_serf__do_diff,
1247251881Speter  svn_ra_serf__get_log,
1248251881Speter  svn_ra_serf__check_path,
1249251881Speter  svn_ra_serf__stat,
1250251881Speter  svn_ra_serf__get_uuid,
1251251881Speter  svn_ra_serf__get_repos_root,
1252251881Speter  svn_ra_serf__get_locations,
1253251881Speter  svn_ra_serf__get_location_segments,
1254251881Speter  svn_ra_serf__get_file_revs,
1255251881Speter  svn_ra_serf__lock,
1256251881Speter  svn_ra_serf__unlock,
1257251881Speter  svn_ra_serf__get_lock,
1258251881Speter  svn_ra_serf__get_locks,
1259251881Speter  svn_ra_serf__replay,
1260251881Speter  svn_ra_serf__has_capability,
1261251881Speter  svn_ra_serf__replay_range,
1262251881Speter  svn_ra_serf__get_deleted_rev,
1263251881Speter  svn_ra_serf__register_editor_shim_callbacks,
1264251881Speter  svn_ra_serf__get_inherited_props
1265251881Speter};
1266251881Speter
1267251881Spetersvn_error_t *
1268251881Spetersvn_ra_serf__init(const svn_version_t *loader_version,
1269251881Speter                  const svn_ra__vtable_t **vtable,
1270251881Speter                  apr_pool_t *pool)
1271251881Speter{
1272251881Speter  static const svn_version_checklist_t checklist[] =
1273251881Speter    {
1274251881Speter      { "svn_subr",  svn_subr_version },
1275251881Speter      { "svn_delta", svn_delta_version },
1276251881Speter      { NULL, NULL }
1277251881Speter    };
1278251881Speter  int serf_major;
1279251881Speter  int serf_minor;
1280251881Speter  int serf_patch;
1281251881Speter
1282262253Speter  SVN_ERR(svn_ver_check_list2(ra_serf_version(), checklist, svn_ver_equal));
1283251881Speter
1284251881Speter  /* Simplified version check to make sure we can safely use the
1285251881Speter     VTABLE parameter. The RA loader does a more exhaustive check. */
1286251881Speter  if (loader_version->major != SVN_VER_MAJOR)
1287251881Speter    {
1288251881Speter      return svn_error_createf(
1289251881Speter         SVN_ERR_VERSION_MISMATCH, NULL,
1290251881Speter         _("Unsupported RA loader version (%d) for ra_serf"),
1291251881Speter         loader_version->major);
1292251881Speter    }
1293251881Speter
1294251881Speter  /* Make sure that we have loaded a compatible library: the MAJOR must
1295251881Speter     match, and the minor must be at *least* what we compiled against.
1296251881Speter     The patch level is simply ignored.  */
1297251881Speter  serf_lib_version(&serf_major, &serf_minor, &serf_patch);
1298251881Speter  if (serf_major != SERF_MAJOR_VERSION
1299251881Speter      || serf_minor < SERF_MINOR_VERSION)
1300251881Speter    {
1301251881Speter      return svn_error_createf(
1302251881Speter         /* ### should return a unique error  */
1303251881Speter         SVN_ERR_VERSION_MISMATCH, NULL,
1304251881Speter         _("ra_serf was compiled for serf %d.%d.%d but loaded "
1305251881Speter           "an incompatible %d.%d.%d library"),
1306251881Speter         SERF_MAJOR_VERSION, SERF_MINOR_VERSION, SERF_PATCH_VERSION,
1307251881Speter         serf_major, serf_minor, serf_patch);
1308251881Speter    }
1309251881Speter
1310251881Speter  *vtable = &serf_vtable;
1311251881Speter
1312251881Speter  return SVN_NO_ERROR;
1313251881Speter}
1314251881Speter
1315251881Speter/* Compatibility wrapper for pre-1.2 subversions.  Needed? */
1316251881Speter#define NAME "ra_serf"
1317251881Speter#define DESCRIPTION RA_SERF_DESCRIPTION
1318251881Speter#define VTBL serf_vtable
1319251881Speter#define INITFUNC svn_ra_serf__init
1320251881Speter#define COMPAT_INITFUNC svn_ra_serf_init
1321251881Speter#include "../libsvn_ra/wrapper_template.h"
1322