util.c revision 362181
1/*
2 * util.c : serf utility routines 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#include <assert.h>
27
28#define APR_WANT_STRFUNC
29#include <apr.h>
30#include <apr_want.h>
31
32#include <serf.h>
33#include <serf_bucket_types.h>
34
35#include "svn_hash.h"
36#include "svn_dirent_uri.h"
37#include "svn_path.h"
38#include "svn_private_config.h"
39#include "svn_string.h"
40#include "svn_props.h"
41#include "svn_dirent_uri.h"
42
43#include "../libsvn_ra/ra_loader.h"
44#include "private/svn_dep_compat.h"
45#include "private/svn_fspath.h"
46#include "private/svn_auth_private.h"
47#include "private/svn_cert.h"
48
49#include "ra_serf.h"
50
51static const apr_uint32_t serf_failure_map[][2] =
52{
53  { SERF_SSL_CERT_NOTYETVALID,   SVN_AUTH_SSL_NOTYETVALID },
54  { SERF_SSL_CERT_EXPIRED,       SVN_AUTH_SSL_EXPIRED },
55  { SERF_SSL_CERT_SELF_SIGNED,   SVN_AUTH_SSL_UNKNOWNCA },
56  { SERF_SSL_CERT_UNKNOWNCA,     SVN_AUTH_SSL_UNKNOWNCA }
57};
58
59/* Return a Subversion failure mask based on FAILURES, a serf SSL
60   failure mask.  If anything in FAILURES is not directly mappable to
61   Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */
62static apr_uint32_t
63ssl_convert_serf_failures(int failures)
64{
65  apr_uint32_t svn_failures = 0;
66  apr_size_t i;
67
68  for (i = 0;
69       i < sizeof(serf_failure_map) / (sizeof(serf_failure_map[0]));
70       ++i)
71    {
72      if (failures & serf_failure_map[i][0])
73        {
74          svn_failures |= serf_failure_map[i][1];
75          failures &= ~serf_failure_map[i][0];
76        }
77    }
78
79  /* Map any remaining failure bits to our OTHER bit. */
80  if (failures)
81    {
82      svn_failures |= SVN_AUTH_SSL_OTHER;
83    }
84
85  return svn_failures;
86}
87
88
89static apr_status_t
90save_error(svn_ra_serf__session_t *session,
91           svn_error_t *err)
92{
93  if (err || session->pending_error)
94    {
95      session->pending_error = svn_error_compose_create(
96                                  session->pending_error,
97                                  err);
98      return session->pending_error->apr_err;
99    }
100
101  return APR_SUCCESS;
102}
103
104
105/* Construct the realmstring, e.g. https://svn.collab.net:443. */
106static const char *
107construct_realm(svn_ra_serf__session_t *session,
108                apr_pool_t *pool)
109{
110  const char *realm;
111  apr_port_t port;
112
113  if (session->session_url.port_str)
114    {
115      port = session->session_url.port;
116    }
117  else
118    {
119      port = apr_uri_port_of_scheme(session->session_url.scheme);
120    }
121
122  realm = apr_psprintf(pool, "%s://%s:%d",
123                       session->session_url.scheme,
124                       session->session_url.hostname,
125                       port);
126
127  return realm;
128}
129
130/* Convert a hash table containing the fields (as documented in X.509) of an
131   organisation to a string ORG, allocated in POOL. ORG is as returned by
132   serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
133static char *
134convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
135{
136  const char *cn = svn_hash_gets(org, "CN");
137  const char *org_unit = svn_hash_gets(org, "OU");
138  const char *org_name = svn_hash_gets(org, "O");
139  const char *locality = svn_hash_gets(org, "L");
140  const char *state = svn_hash_gets(org, "ST");
141  const char *country = svn_hash_gets(org, "C");
142  const char *email = svn_hash_gets(org, "E");
143  svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
144
145  if (cn)
146    {
147      svn_stringbuf_appendcstr(buf, cn);
148      svn_stringbuf_appendcstr(buf, ", ");
149    }
150
151  if (org_unit)
152    {
153      svn_stringbuf_appendcstr(buf, org_unit);
154      svn_stringbuf_appendcstr(buf, ", ");
155    }
156
157  if (org_name)
158    {
159      svn_stringbuf_appendcstr(buf, org_name);
160      svn_stringbuf_appendcstr(buf, ", ");
161    }
162
163  if (locality)
164    {
165      svn_stringbuf_appendcstr(buf, locality);
166      svn_stringbuf_appendcstr(buf, ", ");
167    }
168
169  if (state)
170    {
171      svn_stringbuf_appendcstr(buf, state);
172      svn_stringbuf_appendcstr(buf, ", ");
173    }
174
175  if (country)
176    {
177      svn_stringbuf_appendcstr(buf, country);
178      svn_stringbuf_appendcstr(buf, ", ");
179    }
180
181  /* Chop ', ' if any. */
182  svn_stringbuf_chop(buf, 2);
183
184  if (email)
185    {
186      svn_stringbuf_appendcstr(buf, "(");
187      svn_stringbuf_appendcstr(buf, email);
188      svn_stringbuf_appendcstr(buf, ")");
189    }
190
191  return buf->data;
192}
193
194static void append_reason(svn_stringbuf_t *errmsg, const char *reason, int *reasons)
195{
196  if (*reasons < 1)
197    svn_stringbuf_appendcstr(errmsg, _(": "));
198  else
199    svn_stringbuf_appendcstr(errmsg, _(", "));
200  svn_stringbuf_appendcstr(errmsg, reason);
201  (*reasons)++;
202}
203
204/* This function is called on receiving a ssl certificate of a server when
205   opening a https connection. It allows Subversion to override the initial
206   validation done by serf.
207   Serf provides us the @a baton as provided in the call to
208   serf_ssl_server_cert_callback_set. The result of serf's initial validation
209   of the certificate @a CERT is returned as a bitmask in FAILURES. */
210static svn_error_t *
211ssl_server_cert(void *baton, int failures,
212                const serf_ssl_certificate_t *cert,
213                apr_pool_t *scratch_pool)
214{
215  svn_ra_serf__connection_t *conn = baton;
216  svn_auth_ssl_server_cert_info_t cert_info;
217  svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
218  svn_auth_iterstate_t *state;
219  const char *realmstring;
220  apr_uint32_t svn_failures;
221  apr_hash_t *issuer;
222  apr_hash_t *subject = NULL;
223  apr_hash_t *serf_cert = NULL;
224  void *creds;
225
226  svn_failures = (ssl_convert_serf_failures(failures)
227      | conn->server_cert_failures);
228
229  if (serf_ssl_cert_depth(cert) == 0)
230    {
231      /* If the depth is 0, the hostname must match the certificate.
232
233      ### This should really be handled by serf, which should pass an error
234          for this case, but that has backwards compatibility issues. */
235      apr_array_header_t *san;
236      svn_boolean_t found_matching_hostname = FALSE;
237      svn_string_t *actual_hostname =
238          svn_string_create(conn->session->session_url.hostname, scratch_pool);
239
240      serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
241
242      san = svn_hash_gets(serf_cert, "subjectAltName");
243      /* Match server certificate CN with the hostname of the server iff
244       * we didn't find any subjectAltName fields and try to match them.
245       * Per RFC 2818 they are authoritative if present and CommonName
246       * should be ignored.  NOTE: This isn't 100% correct since serf
247       * only loads the subjectAltName hash with dNSNames, technically
248       * we should ignore the CommonName if any subjectAltName entry
249       * exists even if it is one we don't support. */
250      if (san && san->nelts > 0)
251        {
252          int i;
253          for (i = 0; i < san->nelts; i++)
254            {
255              const char *s = APR_ARRAY_IDX(san, i, const char*);
256              svn_string_t *cert_hostname = svn_string_create(s, scratch_pool);
257
258              if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
259                {
260                  found_matching_hostname = TRUE;
261                  break;
262                }
263            }
264        }
265      else
266        {
267          const char *hostname = NULL;
268
269          subject = serf_ssl_cert_subject(cert, scratch_pool);
270
271          if (subject)
272            hostname = svn_hash_gets(subject, "CN");
273
274          if (hostname)
275            {
276              svn_string_t *cert_hostname = svn_string_create(hostname,
277                                                              scratch_pool);
278
279              if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
280                {
281                  found_matching_hostname = TRUE;
282                }
283            }
284        }
285
286      if (!found_matching_hostname)
287        svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
288    }
289
290  if (!svn_failures)
291    return SVN_NO_ERROR;
292
293  /* Extract the info from the certificate */
294  if (! subject)
295    subject = serf_ssl_cert_subject(cert, scratch_pool);
296  issuer = serf_ssl_cert_issuer(cert, scratch_pool);
297  if (! serf_cert)
298    serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
299
300  cert_info.hostname = svn_hash_gets(subject, "CN");
301  cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
302  if (! cert_info.fingerprint)
303    cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
304  cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
305  if (! cert_info.valid_from)
306    cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
307  cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
308  if (! cert_info.valid_until)
309    cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
310  cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
311  cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
312
313  /* Handle any non-server certs. */
314  if (serf_ssl_cert_depth(cert) > 0)
315    {
316      svn_error_t *err;
317
318      svn_auth_set_parameter(conn->session->auth_baton,
319                             SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
320                             &cert_info);
321
322      svn_auth_set_parameter(conn->session->auth_baton,
323                             SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
324                             &svn_failures);
325
326      realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s",
327                                 cert_info.fingerprint);
328
329      err = svn_auth_first_credentials(&creds, &state,
330                                       SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
331                                       realmstring,
332                                       conn->session->auth_baton,
333                                       scratch_pool);
334
335      svn_auth_set_parameter(conn->session->auth_baton,
336                             SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
337
338      svn_auth_set_parameter(conn->session->auth_baton,
339                             SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
340
341      if (err)
342        {
343          if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER)
344            return svn_error_trace(err);
345
346          /* No provider registered that handles server authorities */
347          svn_error_clear(err);
348          creds = NULL;
349        }
350
351      if (creds)
352        {
353          server_creds = creds;
354          SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
355
356          svn_failures &= ~server_creds->accepted_failures;
357        }
358
359      if (svn_failures)
360        conn->server_cert_failures |= svn_failures;
361
362      return APR_SUCCESS;
363    }
364
365  svn_auth_set_parameter(conn->session->auth_baton,
366                         SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
367                         &svn_failures);
368
369  svn_auth_set_parameter(conn->session->auth_baton,
370                         SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
371                         &cert_info);
372
373  realmstring = construct_realm(conn->session, conn->session->pool);
374
375  SVN_ERR(svn_auth_first_credentials(&creds, &state,
376                                     SVN_AUTH_CRED_SSL_SERVER_TRUST,
377                                     realmstring,
378                                     conn->session->auth_baton,
379                                     scratch_pool));
380  if (creds)
381    {
382      server_creds = creds;
383      svn_failures &= ~server_creds->accepted_failures;
384      SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
385    }
386
387  while (svn_failures && creds)
388    {
389      SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool));
390
391      if (creds)
392        {
393          server_creds = creds;
394          svn_failures &= ~server_creds->accepted_failures;
395          SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
396        }
397    }
398
399  svn_auth_set_parameter(conn->session->auth_baton,
400                         SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
401
402  /* Are there non accepted failures left? */
403  if (svn_failures)
404    {
405      svn_stringbuf_t *errmsg;
406      int reasons = 0;
407
408      errmsg = svn_stringbuf_create(
409                 _("Server SSL certificate verification failed"),
410                 scratch_pool);
411
412
413      if (svn_failures & SVN_AUTH_SSL_NOTYETVALID)
414        append_reason(errmsg, _("certificate is not yet valid"), &reasons);
415
416      if (svn_failures & SVN_AUTH_SSL_EXPIRED)
417        append_reason(errmsg, _("certificate has expired"), &reasons);
418
419      if (svn_failures & SVN_AUTH_SSL_CNMISMATCH)
420        append_reason(errmsg,
421                      _("certificate issued for a different hostname"),
422                      &reasons);
423
424      if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA)
425        append_reason(errmsg, _("issuer is not trusted"), &reasons);
426
427      if (svn_failures & SVN_AUTH_SSL_OTHER)
428        append_reason(errmsg, _("and other reason(s)"), &reasons);
429
430      return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL,
431                              errmsg->data);
432    }
433
434  return SVN_NO_ERROR;
435}
436
437/* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
438static apr_status_t
439ssl_server_cert_cb(void *baton, int failures,
440                const serf_ssl_certificate_t *cert)
441{
442  svn_ra_serf__connection_t *conn = baton;
443  svn_ra_serf__session_t *session = conn->session;
444  apr_pool_t *subpool;
445  svn_error_t *err;
446
447  subpool = svn_pool_create(session->pool);
448  err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
449  svn_pool_destroy(subpool);
450
451  return save_error(session, err);
452}
453
454static svn_error_t *
455load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
456                 apr_pool_t *pool)
457{
458  apr_array_header_t *files = svn_cstring_split(authorities, ";",
459                                                TRUE /* chop_whitespace */,
460                                                pool);
461  int i;
462
463  for (i = 0; i < files->nelts; ++i)
464    {
465      const char *file = APR_ARRAY_IDX(files, i, const char *);
466      serf_ssl_certificate_t *ca_cert;
467      apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
468
469      if (status == APR_SUCCESS)
470        status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
471
472      if (status != APR_SUCCESS)
473        {
474          return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
475             _("Invalid config: unable to load certificate file '%s'"),
476             svn_dirent_local_style(file, pool));
477        }
478    }
479
480  return SVN_NO_ERROR;
481}
482
483#if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2)
484/* Implements serf_ssl_protocol_result_cb_t */
485static apr_status_t
486conn_negotiate_protocol(void *data,
487                        const char *protocol)
488{
489  svn_ra_serf__connection_t *conn = data;
490
491  if (!strcmp(protocol, "h2"))
492    {
493      serf_connection_set_framing_type(
494            conn->conn,
495            SERF_CONNECTION_FRAMING_TYPE_HTTP2);
496
497      /* Disable generating content-length headers. */
498      conn->session->http10 = FALSE;
499      conn->session->http20 = TRUE;
500      conn->session->using_chunked_requests = TRUE;
501      conn->session->detect_chunking = FALSE;
502    }
503  else
504    {
505      /* protocol should be "" or "http/1.1" */
506      serf_connection_set_framing_type(
507            conn->conn,
508            SERF_CONNECTION_FRAMING_TYPE_HTTP1);
509    }
510
511  return APR_SUCCESS;
512}
513#endif
514
515static svn_error_t *
516conn_setup(apr_socket_t *sock,
517           serf_bucket_t **read_bkt,
518           serf_bucket_t **write_bkt,
519           void *baton,
520           apr_pool_t *pool)
521{
522  svn_ra_serf__connection_t *conn = baton;
523
524  *read_bkt = serf_context_bucket_socket_create(conn->session->context,
525                                               sock, conn->bkt_alloc);
526
527  if (conn->session->using_ssl)
528    {
529      /* input stream */
530      *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
531                                                 conn->bkt_alloc);
532      if (!conn->ssl_context)
533        {
534          conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
535
536          serf_ssl_set_hostname(conn->ssl_context,
537                                conn->session->session_url.hostname);
538
539          serf_ssl_client_cert_provider_set(conn->ssl_context,
540                                            svn_ra_serf__handle_client_cert,
541                                            conn, conn->session->pool);
542          serf_ssl_client_cert_password_set(conn->ssl_context,
543                                            svn_ra_serf__handle_client_cert_pw,
544                                            conn, conn->session->pool);
545          serf_ssl_server_cert_callback_set(conn->ssl_context,
546                                            ssl_server_cert_cb,
547                                            conn);
548
549          /* See if the user wants us to trust "default" openssl CAs. */
550          if (conn->session->trust_default_ca)
551            {
552              serf_ssl_use_default_certificates(conn->ssl_context);
553            }
554          /* Are there custom CAs to load? */
555          if (conn->session->ssl_authorities)
556            {
557              SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
558                                       conn->session->pool));
559            }
560#if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2)
561          if (APR_SUCCESS ==
562                serf_ssl_negotiate_protocol(conn->ssl_context, "h2,http/1.1",
563                                            conn_negotiate_protocol, conn))
564            {
565                serf_connection_set_framing_type(
566                            conn->conn,
567                            SERF_CONNECTION_FRAMING_TYPE_NONE);
568            }
569#endif
570        }
571
572      if (write_bkt)
573        {
574          /* output stream */
575          *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
576                                                      conn->ssl_context,
577                                                      conn->bkt_alloc);
578        }
579    }
580
581  return SVN_NO_ERROR;
582}
583
584/* svn_ra_serf__conn_setup is a callback for serf. This function
585   creates a read bucket and will wrap the write bucket if SSL
586   is needed. */
587apr_status_t
588svn_ra_serf__conn_setup(apr_socket_t *sock,
589                        serf_bucket_t **read_bkt,
590                        serf_bucket_t **write_bkt,
591                        void *baton,
592                        apr_pool_t *pool)
593{
594  svn_ra_serf__connection_t *conn = baton;
595  svn_ra_serf__session_t *session = conn->session;
596  svn_error_t *err;
597
598  err = svn_error_trace(conn_setup(sock,
599                                   read_bkt,
600                                   write_bkt,
601                                   baton,
602                                   pool));
603  return save_error(session, err);
604}
605
606
607/* Our default serf response acceptor.  */
608static serf_bucket_t *
609accept_response(serf_request_t *request,
610                serf_bucket_t *stream,
611                void *acceptor_baton,
612                apr_pool_t *pool)
613{
614  /* svn_ra_serf__handler_t *handler = acceptor_baton; */
615  serf_bucket_t *c;
616  serf_bucket_alloc_t *bkt_alloc;
617
618  bkt_alloc = serf_request_get_alloc(request);
619  c = serf_bucket_barrier_create(stream, bkt_alloc);
620
621  return serf_bucket_response_create(c, bkt_alloc);
622}
623
624
625/* Custom response acceptor for HEAD requests.  */
626static serf_bucket_t *
627accept_head(serf_request_t *request,
628            serf_bucket_t *stream,
629            void *acceptor_baton,
630            apr_pool_t *pool)
631{
632  /* svn_ra_serf__handler_t *handler = acceptor_baton; */
633  serf_bucket_t *response;
634
635  response = accept_response(request, stream, acceptor_baton, pool);
636
637  /* We know we shouldn't get a response body. */
638  serf_bucket_response_set_head(response);
639
640  return response;
641}
642
643static svn_error_t *
644connection_closed(svn_ra_serf__connection_t *conn,
645                  apr_status_t why,
646                  apr_pool_t *pool)
647{
648  if (why)
649    {
650      return svn_ra_serf__wrap_err(why, NULL);
651    }
652
653  if (conn->session->using_ssl)
654    conn->ssl_context = NULL;
655
656  return SVN_NO_ERROR;
657}
658
659void
660svn_ra_serf__conn_closed(serf_connection_t *conn,
661                         void *closed_baton,
662                         apr_status_t why,
663                         apr_pool_t *pool)
664{
665  svn_ra_serf__connection_t *ra_conn = closed_baton;
666  svn_error_t *err;
667
668  err = svn_error_trace(connection_closed(ra_conn, why, pool));
669
670  (void) save_error(ra_conn->session, err);
671}
672
673
674/* Implementation of svn_ra_serf__handle_client_cert */
675static svn_error_t *
676handle_client_cert(void *data,
677                   const char **cert_path,
678                   apr_pool_t *pool)
679{
680    svn_ra_serf__connection_t *conn = data;
681    svn_ra_serf__session_t *session = conn->session;
682    const char *realm;
683    void *creds;
684
685    *cert_path = NULL;
686
687    realm = construct_realm(session, session->pool);
688
689    if (!conn->ssl_client_auth_state)
690      {
691        SVN_ERR(svn_auth_first_credentials(&creds,
692                                           &conn->ssl_client_auth_state,
693                                           SVN_AUTH_CRED_SSL_CLIENT_CERT,
694                                           realm,
695                                           session->auth_baton,
696                                           pool));
697      }
698    else
699      {
700        SVN_ERR(svn_auth_next_credentials(&creds,
701                                          conn->ssl_client_auth_state,
702                                          session->pool));
703      }
704
705    if (creds)
706      {
707        svn_auth_cred_ssl_client_cert_t *client_creds;
708        client_creds = creds;
709        *cert_path = client_creds->cert_file;
710      }
711
712    return SVN_NO_ERROR;
713}
714
715/* Implements serf_ssl_need_client_cert_t for handle_client_cert */
716apr_status_t svn_ra_serf__handle_client_cert(void *data,
717                                             const char **cert_path)
718{
719  svn_ra_serf__connection_t *conn = data;
720  svn_ra_serf__session_t *session = conn->session;
721  svn_error_t *err;
722
723  err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
724
725  return save_error(session, err);
726}
727
728/* Implementation for svn_ra_serf__handle_client_cert_pw */
729static svn_error_t *
730handle_client_cert_pw(void *data,
731                      const char *cert_path,
732                      const char **password,
733                      apr_pool_t *pool)
734{
735    svn_ra_serf__connection_t *conn = data;
736    svn_ra_serf__session_t *session = conn->session;
737    void *creds;
738
739    *password = NULL;
740
741    if (!conn->ssl_client_pw_auth_state)
742      {
743        SVN_ERR(svn_auth_first_credentials(&creds,
744                                           &conn->ssl_client_pw_auth_state,
745                                           SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
746                                           cert_path,
747                                           session->auth_baton,
748                                           pool));
749      }
750    else
751      {
752        SVN_ERR(svn_auth_next_credentials(&creds,
753                                          conn->ssl_client_pw_auth_state,
754                                          pool));
755      }
756
757    if (creds)
758      {
759        /* At this stage we are unable to check whether the password
760           is correct; if it is incorrect serf will fail to establish
761           an SSL connection and will return a generic SSL error. */
762        svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
763        pw_creds = creds;
764        *password = pw_creds->password;
765      }
766
767    return APR_SUCCESS;
768}
769
770/* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
771apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
772                                                const char *cert_path,
773                                                const char **password)
774{
775  svn_ra_serf__connection_t *conn = data;
776  svn_ra_serf__session_t *session = conn->session;
777  svn_error_t *err;
778
779  err = svn_error_trace(handle_client_cert_pw(data,
780                                              cert_path,
781                                              password,
782                                              session->pool));
783
784  return save_error(session, err);
785}
786
787
788/*
789 * Given a REQUEST on connection CONN, construct a request bucket for it,
790 * returning the bucket in *REQ_BKT.
791 *
792 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
793 * corresponds to the new request.
794 *
795 * The request will be METHOD at URL.
796 *
797 * If BODY_BKT is not-NULL, it will be sent as the request body.
798 *
799 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
800 *
801 * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers
802 * to request.
803 *
804 * REQUEST_POOL should live for the duration of the request. Serf will
805 * construct this and provide it to the request_setup callback, so we
806 * should just use that one.
807 */
808static svn_error_t *
809setup_serf_req(serf_request_t *request,
810               serf_bucket_t **req_bkt,
811               serf_bucket_t **hdrs_bkt,
812               svn_ra_serf__session_t *session,
813               const char *method, const char *url,
814               serf_bucket_t *body_bkt, const char *content_type,
815               const char *accept_encoding,
816               svn_boolean_t dav_headers,
817               apr_pool_t *request_pool,
818               apr_pool_t *scratch_pool)
819{
820  serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
821
822  svn_spillbuf_t *buf;
823  svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
824
825  if (set_CL && body_bkt != NULL)
826    {
827      /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
828         it speaks HTTP/1.1 (and thus, chunked requests), or because the
829         server actually responded as only supporting HTTP/1.0.
830
831         We'll take the existing body_bkt, spool it into a spillbuf, and
832         then wrap a bucket around that spillbuf. The spillbuf will give
833         us the Content-Length value.  */
834      SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
835                                              request_pool,
836                                              scratch_pool));
837      /* Destroy original bucket since it content is already copied
838         to spillbuf. */
839      serf_bucket_destroy(body_bkt);
840
841      body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
842                                               request_pool,
843                                               scratch_pool);
844    }
845
846  /* Create a request bucket.  Note that this sucker is kind enough to
847     add a "Host" header for us.  */
848  *req_bkt = serf_request_bucket_request_create(request, method, url,
849                                                body_bkt, allocator);
850
851  /* Set the Content-Length value. This will also trigger an HTTP/1.0
852     request (rather than the default chunked request).  */
853  if (set_CL)
854    {
855      if (body_bkt == NULL)
856        serf_bucket_request_set_CL(*req_bkt, 0);
857      else
858        serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
859    }
860
861  *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
862
863  /* We use serf_bucket_headers_setn() because the USERAGENT has a
864     lifetime longer than this bucket. Thus, there is no need to copy
865     the header values.  */
866  serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
867
868  if (content_type)
869    {
870      serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
871    }
872
873  if (session->http10)
874    {
875      serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
876    }
877
878  if (accept_encoding)
879    {
880      serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
881    }
882
883  /* These headers need to be sent with every request that might need
884     capability processing (e.g. during commit, reports, etc.), see
885     issue #3255 ("mod_dav_svn does not pass client capabilities to
886     start-commit hooks") for why.
887
888     Some request types like GET/HEAD/PROPFIND are unaware of capability
889     handling; and in some cases the responses can even be cached by
890     proxies, so we don't have to send these hearders there. */
891  if (dav_headers)
892    {
893      serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
894      serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
895      serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
896    }
897
898  return SVN_NO_ERROR;
899}
900
901svn_error_t *
902svn_ra_serf__context_run(svn_ra_serf__session_t *sess,
903                         apr_interval_time_t *waittime_left,
904                         apr_pool_t *scratch_pool)
905{
906  apr_status_t status;
907  svn_error_t *err;
908  assert(sess->pending_error == SVN_NO_ERROR);
909
910  if (sess->cancel_func)
911    SVN_ERR(sess->cancel_func(sess->cancel_baton));
912
913  status = serf_context_run(sess->context,
914                            SVN_RA_SERF__CONTEXT_RUN_DURATION,
915                            scratch_pool);
916
917  err = sess->pending_error;
918  sess->pending_error = SVN_NO_ERROR;
919
920   /* If the context duration timeout is up, we'll subtract that
921      duration from the total time alloted for such things.  If
922      there's no time left, we fail with a message indicating that
923      the connection timed out.  */
924  if (APR_STATUS_IS_TIMEUP(status))
925    {
926      status = 0;
927
928      if (sess->timeout)
929        {
930          if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
931            {
932              *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
933            }
934          else
935            {
936              return
937                  svn_error_compose_create(
938                        err,
939                        svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
940                                         _("Connection timed out")));
941            }
942        }
943    }
944  else
945    {
946      *waittime_left = sess->timeout;
947    }
948
949  SVN_ERR(err);
950  if (status)
951    {
952      /* ### This omits SVN_WARNING, and possibly relies on the fact that
953         ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */
954      if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
955        {
956          /* apr can't translate subversion errors to text */
957          SVN_ERR_W(svn_error_create(status, NULL, NULL),
958                    _("Error running context"));
959        }
960
961      return svn_ra_serf__wrap_err(status, _("Error running context"));
962    }
963
964  return SVN_NO_ERROR;
965}
966
967svn_error_t *
968svn_ra_serf__context_run_wait(svn_boolean_t *done,
969                              svn_ra_serf__session_t *sess,
970                              apr_pool_t *scratch_pool)
971{
972  apr_pool_t *iterpool;
973  apr_interval_time_t waittime_left = sess->timeout;
974
975  assert(sess->pending_error == SVN_NO_ERROR);
976
977  iterpool = svn_pool_create(scratch_pool);
978  while (!*done)
979    {
980      int i;
981
982      svn_pool_clear(iterpool);
983
984      SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
985
986      /* Debugging purposes only! */
987      for (i = 0; i < sess->num_conns; i++)
988        {
989          serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
990        }
991    }
992  svn_pool_destroy(iterpool);
993
994  return SVN_NO_ERROR;
995}
996
997/* Ensure that a handler is no longer scheduled on the connection.
998
999   Eventually serf will have a reliable way to cancel existing requests,
1000   but currently it doesn't even have a way to relyable identify a request
1001   after rescheduling, for auth reasons.
1002
1003   So the only thing we can do today is reset the connection, which
1004   will cancel all outstanding requests and prepare the connection
1005   for re-use.
1006*/
1007void
1008svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler)
1009{
1010  serf_connection_reset(handler->conn->conn);
1011  handler->scheduled = FALSE;
1012}
1013
1014svn_error_t *
1015svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
1016                             apr_pool_t *scratch_pool)
1017{
1018  svn_error_t *err;
1019
1020  /* Create a serf request based on HANDLER.  */
1021  svn_ra_serf__request_create(handler);
1022
1023  /* Wait until the response logic marks its DONE status.  */
1024  err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
1025                                      scratch_pool);
1026
1027  if (handler->scheduled)
1028    {
1029      /* We reset the connection (breaking  pipelining, etc.), as
1030         if we didn't the next data would still be handled by this handler,
1031         which is done as far as our caller is concerned. */
1032      svn_ra_serf__unschedule_handler(handler);
1033    }
1034
1035  return svn_error_trace(err);
1036}
1037
1038
1039
1040
1041static apr_status_t
1042drain_bucket(serf_bucket_t *bucket)
1043{
1044  /* Read whatever is in the bucket, and just drop it.  */
1045  while (1)
1046    {
1047      apr_status_t status;
1048      const char *data;
1049      apr_size_t len;
1050
1051      status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
1052      if (status)
1053        return status;
1054    }
1055}
1056
1057
1058
1059
1060/* Implements svn_ra_serf__response_handler_t */
1061svn_error_t *
1062svn_ra_serf__handle_discard_body(serf_request_t *request,
1063                                 serf_bucket_t *response,
1064                                 void *baton,
1065                                 apr_pool_t *pool)
1066{
1067  apr_status_t status;
1068
1069  status = drain_bucket(response);
1070  if (status)
1071    return svn_ra_serf__wrap_err(status, NULL);
1072
1073  return SVN_NO_ERROR;
1074}
1075
1076apr_status_t
1077svn_ra_serf__response_discard_handler(serf_request_t *request,
1078                                      serf_bucket_t *response,
1079                                      void *baton,
1080                                      apr_pool_t *pool)
1081{
1082  return drain_bucket(response);
1083}
1084
1085
1086/* Return the value of the RESPONSE's Location header if any, or NULL
1087   otherwise.  */
1088static const char *
1089response_get_location(serf_bucket_t *response,
1090                      const char *base_url,
1091                      apr_pool_t *result_pool,
1092                      apr_pool_t *scratch_pool)
1093{
1094  serf_bucket_t *headers;
1095  const char *location;
1096
1097  headers = serf_bucket_response_get_headers(response);
1098  location = serf_bucket_headers_get(headers, "Location");
1099  if (location == NULL)
1100    return NULL;
1101
1102  /* The RFCs say we should have received a full url in LOCATION, but
1103     older apache versions and many custom web handlers just return a
1104     relative path here...
1105
1106     And we can't trust anything because it is network data.
1107   */
1108  if (*location == '/')
1109    {
1110      apr_uri_t uri;
1111      apr_status_t status;
1112
1113      status = apr_uri_parse(scratch_pool, base_url, &uri);
1114
1115      if (status != APR_SUCCESS)
1116        return NULL;
1117
1118      /* Replace the path path with what we got */
1119      uri.path = apr_pstrdup(scratch_pool, location);
1120
1121      /* And make APR produce a proper full url for us */
1122      return apr_uri_unparse(result_pool, &uri, 0);
1123    }
1124  else if (!svn_path_is_url(location))
1125    {
1126      return NULL; /* Any other formats we should support? */
1127    }
1128
1129  return apr_pstrdup(result_pool, location);
1130}
1131
1132
1133/* Implements svn_ra_serf__response_handler_t */
1134svn_error_t *
1135svn_ra_serf__expect_empty_body(serf_request_t *request,
1136                               serf_bucket_t *response,
1137                               void *baton,
1138                               apr_pool_t *scratch_pool)
1139{
1140  svn_ra_serf__handler_t *handler = baton;
1141  serf_bucket_t *hdrs;
1142  const char *val;
1143
1144  /* This function is just like handle_multistatus_only() except for the
1145     XML parsing callbacks. We want to look for the -readable element.  */
1146
1147  /* We should see this just once, in order to initialize SERVER_ERROR.
1148     At that point, the core error processing will take over. If we choose
1149     not to parse an error, then we'll never return here (because we
1150     change the response handler).  */
1151  SVN_ERR_ASSERT(handler->server_error == NULL);
1152
1153  hdrs = serf_bucket_response_get_headers(response);
1154  val = serf_bucket_headers_get(hdrs, "Content-Type");
1155  if (val
1156      && (handler->sline.code < 200 || handler->sline.code >= 300)
1157      && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1158    {
1159      svn_ra_serf__server_error_t *server_err;
1160
1161      SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1162                                               FALSE,
1163                                               handler->handler_pool,
1164                                               handler->handler_pool));
1165
1166      handler->server_error = server_err;
1167    }
1168  else
1169    {
1170      /* The body was not text/xml, or we got a success code.
1171         Toss anything that arrives.  */
1172      handler->discard_body = TRUE;
1173    }
1174
1175  /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1176     to call the response handler again. That will start up the XML parsing,
1177     or it will be dropped on the floor (per the decision above).  */
1178  return SVN_NO_ERROR;
1179}
1180
1181
1182apr_status_t
1183svn_ra_serf__credentials_callback(char **username, char **password,
1184                                  serf_request_t *request, void *baton,
1185                                  int code, const char *authn_type,
1186                                  const char *realm,
1187                                  apr_pool_t *pool)
1188{
1189  svn_ra_serf__handler_t *handler = baton;
1190  svn_ra_serf__session_t *session = handler->session;
1191  void *creds;
1192  svn_auth_cred_simple_t *simple_creds;
1193  svn_error_t *err;
1194
1195  if (code == 401)
1196    {
1197      /* Use svn_auth_first_credentials if this is the first time we ask for
1198         credentials during this session OR if the last time we asked
1199         session->auth_state wasn't set (eg. if the credentials provider was
1200         cancelled by the user). */
1201      if (!session->auth_state)
1202        {
1203          err = svn_auth_first_credentials(&creds,
1204                                           &session->auth_state,
1205                                           SVN_AUTH_CRED_SIMPLE,
1206                                           realm,
1207                                           session->auth_baton,
1208                                           session->pool);
1209        }
1210      else
1211        {
1212          err = svn_auth_next_credentials(&creds,
1213                                          session->auth_state,
1214                                          session->pool);
1215        }
1216
1217      if (err)
1218        {
1219          (void) save_error(session, err);
1220          return err->apr_err;
1221        }
1222
1223      session->auth_attempts++;
1224
1225      if (!creds || session->auth_attempts > 4)
1226        {
1227          /* No more credentials. */
1228          (void) save_error(session,
1229                            svn_error_create(
1230                              SVN_ERR_AUTHN_FAILED, NULL,
1231                              _("No more credentials or we tried too many "
1232                                "times.\nAuthentication failed")));
1233          return SVN_ERR_AUTHN_FAILED;
1234        }
1235
1236      simple_creds = creds;
1237      *username = apr_pstrdup(pool, simple_creds->username);
1238      *password = apr_pstrdup(pool, simple_creds->password);
1239    }
1240  else
1241    {
1242      *username = apr_pstrdup(pool, session->proxy_username);
1243      *password = apr_pstrdup(pool, session->proxy_password);
1244
1245      session->proxy_auth_attempts++;
1246
1247      if (!session->proxy_username || session->proxy_auth_attempts > 4)
1248        {
1249          /* No more credentials. */
1250          (void) save_error(session,
1251                            svn_error_create(
1252                              SVN_ERR_AUTHN_FAILED, NULL,
1253                              _("Proxy authentication failed")));
1254          return SVN_ERR_AUTHN_FAILED;
1255        }
1256    }
1257
1258  handler->conn->last_status_code = code;
1259
1260  return APR_SUCCESS;
1261}
1262
1263/* Wait for HTTP response status and headers, and invoke HANDLER->
1264   response_handler() to carry out operation-specific processing.
1265   Afterwards, check for connection close.
1266
1267   SERF_STATUS allows returning errors to serf without creating a
1268   subversion error object.
1269   */
1270static svn_error_t *
1271handle_response(serf_request_t *request,
1272                serf_bucket_t *response,
1273                svn_ra_serf__handler_t *handler,
1274                apr_status_t *serf_status,
1275                apr_pool_t *scratch_pool)
1276{
1277  apr_status_t status;
1278  svn_error_t *err;
1279
1280  /* ### need to verify whether this already gets init'd on every
1281     ### successful exit. for an error-exit, it will (properly) be
1282     ### ignored by the caller.  */
1283  *serf_status = APR_SUCCESS;
1284
1285  if (!response)
1286    {
1287      /* Uh-oh. Our connection died.  */
1288      handler->scheduled = FALSE;
1289
1290      if (handler->response_error)
1291        {
1292          /* Give a handler chance to prevent request requeue. */
1293          SVN_ERR(handler->response_error(request, response, 0,
1294                                          handler->response_error_baton));
1295
1296          svn_ra_serf__request_create(handler);
1297        }
1298      /* Response error callback is not configured. Requeue another request
1299         for this handler only if we didn't started to process body.
1300         Return error otherwise. */
1301      else if (!handler->reading_body)
1302        {
1303          svn_ra_serf__request_create(handler);
1304        }
1305      else
1306        {
1307          return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1308                                    _("%s request on '%s' failed"),
1309                                   handler->method, handler->path);
1310        }
1311
1312      return SVN_NO_ERROR;
1313    }
1314
1315  /* If we're reading the body, then skip all this preparation.  */
1316  if (handler->reading_body)
1317    goto process_body;
1318
1319  /* Copy the Status-Line info into HANDLER, if we don't yet have it.  */
1320  if (handler->sline.version == 0)
1321    {
1322      serf_status_line sl;
1323
1324      status = serf_bucket_response_status(response, &sl);
1325      if (status != APR_SUCCESS)
1326        {
1327          /* The response line is not (yet) ready, or some other error.  */
1328          *serf_status = status;
1329          return SVN_NO_ERROR; /* Handled by serf */
1330        }
1331
1332      /* If we got APR_SUCCESS, then we should have Status-Line info.  */
1333      SVN_ERR_ASSERT(sl.version != 0);
1334
1335      handler->sline = sl;
1336      handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
1337
1338      /* HTTP/1.1? (or later)  */
1339      if (sl.version != SERF_HTTP_10)
1340        handler->session->http10 = FALSE;
1341
1342      if (sl.version >= SERF_HTTP_VERSION(2, 0)) {
1343        handler->session->http20 = TRUE;
1344      }
1345    }
1346
1347  /* Keep reading from the network until we've read all the headers.  */
1348  status = serf_bucket_response_wait_for_headers(response);
1349  if (status)
1350    {
1351      /* The typical "error" will be APR_EAGAIN, meaning that more input
1352         from the network is required to complete the reading of the
1353         headers.  */
1354      if (!APR_STATUS_IS_EOF(status))
1355        {
1356          /* Either the headers are not (yet) complete, or there really
1357             was an error.  */
1358          *serf_status = status;
1359          return SVN_NO_ERROR;
1360        }
1361
1362      /* wait_for_headers() will return EOF if there is no body in this
1363         response, or if we completely read the body. The latter is not
1364         true since we would have set READING_BODY to get the body read,
1365         and we would not be back to this code block.
1366
1367         It can also return EOF if we truly hit EOF while (say) processing
1368         the headers. aka Badness.  */
1369
1370      /* Cases where a lack of a response body (via EOF) is okay:
1371       *  - A HEAD request
1372       *  - 204/304 response
1373       *
1374       * Otherwise, if we get an EOF here, something went really wrong: either
1375       * the server closed on us early or we're reading too much.  Either way,
1376       * scream loudly.
1377       */
1378      if (strcmp(handler->method, "HEAD") != 0
1379          && handler->sline.code != 204
1380          && handler->sline.code != 304)
1381        {
1382          err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
1383                                  svn_ra_serf__wrap_err(status, NULL),
1384                                  _("Premature EOF seen from server"
1385                                    " (http status=%d)"),
1386                                  handler->sline.code);
1387
1388          /* In case anything else arrives... discard it.  */
1389          handler->discard_body = TRUE;
1390
1391          return err;
1392        }
1393    }
1394
1395  /* ... and set up the header fields in HANDLER.  */
1396  handler->location = response_get_location(response,
1397                                            handler->session->session_url_str,
1398                                            handler->handler_pool,
1399                                            scratch_pool);
1400
1401  /* On the last request, we failed authentication. We succeeded this time,
1402     so let's save away these credentials.  */
1403  if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
1404    {
1405      SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
1406                                        handler->session->pool));
1407      handler->session->auth_attempts = 0;
1408      handler->session->auth_state = NULL;
1409    }
1410  handler->conn->last_status_code = handler->sline.code;
1411
1412  if (handler->sline.code >= 400)
1413    {
1414      /* 405 Method Not allowed.
1415         408 Request Timeout
1416         409 Conflict: can indicate a hook error.
1417         5xx (Internal) Server error. */
1418      serf_bucket_t *hdrs;
1419      const char *val;
1420
1421      hdrs = serf_bucket_response_get_headers(response);
1422      val = serf_bucket_headers_get(hdrs, "Content-Type");
1423      if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1424        {
1425          svn_ra_serf__server_error_t *server_err;
1426
1427          SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1428                                                   FALSE,
1429                                                   handler->handler_pool,
1430                                                   handler->handler_pool));
1431
1432          handler->server_error = server_err;
1433        }
1434      else
1435        {
1436          handler->discard_body = TRUE;
1437        }
1438    }
1439  else if (handler->sline.code <= 199)
1440    {
1441      handler->discard_body = TRUE;
1442    }
1443
1444  /* Stop processing the above, on every packet arrival.  */
1445  handler->reading_body = TRUE;
1446
1447 process_body:
1448
1449  /* A client cert file password was obtained and worked (any HTTP
1450     response means that the SSL connection was established.) */
1451  if (handler->conn->ssl_client_pw_auth_state)
1452    {
1453      SVN_ERR(svn_auth_save_credentials(handler->conn->ssl_client_pw_auth_state,
1454                                        handler->session->pool));
1455      handler->conn->ssl_client_pw_auth_state = NULL;
1456    }
1457  if (handler->conn->ssl_client_auth_state)
1458    {
1459      /* The cert file provider doesn't have any code to save creds so
1460         this is currently a no-op. */
1461      SVN_ERR(svn_auth_save_credentials(handler->conn->ssl_client_auth_state,
1462                                        handler->session->pool));
1463      handler->conn->ssl_client_auth_state = NULL;
1464    }
1465
1466  /* We've been instructed to ignore the body. Drain whatever is present.  */
1467  if (handler->discard_body)
1468    {
1469      *serf_status = drain_bucket(response);
1470
1471      return SVN_NO_ERROR;
1472    }
1473
1474  /* If we are supposed to parse the body as a server_error, then do
1475     that now.  */
1476  if (handler->server_error != NULL)
1477    {
1478      return svn_error_trace(
1479                svn_ra_serf__handle_server_error(handler->server_error,
1480                                                 handler,
1481                                                 request, response,
1482                                                 serf_status,
1483                                                 scratch_pool));
1484    }
1485
1486  /* Pass the body along to the registered response handler.  */
1487  err = handler->response_handler(request, response,
1488                                  handler->response_baton,
1489                                  scratch_pool);
1490
1491  if (err
1492      && (!SERF_BUCKET_READ_ERROR(err->apr_err)
1493          || APR_STATUS_IS_ECONNRESET(err->apr_err)
1494          || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
1495    {
1496      /* These errors are special cased in serf
1497         ### We hope no handler returns these by accident. */
1498      *serf_status = err->apr_err;
1499      svn_error_clear(err);
1500      return SVN_NO_ERROR;
1501    }
1502
1503  return svn_error_trace(err);
1504}
1505
1506
1507/* Implements serf_response_handler_t for handle_response. Storing
1508   errors in handler->session->pending_error if appropriate. */
1509static apr_status_t
1510handle_response_cb(serf_request_t *request,
1511                   serf_bucket_t *response,
1512                   void *baton,
1513                   apr_pool_t *response_pool)
1514{
1515  svn_ra_serf__handler_t *handler = baton;
1516  svn_error_t *err;
1517  apr_status_t inner_status;
1518  apr_status_t outer_status;
1519  apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */
1520
1521  err = svn_error_trace(handle_response(request, response,
1522                                        handler, &inner_status,
1523                                        scratch_pool));
1524
1525  /* Select the right status value to return.  */
1526  outer_status = save_error(handler->session, err);
1527  if (!outer_status)
1528    outer_status = inner_status;
1529
1530  /* Make sure the DONE flag is set properly and requests are cleaned up. */
1531  if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
1532    {
1533      svn_ra_serf__session_t *sess = handler->session;
1534      handler->done = TRUE;
1535      handler->scheduled = FALSE;
1536      outer_status = APR_EOF;
1537
1538      /* We use a cached handler->session here to allow handler to free the
1539         memory containing the handler */
1540      save_error(sess,
1541                 handler->done_delegate(request, handler->done_delegate_baton,
1542                                        scratch_pool));
1543    }
1544  else if (SERF_BUCKET_READ_ERROR(outer_status)
1545           && handler->session->pending_error)
1546    {
1547      handler->discard_body = TRUE; /* Discard further data */
1548      handler->done = TRUE; /* Mark as done */
1549      /* handler->scheduled is still TRUE, as we still expect data.
1550         If we would return an error outer-status the connection
1551         would have to be restarted. With scheduled still TRUE
1552         destroying the handler's pool will still reset the
1553         connection, avoiding the posibility of returning
1554         an error for this handler when a new request is
1555         scheduled. */
1556      outer_status = APR_EAGAIN; /* Exit context loop */
1557    }
1558
1559  return outer_status;
1560}
1561
1562/* Perform basic request setup, with special handling for HEAD requests,
1563   and finer-grained callbacks invoked (if non-NULL) to produce the request
1564   headers and body. */
1565static svn_error_t *
1566setup_request(serf_request_t *request,
1567              svn_ra_serf__handler_t *handler,
1568              serf_bucket_t **req_bkt,
1569              apr_pool_t *request_pool,
1570              apr_pool_t *scratch_pool)
1571{
1572  serf_bucket_t *body_bkt;
1573  serf_bucket_t *headers_bkt;
1574  const char *accept_encoding;
1575
1576  if (handler->body_delegate)
1577    {
1578      serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
1579
1580      SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
1581                                     bkt_alloc, request_pool, scratch_pool));
1582    }
1583  else
1584    {
1585      body_bkt = NULL;
1586    }
1587
1588  if (handler->custom_accept_encoding)
1589    {
1590      accept_encoding = NULL;
1591    }
1592  else if (handler->session->using_compression != svn_tristate_false)
1593    {
1594      /* Accept gzip compression if enabled. */
1595      accept_encoding = "gzip";
1596    }
1597  else
1598    {
1599      accept_encoding = NULL;
1600    }
1601
1602  SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
1603                         handler->session, handler->method, handler->path,
1604                         body_bkt, handler->body_type, accept_encoding,
1605                         !handler->no_dav_headers, request_pool,
1606                         scratch_pool));
1607
1608  if (handler->header_delegate)
1609    {
1610      SVN_ERR(handler->header_delegate(headers_bkt,
1611                                       handler->header_delegate_baton,
1612                                       request_pool, scratch_pool));
1613    }
1614
1615  return SVN_NO_ERROR;
1616}
1617
1618/* Implements the serf_request_setup_t interface (which sets up both a
1619   request and its response handler callback). Handles errors for
1620   setup_request_cb */
1621static apr_status_t
1622setup_request_cb(serf_request_t *request,
1623              void *setup_baton,
1624              serf_bucket_t **req_bkt,
1625              serf_response_acceptor_t *acceptor,
1626              void **acceptor_baton,
1627              serf_response_handler_t *s_handler,
1628              void **s_handler_baton,
1629              apr_pool_t *request_pool)
1630{
1631  svn_ra_serf__handler_t *handler = setup_baton;
1632  apr_pool_t *scratch_pool;
1633  svn_error_t *err;
1634
1635  /* Construct a scratch_pool? serf gives us a pool that will live for
1636     the duration of the request. But requests are retried in some cases */
1637  scratch_pool = svn_pool_create(request_pool);
1638
1639  if (strcmp(handler->method, "HEAD") == 0)
1640    *acceptor = accept_head;
1641  else
1642    *acceptor = accept_response;
1643  *acceptor_baton = handler;
1644
1645  *s_handler = handle_response_cb;
1646  *s_handler_baton = handler;
1647
1648  err = svn_error_trace(setup_request(request, handler, req_bkt,
1649                                      request_pool, scratch_pool));
1650
1651  svn_pool_destroy(scratch_pool);
1652  return save_error(handler->session, err);
1653}
1654
1655void
1656svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
1657{
1658  SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL
1659                           && !handler->scheduled);
1660
1661  /* In case HANDLER is re-queued, reset the various transient fields. */
1662  handler->done = FALSE;
1663  handler->server_error = NULL;
1664  handler->sline.version = 0;
1665  handler->location = NULL;
1666  handler->reading_body = FALSE;
1667  handler->discard_body = FALSE;
1668  handler->scheduled = TRUE;
1669
1670  /* Keeping track of the returned request object would be nice, but doesn't
1671     work the way we would expect in ra_serf..
1672
1673     Serf sometimes creates a new request for us (and destroys the old one)
1674     without telling, like when authentication failed (401/407 response.
1675
1676     We 'just' trust serf to do the right thing and expect it to tell us
1677     when the state of the request changes.
1678
1679     ### I fixed a request leak in serf in r2258 on auth failures.
1680   */
1681  (void) serf_connection_request_create(handler->conn->conn,
1682                                        setup_request_cb, handler);
1683}
1684
1685
1686svn_error_t *
1687svn_ra_serf__discover_vcc(const char **vcc_url,
1688                          svn_ra_serf__session_t *session,
1689                          apr_pool_t *scratch_pool)
1690{
1691  const char *path;
1692  const char *relative_path;
1693  const char *uuid;
1694
1695  /* If we've already got the information our caller seeks, just return it.  */
1696  if (session->vcc_url && session->repos_root_str)
1697    {
1698      *vcc_url = session->vcc_url;
1699      return SVN_NO_ERROR;
1700    }
1701
1702  path = session->session_url.path;
1703  *vcc_url = NULL;
1704  uuid = NULL;
1705
1706  do
1707    {
1708      apr_hash_t *props;
1709      svn_error_t *err;
1710
1711      err = svn_ra_serf__fetch_node_props(&props, session,
1712                                          path, SVN_INVALID_REVNUM,
1713                                          base_props,
1714                                          scratch_pool, scratch_pool);
1715      if (! err)
1716        {
1717          apr_hash_t *ns_props;
1718
1719          ns_props = apr_hash_get(props, "DAV:", 4);
1720          *vcc_url = svn_prop_get_value(ns_props,
1721                                        "version-controlled-configuration");
1722
1723          ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
1724          relative_path = svn_prop_get_value(ns_props,
1725                                             "baseline-relative-path");
1726          uuid = svn_prop_get_value(ns_props, "repository-uuid");
1727          break;
1728        }
1729      else
1730        {
1731          if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
1732              (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
1733            {
1734              return svn_error_trace(err);  /* found a _real_ error */
1735            }
1736          else
1737            {
1738              /* This happens when the file is missing in HEAD. */
1739              svn_error_clear(err);
1740
1741              /* Okay, strip off a component from PATH. */
1742              path = svn_urlpath__dirname(path, scratch_pool);
1743            }
1744        }
1745    }
1746  while ((path[0] != '\0')
1747         && (! (path[0] == '/' && path[1] == '\0')));
1748
1749  if (!*vcc_url)
1750    {
1751      return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1752                              _("The PROPFIND response did not include the "
1753                                "requested version-controlled-configuration "
1754                                "value"));
1755    }
1756
1757  /* Store our VCC in our cache. */
1758  if (!session->vcc_url)
1759    {
1760      session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
1761    }
1762
1763  /* Update our cached repository root URL. */
1764  if (!session->repos_root_str)
1765    {
1766      svn_stringbuf_t *url_buf;
1767
1768      url_buf = svn_stringbuf_create(path, scratch_pool);
1769
1770      svn_path_remove_components(url_buf,
1771                                 svn_path_component_count(relative_path));
1772
1773      /* Now recreate the root_url. */
1774      session->repos_root = session->session_url;
1775      session->repos_root.path =
1776        (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
1777      session->repos_root_str =
1778        svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
1779                                                  &session->repos_root, 0),
1780                                  session->pool);
1781    }
1782
1783  /* Store the repository UUID in the cache. */
1784  if (!session->uuid)
1785    {
1786      session->uuid = apr_pstrdup(session->pool, uuid);
1787    }
1788
1789  return SVN_NO_ERROR;
1790}
1791
1792svn_error_t *
1793svn_ra_serf__get_relative_path(const char **rel_path,
1794                               const char *orig_path,
1795                               svn_ra_serf__session_t *session,
1796                               apr_pool_t *pool)
1797{
1798  const char *decoded_root, *decoded_orig;
1799
1800  if (! session->repos_root.path)
1801    {
1802      const char *vcc_url;
1803
1804      /* This should only happen if we haven't detected HTTP v2
1805         support from the server.  */
1806      assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1807
1808      /* We don't actually care about the VCC_URL, but this API
1809         promises to populate the session's root-url cache, and that's
1810         what we really want. */
1811      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
1812                                        pool));
1813    }
1814
1815  decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
1816  decoded_orig = svn_path_uri_decode(orig_path, pool);
1817  *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
1818  SVN_ERR_ASSERT(*rel_path != NULL);
1819  return SVN_NO_ERROR;
1820}
1821
1822svn_error_t *
1823svn_ra_serf__report_resource(const char **report_target,
1824                             svn_ra_serf__session_t *session,
1825                             apr_pool_t *pool)
1826{
1827  /* If we have HTTP v2 support, we want to report against the 'me'
1828     resource. */
1829  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
1830    *report_target = apr_pstrdup(pool, session->me_resource);
1831
1832  /* Otherwise, we'll use the default VCC. */
1833  else
1834    SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool));
1835
1836  return SVN_NO_ERROR;
1837}
1838
1839svn_error_t *
1840svn_ra_serf__error_on_status(serf_status_line sline,
1841                             const char *path,
1842                             const char *location)
1843{
1844  switch(sline.code)
1845    {
1846      case 301:
1847      case 302:
1848      case 303:
1849      case 307:
1850      case 308:
1851        return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
1852                                 (sline.code == 301)
1853                                  ? _("Repository moved permanently to '%s'")
1854                                  : _("Repository moved temporarily to '%s'"),
1855                                 location);
1856      case 403:
1857        return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
1858                                 _("Access to '%s' forbidden"), path);
1859
1860      case 404:
1861        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1862                                 _("'%s' path not found"), path);
1863      case 405:
1864        return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1865                                 _("HTTP method is not allowed on '%s'"),
1866                                 path);
1867      case 409:
1868        return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1869                                 _("'%s' conflicts"), path);
1870      case 412:
1871        return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL,
1872                                 _("Precondition on '%s' failed"), path);
1873      case 423:
1874        return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
1875                                 _("'%s': no lock token available"), path);
1876
1877      case 411:
1878        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1879                    _("DAV request failed: 411 Content length required. The "
1880                      "server or an intermediate proxy does not accept "
1881                      "chunked encoding. Try setting 'http-chunked-requests' "
1882                      "to 'auto' or 'no' in your client configuration."));
1883      case 500:
1884        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1885                                 _("Unexpected server error %d '%s' on '%s'"),
1886                                 sline.code, sline.reason, path);
1887      case 501:
1888        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1889                                 _("The requested feature is not supported by "
1890                                   "'%s'"), path);
1891    }
1892
1893  if (sline.code >= 300 || sline.code <= 199)
1894    return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1895                             _("Unexpected HTTP status %d '%s' on '%s'"),
1896                             sline.code, sline.reason, path);
1897
1898  return SVN_NO_ERROR;
1899}
1900
1901svn_error_t *
1902svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler)
1903{
1904  /* Is it a standard error status? */
1905  if (handler->sline.code != 405)
1906    SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
1907                                         handler->path,
1908                                         handler->location));
1909
1910  switch (handler->sline.code)
1911    {
1912      case 201:
1913        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1914                                 _("Path '%s' unexpectedly created"),
1915                                 handler->path);
1916      case 204:
1917        return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1918                                 _("Path '%s' already exists"),
1919                                 handler->path);
1920
1921      case 405:
1922        return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1923                                 _("The HTTP method '%s' is not allowed"
1924                                   " on '%s'"),
1925                                 handler->method, handler->path);
1926      default:
1927        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1928                                 _("Unexpected HTTP status %d '%s' on '%s' "
1929                                   "request to '%s'"),
1930                                 handler->sline.code, handler->sline.reason,
1931                                 handler->method, handler->path);
1932    }
1933}
1934
1935svn_error_t *
1936svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
1937                                    svn_delta_shim_callbacks_t *callbacks)
1938{
1939  svn_ra_serf__session_t *session = ra_session->priv;
1940
1941  session->shim_callbacks = callbacks;
1942  return SVN_NO_ERROR;
1943}
1944
1945/* Shared/standard done_delegate handler */
1946static svn_error_t *
1947response_done(serf_request_t *request,
1948              void *handler_baton,
1949              apr_pool_t *scratch_pool)
1950{
1951  svn_ra_serf__handler_t *handler = handler_baton;
1952
1953  assert(handler->done);
1954
1955  if (handler->no_fail_on_http_failure_status)
1956    return SVN_NO_ERROR;
1957
1958  if (handler->server_error)
1959    return svn_ra_serf__server_error_create(handler, scratch_pool);
1960
1961  if (handler->sline.code >= 400 || handler->sline.code <= 199)
1962    {
1963      return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1964    }
1965
1966  if ((handler->sline.code >= 300 && handler->sline.code < 399)
1967      && !handler->no_fail_on_http_redirect_status)
1968    {
1969      return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1970    }
1971
1972  return SVN_NO_ERROR;
1973}
1974
1975/* Pool cleanup handler for request handlers.
1976
1977   If a serf context run stops for some outside error, like when the user
1978   cancels a request via ^C in the context loop, the handler is still
1979   registered in the serf context. With the pool cleanup there would be
1980   handlers registered in no freed memory.
1981
1982   This fallback kills the connection for this case, which will make serf
1983   unregister any outstanding requests on it. */
1984static apr_status_t
1985handler_cleanup(void *baton)
1986{
1987  svn_ra_serf__handler_t *handler = baton;
1988  if (handler->scheduled)
1989    {
1990      svn_ra_serf__unschedule_handler(handler);
1991    }
1992
1993  return APR_SUCCESS;
1994}
1995
1996svn_ra_serf__handler_t *
1997svn_ra_serf__create_handler(svn_ra_serf__session_t *session,
1998                            apr_pool_t *result_pool)
1999{
2000  svn_ra_serf__handler_t *handler;
2001
2002  handler = apr_pcalloc(result_pool, sizeof(*handler));
2003  handler->handler_pool = result_pool;
2004
2005  apr_pool_cleanup_register(result_pool, handler, handler_cleanup,
2006                            apr_pool_cleanup_null);
2007
2008  handler->session = session;
2009  handler->conn = session->conns[0];
2010
2011  /* Setup the default done handler, to handle server errors */
2012  handler->done_delegate_baton = handler;
2013  handler->done_delegate = response_done;
2014
2015  return handler;
2016}
2017
2018svn_error_t *
2019svn_ra_serf__uri_parse(apr_uri_t *uri,
2020                       const char *url_str,
2021                       apr_pool_t *result_pool)
2022{
2023  apr_status_t status;
2024
2025  status = apr_uri_parse(result_pool, url_str, uri);
2026  if (status)
2027    {
2028      /* Do not use returned error status in error message because currently
2029         apr_uri_parse() returns APR_EGENERAL for all parsing errors. */
2030      return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2031                               _("Illegal URL '%s'"),
2032                               url_str);
2033    }
2034
2035  /* Depending the version of apr-util in use, for root paths uri.path
2036     will be NULL or "", where serf requires "/". */
2037  if (uri->path == NULL || uri->path[0] == '\0')
2038    {
2039      uri->path = apr_pstrdup(result_pool, "/");
2040    }
2041
2042  return SVN_NO_ERROR;
2043}
2044
2045void
2046svn_ra_serf__setup_svndiff_accept_encoding(serf_bucket_t *headers,
2047                                           svn_ra_serf__session_t *session)
2048{
2049  if (session->using_compression == svn_tristate_false)
2050    {
2051      /* Don't advertise support for compressed svndiff formats if
2052         compression is disabled. */
2053      serf_bucket_headers_setn(
2054        headers, "Accept-Encoding", "svndiff");
2055    }
2056  else if (session->using_compression == svn_tristate_unknown &&
2057           svn_ra_serf__is_low_latency_connection(session))
2058    {
2059      /* With http-compression=auto, advertise that we prefer svndiff2
2060         to svndiff1 with a low latency connection (assuming the underlying
2061         network has high bandwidth), as it is faster and in this case, we
2062         don't care about worse compression ratio. */
2063      serf_bucket_headers_setn(
2064        headers, "Accept-Encoding",
2065        "gzip,svndiff2;q=0.9,svndiff1;q=0.8,svndiff;q=0.7");
2066    }
2067  else
2068    {
2069      /* Otherwise, advertise that we prefer svndiff1 over svndiff2.
2070         svndiff2 is not a reasonable substitute for svndiff1 with default
2071         compression level, because, while it is faster, it also gives worse
2072         compression ratio.  While we can use svndiff2 in some cases (see
2073         above), we can't do this generally. */
2074      serf_bucket_headers_setn(
2075        headers, "Accept-Encoding",
2076        "gzip,svndiff1;q=0.9,svndiff2;q=0.8,svndiff;q=0.7");
2077    }
2078}
2079
2080svn_boolean_t
2081svn_ra_serf__is_low_latency_connection(svn_ra_serf__session_t *session)
2082{
2083  return session->conn_latency >= 0 &&
2084         session->conn_latency < apr_time_from_msec(5);
2085}
2086
2087apr_array_header_t *
2088svn_ra_serf__get_dirent_props(apr_uint32_t dirent_fields,
2089                              svn_ra_serf__session_t *session,
2090                              apr_pool_t *result_pool)
2091{
2092  svn_ra_serf__dav_props_t *prop;
2093  apr_array_header_t *props = apr_array_make
2094    (result_pool, 7, sizeof(svn_ra_serf__dav_props_t));
2095
2096  if (session->supports_deadprop_count != svn_tristate_false
2097      || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
2098    {
2099      if (dirent_fields & SVN_DIRENT_KIND)
2100        {
2101          prop = apr_array_push(props);
2102          prop->xmlns = "DAV:";
2103          prop->name = "resourcetype";
2104        }
2105
2106      if (dirent_fields & SVN_DIRENT_SIZE)
2107        {
2108          prop = apr_array_push(props);
2109          prop->xmlns = "DAV:";
2110          prop->name = "getcontentlength";
2111        }
2112
2113      if (dirent_fields & SVN_DIRENT_HAS_PROPS)
2114        {
2115          prop = apr_array_push(props);
2116          prop->xmlns = SVN_DAV_PROP_NS_DAV;
2117          prop->name = "deadprop-count";
2118        }
2119
2120      if (dirent_fields & SVN_DIRENT_CREATED_REV)
2121        {
2122          svn_ra_serf__dav_props_t *p = apr_array_push(props);
2123          p->xmlns = "DAV:";
2124          p->name = SVN_DAV__VERSION_NAME;
2125        }
2126
2127      if (dirent_fields & SVN_DIRENT_TIME)
2128        {
2129          prop = apr_array_push(props);
2130          prop->xmlns = "DAV:";
2131          prop->name = SVN_DAV__CREATIONDATE;
2132        }
2133
2134      if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
2135        {
2136          prop = apr_array_push(props);
2137          prop->xmlns = "DAV:";
2138          prop->name = "creator-displayname";
2139        }
2140    }
2141  else
2142    {
2143      /* We found an old subversion server that can't handle
2144         the deadprop-count property in the way we expect.
2145
2146         The neon behavior is to retrieve all properties in this case */
2147      prop = apr_array_push(props);
2148      prop->xmlns = "DAV:";
2149      prop->name = "allprop";
2150    }
2151
2152  return props;
2153}
2154
2155