155682Smarkm/*
2233294Sstas * util.c : serf utility routines for ra_serf
3233294Sstas *
4233294Sstas * ====================================================================
555682Smarkm *    Licensed to the Apache Software Foundation (ASF) under one
6233294Sstas *    or more contributor license agreements.  See the NOTICE file
7233294Sstas *    distributed with this work for additional information
8233294Sstas *    regarding copyright ownership.  The ASF licenses this file
955682Smarkm *    to you under the Apache License, Version 2.0 (the
10233294Sstas *    "License"); you may not use this file except in compliance
11233294Sstas *    with the License.  You may obtain a copy of the License at
1255682Smarkm *
13233294Sstas *      http://www.apache.org/licenses/LICENSE-2.0
14233294Sstas *
15233294Sstas *    Unless required by applicable law or agreed to in writing,
1655682Smarkm *    software distributed under the License is distributed on an
17233294Sstas *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18233294Sstas *    KIND, either express or implied.  See the License for the
19233294Sstas *    specific language governing permissions and limitations
2055682Smarkm *    under the License.
21233294Sstas * ====================================================================
22233294Sstas */
23233294Sstas
24233294Sstas
25233294Sstas
26233294Sstas#include <assert.h>
27233294Sstas
28233294Sstas#define APR_WANT_STRFUNC
29233294Sstas#include <apr.h>
30233294Sstas#include <apr_want.h>
31233294Sstas
3255682Smarkm#include <serf.h>
3355682Smarkm#include <serf_bucket_types.h>
3455682Smarkm
3555682Smarkm#include "svn_hash.h"
3655682Smarkm#include "svn_dirent_uri.h"
3755682Smarkm#include "svn_path.h"
3855682Smarkm#include "svn_private_config.h"
3955682Smarkm#include "svn_string.h"
4055682Smarkm#include "svn_props.h"
4155682Smarkm#include "svn_dirent_uri.h"
4255682Smarkm
43102644Snectar#include "../libsvn_ra/ra_loader.h"
44102644Snectar#include "private/svn_dep_compat.h"
4555682Smarkm#include "private/svn_fspath.h"
4655682Smarkm#include "private/svn_auth_private.h"
47233294Sstas#include "private/svn_cert.h"
48102644Snectar
4955682Smarkm#include "ra_serf.h"
5090926Snectar
5190926Snectarstatic const apr_uint32_t serf_failure_map[][2] =
5290926Snectar{
5390926Snectar  { SERF_SSL_CERT_NOTYETVALID,   SVN_AUTH_SSL_NOTYETVALID },
54233294Sstas  { SERF_SSL_CERT_EXPIRED,       SVN_AUTH_SSL_EXPIRED },
55233294Sstas  { SERF_SSL_CERT_SELF_SIGNED,   SVN_AUTH_SSL_UNKNOWNCA },
5655682Smarkm  { SERF_SSL_CERT_UNKNOWNCA,     SVN_AUTH_SSL_UNKNOWNCA }
5755682Smarkm};
5855682Smarkm
5955682Smarkm/* Return a Subversion failure mask based on FAILURES, a serf SSL
6055682Smarkm   failure mask.  If anything in FAILURES is not directly mappable to
6155682Smarkm   Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */
6255682Smarkmstatic apr_uint32_t
6355682Smarkmssl_convert_serf_failures(int failures)
6455682Smarkm{
65178825Sdfr  apr_uint32_t svn_failures = 0;
6655682Smarkm  apr_size_t i;
6755682Smarkm
6855682Smarkm  for (i = 0; i < sizeof(serf_failure_map) / (2 * sizeof(apr_uint32_t)); ++i)
69178825Sdfr    {
7055682Smarkm      if (failures & serf_failure_map[i][0])
7155682Smarkm        {
7255682Smarkm          svn_failures |= serf_failure_map[i][1];
7355682Smarkm          failures &= ~serf_failure_map[i][0];
7455682Smarkm        }
7555682Smarkm    }
76178825Sdfr
7755682Smarkm  /* Map any remaining failure bits to our OTHER bit. */
78178825Sdfr  if (failures)
7955682Smarkm    {
8055682Smarkm      svn_failures |= SVN_AUTH_SSL_OTHER;
8155682Smarkm    }
8255682Smarkm
8355682Smarkm  return svn_failures;
8455682Smarkm}
85102644Snectar
8655682Smarkm
8755682Smarkmstatic apr_status_t
88102644Snectarsave_error(svn_ra_serf__session_t *session,
8955682Smarkm           svn_error_t *err)
90102644Snectar{
91102644Snectar  if (err || session->pending_error)
92102644Snectar    {
93102644Snectar      session->pending_error = svn_error_compose_create(
94102644Snectar                                  session->pending_error,
95102644Snectar                                  err);
9655682Smarkm      return session->pending_error->apr_err;
9755682Smarkm    }
9855682Smarkm
9955682Smarkm  return APR_SUCCESS;
100102644Snectar}
101102644Snectar
102102644Snectar
10355682Smarkm/* Construct the realmstring, e.g. https://svn.collab.net:443. */
104102644Snectarstatic const char *
10555682Smarkmconstruct_realm(svn_ra_serf__session_t *session,
106102644Snectar                apr_pool_t *pool)
107102644Snectar{
108102644Snectar  const char *realm;
109102644Snectar  apr_port_t port;
110102644Snectar
111102644Snectar  if (session->session_url.port_str)
11255682Smarkm    {
11355682Smarkm      port = session->session_url.port;
11455682Smarkm    }
11555682Smarkm  else
11655682Smarkm    {
11755682Smarkm      port = apr_uri_port_of_scheme(session->session_url.scheme);
11855682Smarkm    }
11955682Smarkm
12055682Smarkm  realm = apr_psprintf(pool, "%s://%s:%d",
12155682Smarkm                       session->session_url.scheme,
12255682Smarkm                       session->session_url.hostname,
12355682Smarkm                       port);
12455682Smarkm
12555682Smarkm  return realm;
12655682Smarkm}
12755682Smarkm
12855682Smarkm/* Convert a hash table containing the fields (as documented in X.509) of an
12955682Smarkm   organisation to a string ORG, allocated in POOL. ORG is as returned by
13055682Smarkm   serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
13155682Smarkmstatic char *
132178825Sdfrconvert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
13355682Smarkm{
134178825Sdfr  const char *cn = svn_hash_gets(org, "CN");
13555682Smarkm  const char *org_unit = svn_hash_gets(org, "OU");
13655682Smarkm  const char *org_name = svn_hash_gets(org, "O");
13755682Smarkm  const char *locality = svn_hash_gets(org, "L");
13855682Smarkm  const char *state = svn_hash_gets(org, "ST");
13955682Smarkm  const char *country = svn_hash_gets(org, "C");
140233294Sstas  const char *email = svn_hash_gets(org, "E");
141233294Sstas  svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
142233294Sstas
143233294Sstas  if (cn)
144233294Sstas    {
145233294Sstas      svn_stringbuf_appendcstr(buf, cn);
146233294Sstas      svn_stringbuf_appendcstr(buf, ", ");
147233294Sstas    }
148233294Sstas
149233294Sstas  if (org_unit)
150233294Sstas    {
15155682Smarkm      svn_stringbuf_appendcstr(buf, org_unit);
152102644Snectar      svn_stringbuf_appendcstr(buf, ", ");
15355682Smarkm    }
154102644Snectar
15555682Smarkm  if (org_name)
156102644Snectar    {
157102644Snectar      svn_stringbuf_appendcstr(buf, org_name);
158102644Snectar      svn_stringbuf_appendcstr(buf, ", ");
159102644Snectar    }
160102644Snectar
161102644Snectar  if (locality)
16255682Smarkm    {
16355682Smarkm      svn_stringbuf_appendcstr(buf, locality);
16455682Smarkm      svn_stringbuf_appendcstr(buf, ", ");
16555682Smarkm    }
16655682Smarkm
16755682Smarkm  if (state)
16855682Smarkm    {
16955682Smarkm      svn_stringbuf_appendcstr(buf, state);
17055682Smarkm      svn_stringbuf_appendcstr(buf, ", ");
17155682Smarkm    }
17255682Smarkm
17355682Smarkm  if (country)
17455682Smarkm    {
17590926Snectar      svn_stringbuf_appendcstr(buf, country);
17655682Smarkm      svn_stringbuf_appendcstr(buf, ", ");
17755682Smarkm    }
17855682Smarkm
17955682Smarkm  /* Chop ', ' if any. */
18055682Smarkm  svn_stringbuf_chop(buf, 2);
18155682Smarkm
18255682Smarkm  if (email)
18355682Smarkm    {
18455682Smarkm      svn_stringbuf_appendcstr(buf, "(");
18555682Smarkm      svn_stringbuf_appendcstr(buf, email);
18655682Smarkm      svn_stringbuf_appendcstr(buf, ")");
18755682Smarkm    }
18855682Smarkm
18955682Smarkm  return buf->data;
19055682Smarkm}
19155682Smarkm
19255682Smarkmstatic void append_reason(svn_stringbuf_t *errmsg, const char *reason, int *reasons)
19355682Smarkm{
19455682Smarkm  if (*reasons < 1)
19555682Smarkm    svn_stringbuf_appendcstr(errmsg, _(": "));
19655682Smarkm  else
19755682Smarkm    svn_stringbuf_appendcstr(errmsg, _(", "));
19855682Smarkm  svn_stringbuf_appendcstr(errmsg, reason);
199178825Sdfr  (*reasons)++;
200178825Sdfr}
201178825Sdfr
202178825Sdfr/* This function is called on receiving a ssl certificate of a server when
203178825Sdfr   opening a https connection. It allows Subversion to override the initial
204178825Sdfr   validation done by serf.
205178825Sdfr   Serf provides us the @a baton as provided in the call to
206178825Sdfr   serf_ssl_server_cert_callback_set. The result of serf's initial validation
207233294Sstas   of the certificate @a CERT is returned as a bitmask in FAILURES. */
208233294Sstasstatic svn_error_t *
209178825Sdfrssl_server_cert(void *baton, int failures,
210178825Sdfr                const serf_ssl_certificate_t *cert,
211178825Sdfr                apr_pool_t *scratch_pool)
212178825Sdfr{
213178825Sdfr  svn_ra_serf__connection_t *conn = baton;
214178825Sdfr  svn_auth_ssl_server_cert_info_t cert_info;
215178825Sdfr  svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
216178825Sdfr  svn_auth_iterstate_t *state;
217178825Sdfr  const char *realmstring;
218178825Sdfr  apr_uint32_t svn_failures;
219178825Sdfr  apr_hash_t *issuer;
220178825Sdfr  apr_hash_t *subject = NULL;
221178825Sdfr  apr_hash_t *serf_cert = NULL;
222178825Sdfr  void *creds;
223178825Sdfr
224178825Sdfr  svn_failures = (ssl_convert_serf_failures(failures)
225178825Sdfr      | conn->server_cert_failures);
226178825Sdfr
227178825Sdfr  if (serf_ssl_cert_depth(cert) == 0)
228178825Sdfr    {
229178825Sdfr      /* If the depth is 0, the hostname must match the certificate.
230178825Sdfr
231178825Sdfr      ### This should really be handled by serf, which should pass an error
232178825Sdfr          for this case, but that has backwards compatibility issues. */
233178825Sdfr      apr_array_header_t *san;
23455682Smarkm      svn_boolean_t found_matching_hostname = FALSE;
23555682Smarkm      svn_string_t *actual_hostname =
23655682Smarkm          svn_string_create(conn->session->session_url.hostname, scratch_pool);
23755682Smarkm
23855682Smarkm      serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
23955682Smarkm
24055682Smarkm      san = svn_hash_gets(serf_cert, "subjectAltName");
24155682Smarkm      /* Match server certificate CN with the hostname of the server iff
24255682Smarkm       * we didn't find any subjectAltName fields and try to match them.
24355682Smarkm       * Per RFC 2818 they are authoritative if present and CommonName
24455682Smarkm       * should be ignored.  NOTE: This isn't 100% correct since serf
24555682Smarkm       * only loads the subjectAltName hash with dNSNames, technically
24655682Smarkm       * we should ignore the CommonName if any subjectAltName entry
24755682Smarkm       * exists even if it is one we don't support. */
24855682Smarkm      if (san && san->nelts > 0)
24955682Smarkm        {
25055682Smarkm          int i;
25155682Smarkm          for (i = 0; i < san->nelts; i++)
25255682Smarkm            {
25355682Smarkm              const char *s = APR_ARRAY_IDX(san, i, const char*);
25455682Smarkm              svn_string_t *cert_hostname = svn_string_create(s, scratch_pool);
25555682Smarkm
25655682Smarkm              if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
25755682Smarkm                {
25855682Smarkm                  found_matching_hostname = TRUE;
25955682Smarkm                  break;
26055682Smarkm                }
26155682Smarkm            }
26255682Smarkm        }
26355682Smarkm      else
26455682Smarkm        {
26555682Smarkm          const char *hostname = NULL;
26655682Smarkm
26755682Smarkm          subject = serf_ssl_cert_subject(cert, scratch_pool);
26855682Smarkm
26955682Smarkm          if (subject)
27055682Smarkm            hostname = svn_hash_gets(subject, "CN");
27155682Smarkm
27255682Smarkm          if (hostname)
27355682Smarkm            {
27455682Smarkm              svn_string_t *cert_hostname = svn_string_create(hostname,
27555682Smarkm                                                              scratch_pool);
27655682Smarkm
27755682Smarkm              if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
278102644Snectar                {
27955682Smarkm                  found_matching_hostname = TRUE;
28055682Smarkm                }
281102644Snectar            }
28255682Smarkm        }
283102644Snectar
284102644Snectar      if (!found_matching_hostname)
285102644Snectar        svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
286102644Snectar    }
287102644Snectar
288102644Snectar  if (!svn_failures)
28955682Smarkm    return SVN_NO_ERROR;
29055682Smarkm
29155682Smarkm  /* Extract the info from the certificate */
29255682Smarkm  if (! subject)
29355682Smarkm    subject = serf_ssl_cert_subject(cert, scratch_pool);
294102644Snectar  issuer = serf_ssl_cert_issuer(cert, scratch_pool);
29555682Smarkm  if (! serf_cert)
29655682Smarkm    serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
297102644Snectar
29855682Smarkm  cert_info.hostname = svn_hash_gets(subject, "CN");
299102644Snectar  cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
300102644Snectar  if (! cert_info.fingerprint)
301102644Snectar    cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
302102644Snectar  cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
303102644Snectar  if (! cert_info.valid_from)
304102644Snectar    cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
30555682Smarkm  cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
30655682Smarkm  if (! cert_info.valid_until)
30755682Smarkm    cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
30855682Smarkm  cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
30955682Smarkm  cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
31055682Smarkm
31155682Smarkm  /* Handle any non-server certs. */
31255682Smarkm  if (serf_ssl_cert_depth(cert) > 0)
31355682Smarkm    {
31455682Smarkm      svn_error_t *err;
31555682Smarkm
316233294Sstas      svn_auth_set_parameter(conn->session->auth_baton,
31755682Smarkm                             SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
31855682Smarkm                             &cert_info);
31955682Smarkm
32055682Smarkm      svn_auth_set_parameter(conn->session->auth_baton,
32155682Smarkm                             SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
32255682Smarkm                             &svn_failures);
32355682Smarkm
324233294Sstas      realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s",
325233294Sstas                                 cert_info.fingerprint);
32655682Smarkm
32755682Smarkm      err = svn_auth_first_credentials(&creds, &state,
32855682Smarkm                                       SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
329233294Sstas                                       realmstring,
330233294Sstas                                       conn->session->auth_baton,
331233294Sstas                                       scratch_pool);
332233294Sstas
333233294Sstas      svn_auth_set_parameter(conn->session->auth_baton,
334233294Sstas                             SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
335233294Sstas
336233294Sstas      svn_auth_set_parameter(conn->session->auth_baton,
337233294Sstas                             SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
33855682Smarkm
339102644Snectar      if (err)
34055682Smarkm        {
341102644Snectar          if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER)
34255682Smarkm            return svn_error_trace(err);
343102644Snectar
344102644Snectar          /* No provider registered that handles server authorities */
345102644Snectar          svn_error_clear(err);
346102644Snectar          creds = NULL;
347102644Snectar        }
34855682Smarkm
34955682Smarkm      if (creds)
35055682Smarkm        {
35155682Smarkm          server_creds = creds;
35255682Smarkm          SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
35355682Smarkm
35455682Smarkm          svn_failures &= ~server_creds->accepted_failures;
35555682Smarkm        }
35655682Smarkm
357233294Sstas      if (svn_failures)
35855682Smarkm        conn->server_cert_failures |= svn_failures;
35955682Smarkm
36055682Smarkm      return APR_SUCCESS;
36155682Smarkm    }
36255682Smarkm
36355682Smarkm  svn_auth_set_parameter(conn->session->auth_baton,
36455682Smarkm                         SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
36555682Smarkm                         &svn_failures);
36655682Smarkm
36755682Smarkm  svn_auth_set_parameter(conn->session->auth_baton,
36855682Smarkm                         SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
36955682Smarkm                         &cert_info);
37055682Smarkm
37190926Snectar  realmstring = construct_realm(conn->session, conn->session->pool);
37255682Smarkm
37355682Smarkm  SVN_ERR(svn_auth_first_credentials(&creds, &state,
37455682Smarkm                                     SVN_AUTH_CRED_SSL_SERVER_TRUST,
37590926Snectar                                     realmstring,
37655682Smarkm                                     conn->session->auth_baton,
37790926Snectar                                     scratch_pool));
37890926Snectar  if (creds)
37990926Snectar    {
38090926Snectar      server_creds = creds;
38190926Snectar      svn_failures &= ~server_creds->accepted_failures;
38290926Snectar      SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
38390926Snectar    }
38490926Snectar
38590926Snectar  while (svn_failures && creds)
38655682Smarkm    {
38755682Smarkm      SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool));
38855682Smarkm
38955682Smarkm      if (creds)
39055682Smarkm        {
39155682Smarkm          server_creds = creds;
39255682Smarkm          svn_failures &= ~server_creds->accepted_failures;
39355682Smarkm          SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
39455682Smarkm        }
39555682Smarkm    }
39655682Smarkm
39755682Smarkm  svn_auth_set_parameter(conn->session->auth_baton,
398178825Sdfr                         SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
399178825Sdfr
400178825Sdfr  /* Are there non accepted failures left? */
401178825Sdfr  if (svn_failures)
402178825Sdfr    {
403178825Sdfr      svn_stringbuf_t *errmsg;
404178825Sdfr      int reasons = 0;
405178825Sdfr
406178825Sdfr      errmsg = svn_stringbuf_create(
407233294Sstas                 _("Server SSL certificate verification failed"),
408233294Sstas                 scratch_pool);
409178825Sdfr
410178825Sdfr
411178825Sdfr      if (svn_failures & SVN_AUTH_SSL_NOTYETVALID)
412178825Sdfr        append_reason(errmsg, _("certificate is not yet valid"), &reasons);
413233294Sstas
414233294Sstas      if (svn_failures & SVN_AUTH_SSL_EXPIRED)
415178825Sdfr        append_reason(errmsg, _("certificate has expired"), &reasons);
416178825Sdfr
417178825Sdfr      if (svn_failures & SVN_AUTH_SSL_CNMISMATCH)
418178825Sdfr        append_reason(errmsg,
419178825Sdfr                      _("certificate issued for a different hostname"),
420178825Sdfr                      &reasons);
421178825Sdfr
422178825Sdfr      if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA)
423178825Sdfr        append_reason(errmsg, _("issuer is not trusted"), &reasons);
424233294Sstas
425178825Sdfr      if (svn_failures & SVN_AUTH_SSL_OTHER)
426178825Sdfr        append_reason(errmsg, _("and other reason(s)"), &reasons);
427178825Sdfr
428178825Sdfr      return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL,
429178825Sdfr                              errmsg->data);
430178825Sdfr    }
431178825Sdfr
432178825Sdfr  return SVN_NO_ERROR;
433178825Sdfr}
434178825Sdfr
435178825Sdfr/* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
436178825Sdfrstatic apr_status_t
437178825Sdfrssl_server_cert_cb(void *baton, int failures,
438178825Sdfr                const serf_ssl_certificate_t *cert)
439178825Sdfr{
440178825Sdfr  svn_ra_serf__connection_t *conn = baton;
441178825Sdfr  svn_ra_serf__session_t *session = conn->session;
442178825Sdfr  apr_pool_t *subpool;
443178825Sdfr  svn_error_t *err;
444178825Sdfr
445178825Sdfr  subpool = svn_pool_create(session->pool);
446178825Sdfr  err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
447178825Sdfr  svn_pool_destroy(subpool);
448178825Sdfr
44955682Smarkm  return save_error(session, err);
45055682Smarkm}
451233294Sstas
452233294Sstasstatic svn_error_t *
45355682Smarkmload_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
45455682Smarkm                 apr_pool_t *pool)
45555682Smarkm{
45655682Smarkm  apr_array_header_t *files = svn_cstring_split(authorities, ";",
45790926Snectar                                                TRUE /* chop_whitespace */,
45890926Snectar                                                pool);
45990926Snectar  int i;
46090926Snectar
46190926Snectar  for (i = 0; i < files->nelts; ++i)
46290926Snectar    {
46390926Snectar      const char *file = APR_ARRAY_IDX(files, i, const char *);
46490926Snectar      serf_ssl_certificate_t *ca_cert;
465233294Sstas      apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
466102644Snectar
46790926Snectar      if (status == APR_SUCCESS)
468178825Sdfr        status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
469178825Sdfr
47090926Snectar      if (status != APR_SUCCESS)
47190926Snectar        {
472233294Sstas          return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
47390926Snectar             _("Invalid config: unable to load certificate file '%s'"),
47490926Snectar             svn_dirent_local_style(file, pool));
475233294Sstas        }
47690926Snectar    }
47790926Snectar
478178825Sdfr  return SVN_NO_ERROR;
479178825Sdfr}
480178825Sdfr
481178825Sdfrstatic svn_error_t *
482178825Sdfrconn_setup(apr_socket_t *sock,
48390926Snectar           serf_bucket_t **read_bkt,
484178825Sdfr           serf_bucket_t **write_bkt,
485178825Sdfr           void *baton,
486178825Sdfr           apr_pool_t *pool)
487178825Sdfr{
488178825Sdfr  svn_ra_serf__connection_t *conn = baton;
489178825Sdfr
490178825Sdfr  *read_bkt = serf_context_bucket_socket_create(conn->session->context,
491178825Sdfr                                               sock, conn->bkt_alloc);
492178825Sdfr
493178825Sdfr  if (conn->session->using_ssl)
494233294Sstas    {
495178825Sdfr      /* input stream */
49690926Snectar      *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
497178825Sdfr                                                 conn->bkt_alloc);
498178825Sdfr      if (!conn->ssl_context)
499178825Sdfr        {
500178825Sdfr          conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
501178825Sdfr
502178825Sdfr          serf_ssl_set_hostname(conn->ssl_context,
503178825Sdfr                                conn->session->session_url.hostname);
504178825Sdfr
505178825Sdfr          serf_ssl_client_cert_provider_set(conn->ssl_context,
506178825Sdfr                                            svn_ra_serf__handle_client_cert,
507178825Sdfr                                            conn, conn->session->pool);
508178825Sdfr          serf_ssl_client_cert_password_set(conn->ssl_context,
509178825Sdfr                                            svn_ra_serf__handle_client_cert_pw,
510178825Sdfr                                            conn, conn->session->pool);
511233294Sstas          serf_ssl_server_cert_callback_set(conn->ssl_context,
512178825Sdfr                                            ssl_server_cert_cb,
513178825Sdfr                                            conn);
514178825Sdfr
515178825Sdfr          /* See if the user wants us to trust "default" openssl CAs. */
516178825Sdfr          if (conn->session->trust_default_ca)
517178825Sdfr            {
518178825Sdfr              serf_ssl_use_default_certificates(conn->ssl_context);
519178825Sdfr            }
520233294Sstas          /* Are there custom CAs to load? */
521178825Sdfr          if (conn->session->ssl_authorities)
522178825Sdfr            {
523178825Sdfr              SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
524178825Sdfr                                       conn->session->pool));
525178825Sdfr            }
526178825Sdfr        }
527233294Sstas
528178825Sdfr      if (write_bkt)
529178825Sdfr        {
530178825Sdfr          /* output stream */
531178825Sdfr          *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
532178825Sdfr                                                      conn->ssl_context,
533178825Sdfr                                                      conn->bkt_alloc);
534178825Sdfr        }
535178825Sdfr    }
536178825Sdfr
537178825Sdfr  return SVN_NO_ERROR;
538178825Sdfr}
539178825Sdfr
54090926Snectar/* svn_ra_serf__conn_setup is a callback for serf. This function
54190926Snectar   creates a read bucket and will wrap the write bucket if SSL
542178825Sdfr   is needed. */
543178825Sdfrapr_status_t
54490926Snectarsvn_ra_serf__conn_setup(apr_socket_t *sock,
54590926Snectar                        serf_bucket_t **read_bkt,
54690926Snectar                        serf_bucket_t **write_bkt,
54790926Snectar                        void *baton,
54890926Snectar                        apr_pool_t *pool)
54990926Snectar{
550178825Sdfr  svn_ra_serf__connection_t *conn = baton;
551178825Sdfr  svn_ra_serf__session_t *session = conn->session;
552178825Sdfr  svn_error_t *err;
55390926Snectar
554178825Sdfr  err = svn_error_trace(conn_setup(sock,
555178825Sdfr                                   read_bkt,
55690926Snectar                                   write_bkt,
55790926Snectar                                   baton,
55890926Snectar                                   pool));
55990926Snectar  return save_error(session, err);
56090926Snectar}
56190926Snectar
56290926Snectar
56390926Snectar/* Our default serf response acceptor.  */
56490926Snectarstatic serf_bucket_t *
56590926Snectaraccept_response(serf_request_t *request,
56690926Snectar                serf_bucket_t *stream,
567178825Sdfr                void *acceptor_baton,
56890926Snectar                apr_pool_t *pool)
56990926Snectar{
57090926Snectar  /* svn_ra_serf__handler_t *handler = acceptor_baton; */
57190926Snectar  serf_bucket_t *c;
57290926Snectar  serf_bucket_alloc_t *bkt_alloc;
573233294Sstas
57490926Snectar  bkt_alloc = serf_request_get_alloc(request);
57590926Snectar  c = serf_bucket_barrier_create(stream, bkt_alloc);
57690926Snectar
57790926Snectar  return serf_bucket_response_create(c, bkt_alloc);
57890926Snectar}
57990926Snectar
58090926Snectar
58190926Snectar/* Custom response acceptor for HEAD requests.  */
58290926Snectarstatic serf_bucket_t *
58390926Snectaraccept_head(serf_request_t *request,
58490926Snectar            serf_bucket_t *stream,
58590926Snectar            void *acceptor_baton,
58690926Snectar            apr_pool_t *pool)
58790926Snectar{
58890926Snectar  /* svn_ra_serf__handler_t *handler = acceptor_baton; */
58990926Snectar  serf_bucket_t *response;
59090926Snectar
59190926Snectar  response = accept_response(request, stream, acceptor_baton, pool);
59290926Snectar
59390926Snectar  /* We know we shouldn't get a response body. */
59490926Snectar  serf_bucket_response_set_head(response);
59590926Snectar
59690926Snectar  return response;
59790926Snectar}
59890926Snectar
59990926Snectarstatic svn_error_t *
60090926Snectarconnection_closed(svn_ra_serf__connection_t *conn,
60190926Snectar                  apr_status_t why,
60290926Snectar                  apr_pool_t *pool)
60390926Snectar{
604178825Sdfr  if (why)
60590926Snectar    {
60690926Snectar      return svn_ra_serf__wrap_err(why, NULL);
60790926Snectar    }
60890926Snectar
609178825Sdfr  if (conn->session->using_ssl)
610178825Sdfr    conn->ssl_context = NULL;
611178825Sdfr
612178825Sdfr  return SVN_NO_ERROR;
613233294Sstas}
614178825Sdfr
615178825Sdfrvoid
616178825Sdfrsvn_ra_serf__conn_closed(serf_connection_t *conn,
61790926Snectar                         void *closed_baton,
618178825Sdfr                         apr_status_t why,
619178825Sdfr                         apr_pool_t *pool)
620178825Sdfr{
621178825Sdfr  svn_ra_serf__connection_t *ra_conn = closed_baton;
62290926Snectar  svn_error_t *err;
623178825Sdfr
62490926Snectar  err = svn_error_trace(connection_closed(ra_conn, why, pool));
625178825Sdfr
626178825Sdfr  (void) save_error(ra_conn->session, err);
627178825Sdfr}
628178825Sdfr
62990926Snectar
630178825Sdfr/* Implementation of svn_ra_serf__handle_client_cert */
631178825Sdfrstatic svn_error_t *
632178825Sdfrhandle_client_cert(void *data,
63390926Snectar                   const char **cert_path,
63490926Snectar                   apr_pool_t *pool)
63590926Snectar{
63690926Snectar    svn_ra_serf__connection_t *conn = data;
63790926Snectar    svn_ra_serf__session_t *session = conn->session;
63890926Snectar    const char *realm;
639233294Sstas    void *creds;
640233294Sstas
641102644Snectar    *cert_path = NULL;
64290926Snectar
64390926Snectar    realm = construct_realm(session, session->pool);
64490926Snectar
64590926Snectar    if (!conn->ssl_client_auth_state)
64690926Snectar      {
64790926Snectar        SVN_ERR(svn_auth_first_credentials(&creds,
64890926Snectar                                           &conn->ssl_client_auth_state,
64990926Snectar                                           SVN_AUTH_CRED_SSL_CLIENT_CERT,
65090926Snectar                                           realm,
65190926Snectar                                           session->auth_baton,
65290926Snectar                                           pool));
65390926Snectar      }
65490926Snectar    else
655233294Sstas      {
65690926Snectar        SVN_ERR(svn_auth_next_credentials(&creds,
657233294Sstas                                          conn->ssl_client_auth_state,
658233294Sstas                                          session->pool));
659233294Sstas      }
66090926Snectar
66190926Snectar    if (creds)
66290926Snectar      {
66390926Snectar        svn_auth_cred_ssl_client_cert_t *client_creds;
66490926Snectar        client_creds = creds;
66590926Snectar        *cert_path = client_creds->cert_file;
66690926Snectar      }
66790926Snectar
66890926Snectar    return SVN_NO_ERROR;
66990926Snectar}
67090926Snectar
67190926Snectar/* Implements serf_ssl_need_client_cert_t for handle_client_cert */
67290926Snectarapr_status_t svn_ra_serf__handle_client_cert(void *data,
67390926Snectar                                             const char **cert_path)
67490926Snectar{
67590926Snectar  svn_ra_serf__connection_t *conn = data;
67690926Snectar  svn_ra_serf__session_t *session = conn->session;
67790926Snectar  svn_error_t *err;
67890926Snectar
679233294Sstas  err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
680233294Sstas
681120945Snectar  return save_error(session, err);
682120945Snectar}
683120945Snectar
684178825Sdfr/* Implementation for svn_ra_serf__handle_client_cert_pw */
685120945Snectarstatic svn_error_t *
686120945Snectarhandle_client_cert_pw(void *data,
687178825Sdfr                      const char *cert_path,
688178825Sdfr                      const char **password,
689178825Sdfr                      apr_pool_t *pool)
690178825Sdfr{
691233294Sstas    svn_ra_serf__connection_t *conn = data;
692233294Sstas    svn_ra_serf__session_t *session = conn->session;
693233294Sstas    void *creds;
694120945Snectar
695120945Snectar    *password = NULL;
696120945Snectar
697120945Snectar    if (!conn->ssl_client_pw_auth_state)
698120945Snectar      {
699120945Snectar        SVN_ERR(svn_auth_first_credentials(&creds,
700120945Snectar                                           &conn->ssl_client_pw_auth_state,
701120945Snectar                                           SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
702120945Snectar                                           cert_path,
703120945Snectar                                           session->auth_baton,
704120945Snectar                                           pool));
705120945Snectar      }
706120945Snectar    else
707120945Snectar      {
708120945Snectar        SVN_ERR(svn_auth_next_credentials(&creds,
709120945Snectar                                          conn->ssl_client_pw_auth_state,
710178825Sdfr                                          pool));
711178825Sdfr      }
712178825Sdfr
713178825Sdfr    if (creds)
714178825Sdfr      {
715178825Sdfr        svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
716178825Sdfr        pw_creds = creds;
717178825Sdfr        *password = pw_creds->password;
718120945Snectar      }
719178825Sdfr
720178825Sdfr    return APR_SUCCESS;
721178825Sdfr}
722178825Sdfr
723178825Sdfr/* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
724178825Sdfrapr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
725178825Sdfr                                                const char *cert_path,
726178825Sdfr                                                const char **password)
727178825Sdfr{
728120945Snectar  svn_ra_serf__connection_t *conn = data;
729120945Snectar  svn_ra_serf__session_t *session = conn->session;
730120945Snectar  svn_error_t *err;
73155682Smarkm
732233294Sstas  err = svn_error_trace(handle_client_cert_pw(data,
733233294Sstas                                              cert_path,
734233294Sstas                                              password,
735233294Sstas                                              session->pool));
736233294Sstas
737233294Sstas  return save_error(session, err);
738233294Sstas}
739233294Sstas
740233294Sstas
741233294Sstas/*
742233294Sstas * Given a REQUEST on connection CONN, construct a request bucket for it,
743233294Sstas * returning the bucket in *REQ_BKT.
744233294Sstas *
745233294Sstas * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
746233294Sstas * corresponds to the new request.
747233294Sstas *
748233294Sstas * The request will be METHOD at URL.
74955682Smarkm *
750233294Sstas * If BODY_BKT is not-NULL, it will be sent as the request body.
751233294Sstas *
752233294Sstas * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
753233294Sstas *
754233294Sstas * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers
755233294Sstas * to request.
756233294Sstas *
757233294Sstas * REQUEST_POOL should live for the duration of the request. Serf will
758233294Sstas * construct this and provide it to the request_setup callback, so we
759233294Sstas * should just use that one.
760233294Sstas */
761233294Sstasstatic svn_error_t *
762233294Sstassetup_serf_req(serf_request_t *request,
763233294Sstas               serf_bucket_t **req_bkt,
764233294Sstas               serf_bucket_t **hdrs_bkt,
765233294Sstas               svn_ra_serf__session_t *session,
766233294Sstas               const char *method, const char *url,
76755682Smarkm               serf_bucket_t *body_bkt, const char *content_type,
768233294Sstas               const char *accept_encoding,
76990926Snectar               svn_boolean_t dav_headers,
770233294Sstas               apr_pool_t *request_pool,
771233294Sstas               apr_pool_t *scratch_pool)
772233294Sstas{
773233294Sstas  serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
774233294Sstas
775233294Sstas  svn_spillbuf_t *buf;
776233294Sstas  svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
777233294Sstas
778233294Sstas  if (set_CL && body_bkt != NULL)
779233294Sstas    {
780233294Sstas      /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
781233294Sstas         it speaks HTTP/1.1 (and thus, chunked requests), or because the
782233294Sstas         server actually responded as only supporting HTTP/1.0.
783233294Sstas
784233294Sstas         We'll take the existing body_bkt, spool it into a spillbuf, and
785233294Sstas         then wrap a bucket around that spillbuf. The spillbuf will give
786233294Sstas         us the Content-Length value.  */
787233294Sstas      SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
788233294Sstas                                              request_pool,
789233294Sstas                                              scratch_pool));
790233294Sstas      /* Destroy original bucket since it content is already copied
791233294Sstas         to spillbuf. */
792233294Sstas      serf_bucket_destroy(body_bkt);
793233294Sstas
794233294Sstas      body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
795233294Sstas                                               request_pool,
796233294Sstas                                               scratch_pool);
797233294Sstas    }
798233294Sstas
799233294Sstas  /* Create a request bucket.  Note that this sucker is kind enough to
800233294Sstas     add a "Host" header for us.  */
801233294Sstas  *req_bkt = serf_request_bucket_request_create(request, method, url,
802233294Sstas                                                body_bkt, allocator);
803233294Sstas
80455682Smarkm  /* Set the Content-Length value. This will also trigger an HTTP/1.0
80555682Smarkm     request (rather than the default chunked request).  */
80655682Smarkm  if (set_CL)
80755682Smarkm    {
80855682Smarkm      if (body_bkt == NULL)
80955682Smarkm        serf_bucket_request_set_CL(*req_bkt, 0);
81055682Smarkm      else
81155682Smarkm        serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
81255682Smarkm    }
81355682Smarkm
81455682Smarkm  *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
81555682Smarkm
81655682Smarkm  /* We use serf_bucket_headers_setn() because the USERAGENT has a
81755682Smarkm     lifetime longer than this bucket. Thus, there is no need to copy
81855682Smarkm     the header values.  */
81955682Smarkm  serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
82055682Smarkm
82155682Smarkm  if (content_type)
82255682Smarkm    {
82355682Smarkm      serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
82455682Smarkm    }
82555682Smarkm
826233294Sstas  if (session->http10)
82755682Smarkm    {
82855682Smarkm      serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
82955682Smarkm    }
83055682Smarkm
83155682Smarkm  if (accept_encoding)
83255682Smarkm    {
83355682Smarkm      serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
83455682Smarkm    }
83555682Smarkm
836178825Sdfr  /* These headers need to be sent with every request that might need
837178825Sdfr     capability processing (e.g. during commit, reports, etc.), see
838233294Sstas     issue #3255 ("mod_dav_svn does not pass client capabilities to
839178825Sdfr     start-commit hooks") for why.
840178825Sdfr
841178825Sdfr     Some request types like GET/HEAD/PROPFIND are unaware of capability
842178825Sdfr     handling; and in some cases the responses can even be cached by
843178825Sdfr     proxies, so we don't have to send these hearders there. */
844178825Sdfr  if (dav_headers)
845178825Sdfr    {
846178825Sdfr      serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
847178825Sdfr      serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
848178825Sdfr      serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
849233294Sstas    }
85078527Sassar
85178527Sassar  return SVN_NO_ERROR;
85255682Smarkm}
85355682Smarkm
85478527Sassarsvn_error_t *
855233294Sstassvn_ra_serf__context_run(svn_ra_serf__session_t *sess,
856233294Sstas                         apr_interval_time_t *waittime_left,
857233294Sstas                         apr_pool_t *scratch_pool)
85855682Smarkm{
85978527Sassar  apr_status_t status;
86055682Smarkm  svn_error_t *err;
86155682Smarkm  assert(sess->pending_error == SVN_NO_ERROR);
86255682Smarkm
863178825Sdfr  if (sess->cancel_func)
864178825Sdfr    SVN_ERR(sess->cancel_func(sess->cancel_baton));
865178825Sdfr
866178825Sdfr  status = serf_context_run(sess->context,
867178825Sdfr                            SVN_RA_SERF__CONTEXT_RUN_DURATION,
868178825Sdfr                            scratch_pool);
869178825Sdfr
870178825Sdfr  err = sess->pending_error;
871178825Sdfr  sess->pending_error = SVN_NO_ERROR;
872178825Sdfr
873178825Sdfr   /* If the context duration timeout is up, we'll subtract that
874178825Sdfr      duration from the total time alloted for such things.  If
875178825Sdfr      there's no time left, we fail with a message indicating that
876178825Sdfr      the connection timed out.  */
877233294Sstas  if (APR_STATUS_IS_TIMEUP(status))
87878527Sassar    {
87978527Sassar      status = 0;
88055682Smarkm
88155682Smarkm      if (sess->timeout)
88278527Sassar        {
883233294Sstas          if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
884233294Sstas            {
885233294Sstas              *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
88655682Smarkm            }
88778527Sassar          else
88855682Smarkm            {
88955682Smarkm              return
89055682Smarkm                  svn_error_compose_create(
891178825Sdfr                        err,
892178825Sdfr                        svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
893178825Sdfr                                         _("Connection timed out")));
894178825Sdfr            }
895178825Sdfr        }
896178825Sdfr    }
897178825Sdfr  else
898178825Sdfr    {
899178825Sdfr      *waittime_left = sess->timeout;
900178825Sdfr    }
901178825Sdfr
902178825Sdfr  SVN_ERR(err);
903178825Sdfr  if (status)
904178825Sdfr    {
905178825Sdfr      /* ### This omits SVN_WARNING, and possibly relies on the fact that
906178825Sdfr         ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */
907178825Sdfr      if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
908178825Sdfr        {
909178825Sdfr          /* apr can't translate subversion errors to text */
910178825Sdfr          SVN_ERR_W(svn_error_create(status, NULL, NULL),
911178825Sdfr                    _("Error running context"));
912233294Sstas        }
91378527Sassar
91478527Sassar      return svn_ra_serf__wrap_err(status, _("Error running context"));
91555682Smarkm    }
916102644Snectar
91755682Smarkm  return SVN_NO_ERROR;
91855682Smarkm}
91955682Smarkm
92055682Smarkmsvn_error_t *
92178527Sassarsvn_ra_serf__context_run_wait(svn_boolean_t *done,
922233294Sstas                              svn_ra_serf__session_t *sess,
923233294Sstas                              apr_pool_t *scratch_pool)
924233294Sstas{
925233294Sstas  apr_pool_t *iterpool;
92655682Smarkm  apr_interval_time_t waittime_left = sess->timeout;
92778527Sassar
92890926Snectar  assert(sess->pending_error == SVN_NO_ERROR);
929233294Sstas
930233294Sstas  iterpool = svn_pool_create(scratch_pool);
931233294Sstas  while (!*done)
932233294Sstas    {
93390926Snectar      int i;
93490926Snectar
93555682Smarkm      svn_pool_clear(iterpool);
93655682Smarkm
93755682Smarkm      SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
93855682Smarkm
939178825Sdfr      /* Debugging purposes only! */
940178825Sdfr      for (i = 0; i < sess->num_conns; i++)
941178825Sdfr        {
942178825Sdfr          serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
943178825Sdfr        }
944178825Sdfr    }
945178825Sdfr  svn_pool_destroy(iterpool);
946178825Sdfr
947178825Sdfr  return SVN_NO_ERROR;
948233294Sstas}
94955682Smarkm
95055682Smarkm/* Ensure that a handler is no longer scheduled on the connection.
95155682Smarkm
95255682Smarkm   Eventually serf will have a reliable way to cancel existing requests,
95355682Smarkm   but currently it doesn't even have a way to relyable identify a request
95455682Smarkm   after rescheduling, for auth reasons.
95555682Smarkm
95655682Smarkm   So the only thing we can do today is reset the connection, which
95755682Smarkm   will cancel all outstanding requests and prepare the connection
95855682Smarkm   for re-use.
95955682Smarkm*/
960178825Sdfrstatic void
961178825Sdfrsvn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler)
962178825Sdfr{
963178825Sdfr  serf_connection_reset(handler->conn->conn);
964178825Sdfr  handler->scheduled = FALSE;
965178825Sdfr}
966178825Sdfr
967178825Sdfrsvn_error_t *
968178825Sdfrsvn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
969178825Sdfr                             apr_pool_t *scratch_pool)
970178825Sdfr{
971178825Sdfr  svn_error_t *err;
972233294Sstas
97355682Smarkm  /* Create a serf request based on HANDLER.  */
97455682Smarkm  svn_ra_serf__request_create(handler);
97555682Smarkm
97690926Snectar  /* Wait until the response logic marks its DONE status.  */
97755682Smarkm  err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
97855682Smarkm                                      scratch_pool);
97955682Smarkm
98055682Smarkm  if (handler->scheduled)
981233294Sstas    {
982233294Sstas      /* We reset the connection (breaking  pipelining, etc.), as
983233294Sstas         if we didn't the next data would still be handled by this handler,
984233294Sstas         which is done as far as our caller is concerned. */
985233294Sstas      svn_ra_serf__unschedule_handler(handler);
986233294Sstas    }
987233294Sstas
988233294Sstas  return svn_error_trace(err);
989233294Sstas}
990178825Sdfr
991178825Sdfr
992178825Sdfr
993178825Sdfr
994178825Sdfrstatic apr_status_t
995178825Sdfrdrain_bucket(serf_bucket_t *bucket)
996178825Sdfr{
997178825Sdfr  /* Read whatever is in the bucket, and just drop it.  */
998178825Sdfr  while (1)
999178825Sdfr    {
1000178825Sdfr      apr_status_t status;
1001178825Sdfr      const char *data;
1002178825Sdfr      apr_size_t len;
1003178825Sdfr
1004178825Sdfr      status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
1005178825Sdfr      if (status)
1006178825Sdfr        return status;
1007178825Sdfr    }
1008178825Sdfr}
1009233294Sstas
101078527Sassar
101178527Sassar
1012102644Snectar
1013102644Snectar/* Implements svn_ra_serf__response_handler_t */
101455682Smarkmsvn_error_t *
101555682Smarkmsvn_ra_serf__handle_discard_body(serf_request_t *request,
101655682Smarkm                                 serf_bucket_t *response,
101778527Sassar                                 void *baton,
1018233294Sstas                                 apr_pool_t *pool)
1019233294Sstas{
102055682Smarkm  apr_status_t status;
102178527Sassar
102255682Smarkm  status = drain_bucket(response);
102355682Smarkm  if (status)
102455682Smarkm    return svn_ra_serf__wrap_err(status, NULL);
102555682Smarkm
1026178825Sdfr  return SVN_NO_ERROR;
1027178825Sdfr}
1028178825Sdfr
1029178825Sdfrapr_status_t
1030178825Sdfrsvn_ra_serf__response_discard_handler(serf_request_t *request,
1031178825Sdfr                                      serf_bucket_t *response,
1032178825Sdfr                                      void *baton,
1033178825Sdfr                                      apr_pool_t *pool)
1034178825Sdfr{
1035178825Sdfr  return drain_bucket(response);
1036178825Sdfr}
1037178825Sdfr
1038178825Sdfr
1039178825Sdfr/* Return the value of the RESPONSE's Location header if any, or NULL
1040233294Sstas   otherwise.  */
104178527Sassarstatic const char *
104278527Sassarresponse_get_location(serf_bucket_t *response,
104355682Smarkm                      const char *base_url,
104455682Smarkm                      apr_pool_t *result_pool,
104555682Smarkm                      apr_pool_t *scratch_pool)
104678527Sassar{
1047233294Sstas  serf_bucket_t *headers;
1048233294Sstas  const char *location;
104955682Smarkm
105078527Sassar  headers = serf_bucket_response_get_headers(response);
105155682Smarkm  location = serf_bucket_headers_get(headers, "Location");
105255682Smarkm  if (location == NULL)
105355682Smarkm    return NULL;
1054178825Sdfr
1055178825Sdfr  /* The RFCs say we should have received a full url in LOCATION, but
1056178825Sdfr     older apache versions and many custom web handlers just return a
1057178825Sdfr     relative path here...
1058178825Sdfr
1059178825Sdfr     And we can't trust anything because it is network data.
1060178825Sdfr   */
1061178825Sdfr  if (*location == '/')
1062178825Sdfr    {
1063178825Sdfr      apr_uri_t uri;
1064178825Sdfr      apr_status_t status;
1065178825Sdfr
1066178825Sdfr      status = apr_uri_parse(scratch_pool, base_url, &uri);
1067178825Sdfr
1068178825Sdfr      if (status != APR_SUCCESS)
1069178825Sdfr        return NULL;
1070178825Sdfr
1071233294Sstas      /* Replace the path path with what we got */
107278527Sassar      uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
107378527Sassar
107455682Smarkm      /* And make APR produce a proper full url for us */
1075102644Snectar      location = apr_uri_unparse(scratch_pool, &uri, 0);
107655682Smarkm
107755682Smarkm      /* Fall through to ensure our canonicalization rules */
107855682Smarkm    }
107955682Smarkm  else if (!svn_path_is_url(location))
108078527Sassar    {
1081233294Sstas      return NULL; /* Any other formats we should support? */
1082233294Sstas    }
108355682Smarkm
108478527Sassar  return svn_uri_canonicalize(location, result_pool);
108555682Smarkm}
108655682Smarkm
108755682Smarkm
108855682Smarkm/* Implements svn_ra_serf__response_handler_t */
108955682Smarkmsvn_error_t *
1090178825Sdfrsvn_ra_serf__expect_empty_body(serf_request_t *request,
1091178825Sdfr                               serf_bucket_t *response,
1092178825Sdfr                               void *baton,
1093178825Sdfr                               apr_pool_t *scratch_pool)
1094178825Sdfr{
1095178825Sdfr  svn_ra_serf__handler_t *handler = baton;
1096178825Sdfr  serf_bucket_t *hdrs;
1097178825Sdfr  const char *val;
1098178825Sdfr
1099178825Sdfr  /* This function is just like handle_multistatus_only() except for the
1100178825Sdfr     XML parsing callbacks. We want to look for the -readable element.  */
1101178825Sdfr
1102178825Sdfr  /* We should see this just once, in order to initialize SERVER_ERROR.
1103178825Sdfr     At that point, the core error processing will take over. If we choose
1104178825Sdfr     not to parse an error, then we'll never return here (because we
1105178825Sdfr     change the response handler).  */
1106233294Sstas  SVN_ERR_ASSERT(handler->server_error == NULL);
1107233294Sstas
110855682Smarkm  hdrs = serf_bucket_response_get_headers(response);
110955682Smarkm  val = serf_bucket_headers_get(hdrs, "Content-Type");
111055682Smarkm  if (val
1111178825Sdfr      && (handler->sline.code < 200 || handler->sline.code >= 300)
111255682Smarkm      && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1113120945Snectar    {
111455682Smarkm      svn_ra_serf__server_error_t *server_err;
111590926Snectar
1116233294Sstas      SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
111790926Snectar                                               FALSE,
111855682Smarkm                                               handler->handler_pool,
111955682Smarkm                                               handler->handler_pool));
1120233294Sstas
112190926Snectar      handler->server_error = server_err;
112255682Smarkm    }
112390926Snectar  else
112455682Smarkm    {
112555682Smarkm      /* The body was not text/xml, or we got a success code.
1126233294Sstas         Toss anything that arrives.  */
112790926Snectar      handler->discard_body = TRUE;
112855682Smarkm    }
112955682Smarkm
113055682Smarkm  /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1131102644Snectar     to call the response handler again. That will start up the XML parsing,
1132102644Snectar     or it will be dropped on the floor (per the decision above).  */
113355682Smarkm  return SVN_NO_ERROR;
113455682Smarkm}
1135102644Snectar
1136178825Sdfr
1137178825Sdfrapr_status_t
1138102644Snectarsvn_ra_serf__credentials_callback(char **username, char **password,
1139102644Snectar                                  serf_request_t *request, void *baton,
114055682Smarkm                                  int code, const char *authn_type,
114155682Smarkm                                  const char *realm,
114255682Smarkm                                  apr_pool_t *pool)
1143178825Sdfr{
1144178825Sdfr  svn_ra_serf__handler_t *handler = baton;
1145178825Sdfr  svn_ra_serf__session_t *session = handler->session;
1146178825Sdfr  void *creds;
1147178825Sdfr  svn_auth_cred_simple_t *simple_creds;
1148178825Sdfr  svn_error_t *err;
1149178825Sdfr
1150178825Sdfr  if (code == 401)
1151178825Sdfr    {
1152178825Sdfr      /* Use svn_auth_first_credentials if this is the first time we ask for
1153178825Sdfr         credentials during this session OR if the last time we asked
1154178825Sdfr         session->auth_state wasn't set (eg. if the credentials provider was
1155178825Sdfr         cancelled by the user). */
1156233294Sstas      if (!session->auth_state)
115755682Smarkm        {
115855682Smarkm          err = svn_auth_first_credentials(&creds,
115955682Smarkm                                           &session->auth_state,
116055682Smarkm                                           SVN_AUTH_CRED_SIMPLE,
116155682Smarkm                                           realm,
116255682Smarkm                                           session->auth_baton,
116355682Smarkm                                           session->pool);
116478527Sassar        }
116555682Smarkm      else
1166178825Sdfr        {
1167178825Sdfr          err = svn_auth_next_credentials(&creds,
1168178825Sdfr                                          session->auth_state,
116955682Smarkm                                          session->pool);
117055682Smarkm        }
1171102644Snectar
1172102644Snectar      if (err)
117355682Smarkm        {
1174178825Sdfr          (void) save_error(session, err);
1175233294Sstas          return err->apr_err;
1176233294Sstas        }
1177178825Sdfr
1178178825Sdfr      session->auth_attempts++;
1179102644Snectar
118055682Smarkm      if (!creds || session->auth_attempts > 4)
118155682Smarkm        {
118255682Smarkm          /* No more credentials. */
118355682Smarkm          (void) save_error(session,
118455682Smarkm                            svn_error_create(
118555682Smarkm                              SVN_ERR_AUTHN_FAILED, NULL,
118678527Sassar                              _("No more credentials or we tried too many "
1187233294Sstas                                "times.\nAuthentication failed")));
118878527Sassar          return SVN_ERR_AUTHN_FAILED;
1189233294Sstas        }
1190233294Sstas
1191233294Sstas      simple_creds = creds;
1192233294Sstas      *username = apr_pstrdup(pool, simple_creds->username);
119378527Sassar      *password = apr_pstrdup(pool, simple_creds->password);
1194233294Sstas    }
119555682Smarkm  else
119655682Smarkm    {
119755682Smarkm      *username = apr_pstrdup(pool, session->proxy_username);
119855682Smarkm      *password = apr_pstrdup(pool, session->proxy_password);
119955682Smarkm
1200178825Sdfr      session->proxy_auth_attempts++;
1201233294Sstas
1202233294Sstas      if (!session->proxy_username || session->proxy_auth_attempts > 4)
1203178825Sdfr        {
1204178825Sdfr          /* No more credentials. */
1205178825Sdfr          (void) save_error(session,
120655682Smarkm                            svn_error_create(
1207178825Sdfr                              SVN_ERR_AUTHN_FAILED, NULL,
1208102644Snectar                              _("Proxy authentication failed")));
1209178825Sdfr          return SVN_ERR_AUTHN_FAILED;
1210178825Sdfr        }
1211233294Sstas    }
1212233294Sstas
1213178825Sdfr  handler->conn->last_status_code = code;
1214233294Sstas
1215233294Sstas  return APR_SUCCESS;
1216178825Sdfr}
121755682Smarkm
121855682Smarkm/* Wait for HTTP response status and headers, and invoke HANDLER->
121955682Smarkm   response_handler() to carry out operation-specific processing.
122055682Smarkm   Afterwards, check for connection close.
122190926Snectar
1222178825Sdfr   SERF_STATUS allows returning errors to serf without creating a
1223178825Sdfr   subversion error object.
1224178825Sdfr   */
1225233294Sstasstatic svn_error_t *
1226178825Sdfrhandle_response(serf_request_t *request,
1227178825Sdfr                serf_bucket_t *response,
1228178825Sdfr                svn_ra_serf__handler_t *handler,
1229178825Sdfr                apr_status_t *serf_status,
1230178825Sdfr                apr_pool_t *scratch_pool)
1231178825Sdfr{
1232178825Sdfr  apr_status_t status;
1233178825Sdfr  svn_error_t *err;
1234178825Sdfr
1235178825Sdfr  /* ### need to verify whether this already gets init'd on every
1236178825Sdfr     ### successful exit. for an error-exit, it will (properly) be
1237233294Sstas     ### ignored by the caller.  */
123890926Snectar  *serf_status = APR_SUCCESS;
123990926Snectar
124090926Snectar  if (!response)
124190926Snectar    {
124290926Snectar      /* Uh-oh. Our connection died.  */
124390926Snectar      handler->scheduled = FALSE;
124490926Snectar
1245233294Sstas      if (handler->response_error)
124690926Snectar        {
1247233294Sstas          /* Give a handler chance to prevent request requeue. */
1248233294Sstas          SVN_ERR(handler->response_error(request, response, 0,
1249233294Sstas                                          handler->response_error_baton));
125090926Snectar
125190926Snectar          svn_ra_serf__request_create(handler);
1252233294Sstas        }
1253233294Sstas      /* Response error callback is not configured. Requeue another request
1254233294Sstas         for this handler only if we didn't started to process body.
125590926Snectar         Return error otherwise. */
1256233294Sstas      else if (!handler->reading_body)
1257233294Sstas        {
1258233294Sstas          svn_ra_serf__request_create(handler);
125990926Snectar        }
126090926Snectar      else
1261233294Sstas        {
126290926Snectar          return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
126390926Snectar                                    _("%s request on '%s' failed"),
126490926Snectar                                   handler->method, handler->path);
126590926Snectar        }
126690926Snectar
126790926Snectar      return SVN_NO_ERROR;
126890926Snectar    }
126990926Snectar
127090926Snectar  /* If we're reading the body, then skip all this preparation.  */
127190926Snectar  if (handler->reading_body)
127290926Snectar    goto process_body;
1273178825Sdfr
1274178825Sdfr  /* Copy the Status-Line info into HANDLER, if we don't yet have it.  */
1275178825Sdfr  if (handler->sline.version == 0)
1276178825Sdfr    {
1277178825Sdfr      serf_status_line sl;
1278178825Sdfr
1279178825Sdfr      status = serf_bucket_response_status(response, &sl);
1280178825Sdfr      if (status != APR_SUCCESS)
1281178825Sdfr        {
1282178825Sdfr          /* The response line is not (yet) ready, or some other error.  */
1283178825Sdfr          *serf_status = status;
1284178825Sdfr          return SVN_NO_ERROR; /* Handled by serf */
1285178825Sdfr        }
1286233294Sstas
128790926Snectar      /* If we got APR_SUCCESS, then we should have Status-Line info.  */
128890926Snectar      SVN_ERR_ASSERT(sl.version != 0);
128990926Snectar
129090926Snectar      handler->sline = sl;
129190926Snectar      handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
129290926Snectar
129390926Snectar      /* HTTP/1.1? (or later)  */
1294178825Sdfr      if (sl.version != SERF_HTTP_10)
1295178825Sdfr        handler->session->http10 = FALSE;
1296178825Sdfr    }
1297178825Sdfr
1298178825Sdfr  /* Keep reading from the network until we've read all the headers.  */
1299178825Sdfr  status = serf_bucket_response_wait_for_headers(response);
1300178825Sdfr  if (status)
1301178825Sdfr    {
1302178825Sdfr      /* The typical "error" will be APR_EAGAIN, meaning that more input
1303178825Sdfr         from the network is required to complete the reading of the
1304178825Sdfr         headers.  */
1305178825Sdfr      if (!APR_STATUS_IS_EOF(status))
1306178825Sdfr        {
1307233294Sstas          /* Either the headers are not (yet) complete, or there really
130890926Snectar             was an error.  */
130990926Snectar          *serf_status = status;
131090926Snectar          return SVN_NO_ERROR;
131190926Snectar        }
1312233294Sstas
131390926Snectar      /* wait_for_headers() will return EOF if there is no body in this
131490926Snectar         response, or if we completely read the body. The latter is not
131590926Snectar         true since we would have set READING_BODY to get the body read,
131690926Snectar         and we would not be back to this code block.
131790926Snectar
131890926Snectar         It can also return EOF if we truly hit EOF while (say) processing
131990926Snectar         the headers. aka Badness.  */
1320178825Sdfr
1321178825Sdfr      /* Cases where a lack of a response body (via EOF) is okay:
1322178825Sdfr       *  - A HEAD request
1323178825Sdfr       *  - 204/304 response
1324178825Sdfr       *
1325178825Sdfr       * Otherwise, if we get an EOF here, something went really wrong: either
1326178825Sdfr       * the server closed on us early or we're reading too much.  Either way,
1327178825Sdfr       * scream loudly.
1328178825Sdfr       */
1329178825Sdfr      if (strcmp(handler->method, "HEAD") != 0
1330178825Sdfr          && handler->sline.code != 204
1331178825Sdfr          && handler->sline.code != 304)
1332233294Sstas        {
133390926Snectar          err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
133490926Snectar                                  svn_ra_serf__wrap_err(status, NULL),
133590926Snectar                                  _("Premature EOF seen from server"
1336178825Sdfr                                    " (http status=%d)"),
133790926Snectar                                  handler->sline.code);
133890926Snectar
133990926Snectar          /* In case anything else arrives... discard it.  */
1340178825Sdfr          handler->discard_body = TRUE;
134190926Snectar
134290926Snectar          return err;
134390926Snectar        }
1344178825Sdfr    }
1345178825Sdfr
1346178825Sdfr  /* ... and set up the header fields in HANDLER.  */
1347178825Sdfr  handler->location = response_get_location(response,
1348178825Sdfr                                            handler->session->session_url_str,
1349178825Sdfr                                            handler->handler_pool,
1350178825Sdfr                                            scratch_pool);
1351178825Sdfr
1352178825Sdfr  /* On the last request, we failed authentication. We succeeded this time,
1353178825Sdfr     so let's save away these credentials.  */
1354178825Sdfr  if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
1355178825Sdfr    {
1356233294Sstas      SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
135790926Snectar                                        handler->session->pool));
135890926Snectar      handler->session->auth_attempts = 0;
135990926Snectar      handler->session->auth_state = NULL;
1360233294Sstas    }
136190926Snectar  handler->conn->last_status_code = handler->sline.code;
136290926Snectar
136390926Snectar  if (handler->sline.code >= 400)
1364178825Sdfr    {
1365178825Sdfr      /* 405 Method Not allowed.
136690926Snectar         408 Request Timeout
136790926Snectar         409 Conflict: can indicate a hook error.
136890926Snectar         5xx (Internal) Server error. */
1369178825Sdfr      serf_bucket_t *hdrs;
1370178825Sdfr      const char *val;
1371178825Sdfr
1372178825Sdfr      hdrs = serf_bucket_response_get_headers(response);
1373178825Sdfr      val = serf_bucket_headers_get(hdrs, "Content-Type");
1374178825Sdfr      if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1375178825Sdfr        {
1376178825Sdfr          svn_ra_serf__server_error_t *server_err;
1377178825Sdfr
1378178825Sdfr          SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1379178825Sdfr                                                   FALSE,
1380178825Sdfr                                                   handler->handler_pool,
1381178825Sdfr                                                   handler->handler_pool));
1382233294Sstas
138390926Snectar          handler->server_error = server_err;
138490926Snectar        }
138590926Snectar      else
138690926Snectar        {
138790926Snectar          handler->discard_body = TRUE;
138890926Snectar        }
138990926Snectar    }
139090926Snectar  else if (handler->sline.code <= 199)
139190926Snectar    {
139290926Snectar      handler->discard_body = TRUE;
1393178825Sdfr    }
1394178825Sdfr
1395178825Sdfr  /* Stop processing the above, on every packet arrival.  */
1396178825Sdfr  handler->reading_body = TRUE;
1397178825Sdfr
1398178825Sdfr process_body:
1399178825Sdfr
1400178825Sdfr  /* We've been instructed to ignore the body. Drain whatever is present.  */
1401178825Sdfr  if (handler->discard_body)
1402178825Sdfr    {
1403178825Sdfr      *serf_status = drain_bucket(response);
1404178825Sdfr
1405178825Sdfr      return SVN_NO_ERROR;
1406233294Sstas    }
140790926Snectar
140890926Snectar  /* If we are supposed to parse the body as a server_error, then do
140990926Snectar     that now.  */
141090926Snectar  if (handler->server_error != NULL)
1411233294Sstas    {
141290926Snectar      return svn_error_trace(
141390926Snectar                svn_ra_serf__handle_server_error(handler->server_error,
141490926Snectar                                                 handler,
141590926Snectar                                                 request, response,
141690926Snectar                                                 serf_status,
141790926Snectar                                                 scratch_pool));
141890926Snectar    }
141990926Snectar
1420178825Sdfr  /* Pass the body along to the registered response handler.  */
1421178825Sdfr  err = handler->response_handler(request, response,
1422178825Sdfr                                  handler->response_baton,
1423178825Sdfr                                  scratch_pool);
1424178825Sdfr
1425178825Sdfr  if (err
1426178825Sdfr      && (!SERF_BUCKET_READ_ERROR(err->apr_err)
1427178825Sdfr          || APR_STATUS_IS_ECONNRESET(err->apr_err)
1428178825Sdfr          || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
1429178825Sdfr    {
1430178825Sdfr      /* These errors are special cased in serf
1431178825Sdfr         ### We hope no handler returns these by accident. */
1432178825Sdfr      *serf_status = err->apr_err;
1433233294Sstas      svn_error_clear(err);
143490926Snectar      return SVN_NO_ERROR;
143590926Snectar    }
143690926Snectar
143790926Snectar  return svn_error_trace(err);
143890926Snectar}
143990926Snectar
1440233294Sstas
144190926Snectar/* Implements serf_response_handler_t for handle_response. Storing
144290926Snectar   errors in handler->session->pending_error if appropriate. */
144390926Snectarstatic apr_status_t
1444233294Sstashandle_response_cb(serf_request_t *request,
1445233294Sstas                   serf_bucket_t *response,
144690926Snectar                   void *baton,
144790926Snectar                   apr_pool_t *response_pool)
144890926Snectar{
144990926Snectar  svn_ra_serf__handler_t *handler = baton;
145090926Snectar  svn_error_t *err;
145190926Snectar  apr_status_t inner_status;
145290926Snectar  apr_status_t outer_status;
1453233294Sstas  apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */
1454233294Sstas
145590926Snectar  err = svn_error_trace(handle_response(request, response,
145690926Snectar                                        handler, &inner_status,
145790926Snectar                                        scratch_pool));
145890926Snectar
145990926Snectar  /* Select the right status value to return.  */
146090926Snectar  outer_status = save_error(handler->session, err);
146190926Snectar  if (!outer_status)
146290926Snectar    outer_status = inner_status;
146390926Snectar
1464178825Sdfr  /* Make sure the DONE flag is set properly and requests are cleaned up. */
146590926Snectar  if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
1466178825Sdfr    {
1467178825Sdfr      svn_ra_serf__session_t *sess = handler->session;
1468178825Sdfr      handler->done = TRUE;
1469178825Sdfr      handler->scheduled = FALSE;
1470178825Sdfr      outer_status = APR_EOF;
1471178825Sdfr
1472178825Sdfr      /* We use a cached handler->session here to allow handler to free the
1473178825Sdfr         memory containing the handler */
1474178825Sdfr      save_error(sess,
147590926Snectar                 handler->done_delegate(request, handler->done_delegate_baton,
147690926Snectar                                        scratch_pool));
1477233294Sstas    }
147890926Snectar  else if (SERF_BUCKET_READ_ERROR(outer_status)
147990926Snectar           && handler->session->pending_error)
148090926Snectar    {
148190926Snectar      handler->discard_body = TRUE; /* Discard further data */
148290926Snectar      handler->done = TRUE; /* Mark as done */
148390926Snectar      /* handler->scheduled is still TRUE, as we still expect data.
148490926Snectar         If we would return an error outer-status the connection
148590926Snectar         would have to be restarted. With scheduled still TRUE
148690926Snectar         destroying the handler's pool will still reset the
1487233294Sstas         connection, avoiding the posibility of returning
1488233294Sstas         an error for this handler when a new request is
148990926Snectar         scheduled. */
149090926Snectar      outer_status = APR_EAGAIN; /* Exit context loop */
149190926Snectar    }
149290926Snectar
149390926Snectar  return outer_status;
1494233294Sstas}
1495233294Sstas
149690926Snectar/* Perform basic request setup, with special handling for HEAD requests,
1497178825Sdfr   and finer-grained callbacks invoked (if non-NULL) to produce the request
149890926Snectar   headers and body. */
149990926Snectarstatic svn_error_t *
150090926Snectarsetup_request(serf_request_t *request,
150190926Snectar              svn_ra_serf__handler_t *handler,
150290926Snectar              serf_bucket_t **req_bkt,
150390926Snectar              apr_pool_t *request_pool,
150490926Snectar              apr_pool_t *scratch_pool)
150590926Snectar{
150690926Snectar  serf_bucket_t *body_bkt;
150790926Snectar  serf_bucket_t *headers_bkt;
150890926Snectar  const char *accept_encoding;
150990926Snectar
151090926Snectar  if (handler->body_delegate)
151190926Snectar    {
151290926Snectar      serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
151390926Snectar
151490926Snectar      SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
151590926Snectar                                     bkt_alloc, request_pool, scratch_pool));
151690926Snectar    }
151790926Snectar  else
151890926Snectar    {
151990926Snectar      body_bkt = NULL;
152090926Snectar    }
152190926Snectar
152290926Snectar  if (handler->custom_accept_encoding)
152390926Snectar    {
152490926Snectar      accept_encoding = NULL;
152590926Snectar    }
152690926Snectar  else if (handler->session->using_compression)
152790926Snectar    {
1528178825Sdfr      /* Accept gzip compression if enabled. */
1529178825Sdfr      accept_encoding = "gzip";
1530178825Sdfr    }
1531178825Sdfr  else
1532178825Sdfr    {
1533178825Sdfr      accept_encoding = NULL;
1534178825Sdfr    }
1535178825Sdfr
1536178825Sdfr  SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
1537178825Sdfr                         handler->session, handler->method, handler->path,
1538178825Sdfr                         body_bkt, handler->body_type, accept_encoding,
1539178825Sdfr                         !handler->no_dav_headers, request_pool,
1540178825Sdfr                         scratch_pool));
1541178825Sdfr
1542178825Sdfr  if (handler->header_delegate)
1543178825Sdfr    {
1544233294Sstas      SVN_ERR(handler->header_delegate(headers_bkt,
1545178825Sdfr                                       handler->header_delegate_baton,
1546178825Sdfr                                       request_pool, scratch_pool));
1547178825Sdfr    }
1548178825Sdfr
1549178825Sdfr  return SVN_NO_ERROR;
1550178825Sdfr}
1551178825Sdfr
1552178825Sdfr/* Implements the serf_request_setup_t interface (which sets up both a
1553178825Sdfr   request and its response handler callback). Handles errors for
1554233294Sstas   setup_request_cb */
1555233294Sstasstatic apr_status_t
1556233294Sstassetup_request_cb(serf_request_t *request,
1557233294Sstas              void *setup_baton,
1558178825Sdfr              serf_bucket_t **req_bkt,
1559178825Sdfr              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