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