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