util.c revision 269847
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 <expat.h>
36
37#include "svn_hash.h"
38#include "svn_dirent_uri.h"
39#include "svn_path.h"
40#include "svn_private_config.h"
41#include "svn_string.h"
42#include "svn_xml.h"
43#include "svn_props.h"
44#include "svn_dirent_uri.h"
45
46#include "../libsvn_ra/ra_loader.h"
47#include "private/svn_dep_compat.h"
48#include "private/svn_fspath.h"
49#include "private/svn_subr_private.h"
50#include "private/svn_auth_private.h"
51#include "private/svn_cert.h"
52
53#include "ra_serf.h"
54
55
56/* Fix for older expat 1.95.x's that do not define
57 * XML_STATUS_OK/XML_STATUS_ERROR
58 */
59#ifndef XML_STATUS_OK
60#define XML_STATUS_OK    1
61#define XML_STATUS_ERROR 0
62#endif
63
64#ifndef XML_VERSION_AT_LEAST
65#define XML_VERSION_AT_LEAST(major,minor,patch)                  \
66(((major) < XML_MAJOR_VERSION)                                       \
67 || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION)    \
68 || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \
69     (patch) <= XML_MICRO_VERSION))
70#endif /* APR_VERSION_AT_LEAST */
71
72#if XML_VERSION_AT_LEAST(1, 95, 8)
73#define EXPAT_HAS_STOPPARSER
74#endif
75
76/* Read/write chunks of this size into the spillbuf.  */
77#define PARSE_CHUNK_SIZE 8000
78
79/* We will store one megabyte in memory, before switching to store content
80   into a temporary file.  */
81#define SPILL_SIZE 1000000
82
83
84/* This structure records pending data for the parser in memory blocks,
85   and possibly into a temporary file if "too much" content arrives.  */
86struct svn_ra_serf__pending_t {
87  /* The spillbuf where we record the pending data.  */
88  svn_spillbuf_t *buf;
89
90  /* This flag is set when the network has reached EOF. The PENDING
91     processing can then properly detect when parsing has completed.  */
92  svn_boolean_t network_eof;
93};
94
95#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \
96                             && svn_spillbuf__get_size((p)->buf) != 0)
97
98
99struct expat_ctx_t {
100  svn_ra_serf__xml_context_t *xmlctx;
101  XML_Parser parser;
102  svn_ra_serf__handler_t *handler;
103
104  svn_error_t *inner_error;
105
106  /* Do not use this pool for allocation. It is merely recorded for running
107     the cleanup handler.  */
108  apr_pool_t *cleanup_pool;
109};
110
111
112static const apr_uint32_t serf_failure_map[][2] =
113{
114  { SERF_SSL_CERT_NOTYETVALID,   SVN_AUTH_SSL_NOTYETVALID },
115  { SERF_SSL_CERT_EXPIRED,       SVN_AUTH_SSL_EXPIRED },
116  { SERF_SSL_CERT_SELF_SIGNED,   SVN_AUTH_SSL_UNKNOWNCA },
117  { SERF_SSL_CERT_UNKNOWNCA,     SVN_AUTH_SSL_UNKNOWNCA }
118};
119
120/* Return a Subversion failure mask based on FAILURES, a serf SSL
121   failure mask.  If anything in FAILURES is not directly mappable to
122   Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */
123static apr_uint32_t
124ssl_convert_serf_failures(int failures)
125{
126  apr_uint32_t svn_failures = 0;
127  apr_size_t i;
128
129  for (i = 0; i < sizeof(serf_failure_map) / (2 * sizeof(apr_uint32_t)); ++i)
130    {
131      if (failures & serf_failure_map[i][0])
132        {
133          svn_failures |= serf_failure_map[i][1];
134          failures &= ~serf_failure_map[i][0];
135        }
136    }
137
138  /* Map any remaining failure bits to our OTHER bit. */
139  if (failures)
140    {
141      svn_failures |= SVN_AUTH_SSL_OTHER;
142    }
143
144  return svn_failures;
145}
146
147
148static apr_status_t
149save_error(svn_ra_serf__session_t *session,
150           svn_error_t *err)
151{
152  if (err || session->pending_error)
153    {
154      session->pending_error = svn_error_compose_create(
155                                  session->pending_error,
156                                  err);
157      return session->pending_error->apr_err;
158    }
159
160  return APR_SUCCESS;
161}
162
163
164/* Construct the realmstring, e.g. https://svn.collab.net:443. */
165static const char *
166construct_realm(svn_ra_serf__session_t *session,
167                apr_pool_t *pool)
168{
169  const char *realm;
170  apr_port_t port;
171
172  if (session->session_url.port_str)
173    {
174      port = session->session_url.port;
175    }
176  else
177    {
178      port = apr_uri_port_of_scheme(session->session_url.scheme);
179    }
180
181  realm = apr_psprintf(pool, "%s://%s:%d",
182                       session->session_url.scheme,
183                       session->session_url.hostname,
184                       port);
185
186  return realm;
187}
188
189/* Convert a hash table containing the fields (as documented in X.509) of an
190   organisation to a string ORG, allocated in POOL. ORG is as returned by
191   serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
192static char *
193convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
194{
195  const char *org_unit = svn_hash_gets(org, "OU");
196  const char *org_name = svn_hash_gets(org, "O");
197  const char *locality = svn_hash_gets(org, "L");
198  const char *state = svn_hash_gets(org, "ST");
199  const char *country = svn_hash_gets(org, "C");
200  const char *email = svn_hash_gets(org, "E");
201  svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
202
203  if (org_unit)
204    {
205      svn_stringbuf_appendcstr(buf, org_unit);
206      svn_stringbuf_appendcstr(buf, ", ");
207    }
208
209  if (org_name)
210    {
211      svn_stringbuf_appendcstr(buf, org_name);
212      svn_stringbuf_appendcstr(buf, ", ");
213    }
214
215  if (locality)
216    {
217      svn_stringbuf_appendcstr(buf, locality);
218      svn_stringbuf_appendcstr(buf, ", ");
219    }
220
221  if (state)
222    {
223      svn_stringbuf_appendcstr(buf, state);
224      svn_stringbuf_appendcstr(buf, ", ");
225    }
226
227  if (country)
228    {
229      svn_stringbuf_appendcstr(buf, country);
230      svn_stringbuf_appendcstr(buf, ", ");
231    }
232
233  /* Chop ', ' if any. */
234  svn_stringbuf_chop(buf, 2);
235
236  if (email)
237    {
238      svn_stringbuf_appendcstr(buf, "(");
239      svn_stringbuf_appendcstr(buf, email);
240      svn_stringbuf_appendcstr(buf, ")");
241    }
242
243  return buf->data;
244}
245
246static void append_reason(svn_stringbuf_t *errmsg, const char *reason, int *reasons)
247{
248  if (*reasons < 1)
249    svn_stringbuf_appendcstr(errmsg, _(": "));
250  else
251    svn_stringbuf_appendcstr(errmsg, _(", "));
252  svn_stringbuf_appendcstr(errmsg, reason);
253  (*reasons)++;
254}
255
256/* This function is called on receiving a ssl certificate of a server when
257   opening a https connection. It allows Subversion to override the initial
258   validation done by serf.
259   Serf provides us the @a baton as provided in the call to
260   serf_ssl_server_cert_callback_set. The result of serf's initial validation
261   of the certificate @a CERT is returned as a bitmask in FAILURES. */
262static svn_error_t *
263ssl_server_cert(void *baton, int failures,
264                const serf_ssl_certificate_t *cert,
265                apr_pool_t *scratch_pool)
266{
267  svn_ra_serf__connection_t *conn = baton;
268  svn_auth_ssl_server_cert_info_t cert_info;
269  svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
270  svn_auth_iterstate_t *state;
271  const char *realmstring;
272  apr_uint32_t svn_failures;
273  apr_hash_t *issuer;
274  apr_hash_t *subject = NULL;
275  apr_hash_t *serf_cert = NULL;
276  void *creds;
277
278  svn_failures = (ssl_convert_serf_failures(failures)
279      | conn->server_cert_failures);
280
281  if (serf_ssl_cert_depth(cert) == 0)
282    {
283      /* If the depth is 0, the hostname must match the certificate.
284
285      ### This should really be handled by serf, which should pass an error
286          for this case, but that has backwards compatibility issues. */
287      apr_array_header_t *san;
288      svn_boolean_t found_san_entry = FALSE;
289      svn_boolean_t found_matching_hostname = FALSE;
290      svn_string_t *actual_hostname =
291          svn_string_create(conn->session->session_url.hostname, scratch_pool);
292
293      serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
294
295      san = svn_hash_gets(serf_cert, "subjectAltName");
296      /* Try to find matching server name via subjectAltName first... */
297      if (san)
298        {
299          int i;
300          found_san_entry = san->nelts > 0;
301          for (i = 0; i < san->nelts; i++)
302            {
303              const char *s = APR_ARRAY_IDX(san, i, const char*);
304              svn_string_t *cert_hostname = svn_string_create(s, scratch_pool);
305
306              if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
307                {
308                  found_matching_hostname = TRUE;
309                  break;
310                }
311            }
312        }
313
314      /* Match server certificate CN with the hostname of the server iff
315       * we didn't find any subjectAltName fields and try to match them.
316       * Per RFC 2818 they are authoritative if present and CommonName
317       * should be ignored. */
318      if (!found_matching_hostname && !found_san_entry)
319        {
320          const char *hostname = NULL;
321
322          subject = serf_ssl_cert_subject(cert, scratch_pool);
323
324          if (subject)
325            hostname = svn_hash_gets(subject, "CN");
326
327          if (hostname)
328            {
329              svn_string_t *cert_hostname = svn_string_create(hostname,
330                                                              scratch_pool);
331
332              if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
333                {
334                  found_matching_hostname = TRUE;
335                }
336            }
337        }
338
339      if (!found_matching_hostname)
340        svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
341    }
342
343  if (!svn_failures)
344    return SVN_NO_ERROR;
345
346  /* Extract the info from the certificate */
347  if (! subject)
348    subject = serf_ssl_cert_subject(cert, scratch_pool);
349  issuer = serf_ssl_cert_issuer(cert, scratch_pool);
350  if (! serf_cert)
351    serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
352
353  cert_info.hostname = svn_hash_gets(subject, "CN");
354  cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
355  if (! cert_info.fingerprint)
356    cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
357  cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
358  if (! cert_info.valid_from)
359    cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
360  cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
361  if (! cert_info.valid_until)
362    cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
363  cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
364  cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
365
366  /* Handle any non-server certs. */
367  if (serf_ssl_cert_depth(cert) > 0)
368    {
369      svn_error_t *err;
370
371      svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
372                             SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
373                             &cert_info);
374
375      svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
376                             SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
377                             &svn_failures);
378
379      realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s",
380                                 cert_info.fingerprint);
381
382      err = svn_auth_first_credentials(&creds, &state,
383                                       SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
384                                       realmstring,
385                                       conn->session->wc_callbacks->auth_baton,
386                                       scratch_pool);
387
388      svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
389                             SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
390
391      svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
392                             SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
393
394      if (err)
395        {
396          if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER)
397            return svn_error_trace(err);
398
399          /* No provider registered that handles server authorities */
400          svn_error_clear(err);
401          creds = NULL;
402        }
403
404      if (creds)
405        {
406          server_creds = creds;
407          SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
408
409          svn_failures &= ~server_creds->accepted_failures;
410        }
411
412      if (svn_failures)
413        conn->server_cert_failures |= svn_failures;
414
415      return APR_SUCCESS;
416    }
417
418  svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
419                         SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
420                         &svn_failures);
421
422  svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
423                         SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
424                         &cert_info);
425
426  realmstring = construct_realm(conn->session, conn->session->pool);
427
428  SVN_ERR(svn_auth_first_credentials(&creds, &state,
429                                     SVN_AUTH_CRED_SSL_SERVER_TRUST,
430                                     realmstring,
431                                     conn->session->wc_callbacks->auth_baton,
432                                     scratch_pool));
433  if (creds)
434    {
435      server_creds = creds;
436      svn_failures &= ~server_creds->accepted_failures;
437      SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
438    }
439
440  while (svn_failures && creds)
441    {
442      SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool));
443
444      if (creds)
445        {
446          server_creds = creds;
447          svn_failures &= ~server_creds->accepted_failures;
448          SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
449        }
450    }
451
452  svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton,
453                         SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
454
455  /* Are there non accepted failures left? */
456  if (svn_failures)
457    {
458      svn_stringbuf_t *errmsg;
459      int reasons = 0;
460
461      errmsg = svn_stringbuf_create(
462                 _("Server SSL certificate verification failed"),
463                 scratch_pool);
464
465
466      if (svn_failures & SVN_AUTH_SSL_NOTYETVALID)
467        append_reason(errmsg, _("certificate is not yet valid"), &reasons);
468
469      if (svn_failures & SVN_AUTH_SSL_EXPIRED)
470        append_reason(errmsg, _("certificate has expired"), &reasons);
471
472      if (svn_failures & SVN_AUTH_SSL_CNMISMATCH)
473        append_reason(errmsg,
474                      _("certificate issued for a different hostname"),
475                      &reasons);
476
477      if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA)
478        append_reason(errmsg, _("issuer is not trusted"), &reasons);
479
480      if (svn_failures & SVN_AUTH_SSL_OTHER)
481        append_reason(errmsg, _("and other reason(s)"), &reasons);
482
483      return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL,
484                              errmsg->data);
485    }
486
487  return SVN_NO_ERROR;
488}
489
490/* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
491static apr_status_t
492ssl_server_cert_cb(void *baton, int failures,
493                const serf_ssl_certificate_t *cert)
494{
495  svn_ra_serf__connection_t *conn = baton;
496  svn_ra_serf__session_t *session = conn->session;
497  apr_pool_t *subpool;
498  svn_error_t *err;
499
500  subpool = svn_pool_create(session->pool);
501  err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
502  svn_pool_destroy(subpool);
503
504  return save_error(session, err);
505}
506
507static svn_error_t *
508load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
509                 apr_pool_t *pool)
510{
511  apr_array_header_t *files = svn_cstring_split(authorities, ";",
512                                                TRUE /* chop_whitespace */,
513                                                pool);
514  int i;
515
516  for (i = 0; i < files->nelts; ++i)
517    {
518      const char *file = APR_ARRAY_IDX(files, i, const char *);
519      serf_ssl_certificate_t *ca_cert;
520      apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
521
522      if (status == APR_SUCCESS)
523        status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
524
525      if (status != APR_SUCCESS)
526        {
527          return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
528             _("Invalid config: unable to load certificate file '%s'"),
529             svn_dirent_local_style(file, pool));
530        }
531    }
532
533  return SVN_NO_ERROR;
534}
535
536static svn_error_t *
537conn_setup(apr_socket_t *sock,
538           serf_bucket_t **read_bkt,
539           serf_bucket_t **write_bkt,
540           void *baton,
541           apr_pool_t *pool)
542{
543  svn_ra_serf__connection_t *conn = baton;
544
545  *read_bkt = serf_context_bucket_socket_create(conn->session->context,
546                                               sock, conn->bkt_alloc);
547
548  if (conn->session->using_ssl)
549    {
550      /* input stream */
551      *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
552                                                 conn->bkt_alloc);
553      if (!conn->ssl_context)
554        {
555          conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
556
557          serf_ssl_set_hostname(conn->ssl_context,
558                                conn->session->session_url.hostname);
559
560          serf_ssl_client_cert_provider_set(conn->ssl_context,
561                                            svn_ra_serf__handle_client_cert,
562                                            conn, conn->session->pool);
563          serf_ssl_client_cert_password_set(conn->ssl_context,
564                                            svn_ra_serf__handle_client_cert_pw,
565                                            conn, conn->session->pool);
566          serf_ssl_server_cert_callback_set(conn->ssl_context,
567                                            ssl_server_cert_cb,
568                                            conn);
569
570          /* See if the user wants us to trust "default" openssl CAs. */
571          if (conn->session->trust_default_ca)
572            {
573              serf_ssl_use_default_certificates(conn->ssl_context);
574            }
575          /* Are there custom CAs to load? */
576          if (conn->session->ssl_authorities)
577            {
578              SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
579                                       conn->session->pool));
580            }
581        }
582
583      if (write_bkt)
584        {
585          /* output stream */
586          *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
587                                                      conn->ssl_context,
588                                                      conn->bkt_alloc);
589        }
590    }
591
592  return SVN_NO_ERROR;
593}
594
595/* svn_ra_serf__conn_setup is a callback for serf. This function
596   creates a read bucket and will wrap the write bucket if SSL
597   is needed. */
598apr_status_t
599svn_ra_serf__conn_setup(apr_socket_t *sock,
600                        serf_bucket_t **read_bkt,
601                        serf_bucket_t **write_bkt,
602                        void *baton,
603                        apr_pool_t *pool)
604{
605  svn_ra_serf__connection_t *conn = baton;
606  svn_ra_serf__session_t *session = conn->session;
607  svn_error_t *err;
608
609  err = svn_error_trace(conn_setup(sock,
610                                   read_bkt,
611                                   write_bkt,
612                                   baton,
613                                   pool));
614  return save_error(session, err);
615}
616
617
618/* Our default serf response acceptor.  */
619static serf_bucket_t *
620accept_response(serf_request_t *request,
621                serf_bucket_t *stream,
622                void *acceptor_baton,
623                apr_pool_t *pool)
624{
625  serf_bucket_t *c;
626  serf_bucket_alloc_t *bkt_alloc;
627
628  bkt_alloc = serf_request_get_alloc(request);
629  c = serf_bucket_barrier_create(stream, bkt_alloc);
630
631  return serf_bucket_response_create(c, bkt_alloc);
632}
633
634
635/* Custom response acceptor for HEAD requests.  */
636static serf_bucket_t *
637accept_head(serf_request_t *request,
638            serf_bucket_t *stream,
639            void *acceptor_baton,
640            apr_pool_t *pool)
641{
642  serf_bucket_t *response;
643
644  response = accept_response(request, stream, acceptor_baton, pool);
645
646  /* We know we shouldn't get a response body. */
647  serf_bucket_response_set_head(response);
648
649  return response;
650}
651
652static svn_error_t *
653connection_closed(svn_ra_serf__connection_t *conn,
654                  apr_status_t why,
655                  apr_pool_t *pool)
656{
657  if (why)
658    {
659      return svn_error_wrap_apr(why, NULL);
660    }
661
662  if (conn->session->using_ssl)
663    conn->ssl_context = NULL;
664
665  return SVN_NO_ERROR;
666}
667
668void
669svn_ra_serf__conn_closed(serf_connection_t *conn,
670                         void *closed_baton,
671                         apr_status_t why,
672                         apr_pool_t *pool)
673{
674  svn_ra_serf__connection_t *ra_conn = closed_baton;
675  svn_error_t *err;
676
677  err = svn_error_trace(connection_closed(ra_conn, why, pool));
678
679  (void) save_error(ra_conn->session, err);
680}
681
682
683/* Implementation of svn_ra_serf__handle_client_cert */
684static svn_error_t *
685handle_client_cert(void *data,
686                   const char **cert_path,
687                   apr_pool_t *pool)
688{
689    svn_ra_serf__connection_t *conn = data;
690    svn_ra_serf__session_t *session = conn->session;
691    const char *realm;
692    void *creds;
693
694    *cert_path = NULL;
695
696    realm = construct_realm(session, session->pool);
697
698    if (!conn->ssl_client_auth_state)
699      {
700        SVN_ERR(svn_auth_first_credentials(&creds,
701                                           &conn->ssl_client_auth_state,
702                                           SVN_AUTH_CRED_SSL_CLIENT_CERT,
703                                           realm,
704                                           session->wc_callbacks->auth_baton,
705                                           pool));
706      }
707    else
708      {
709        SVN_ERR(svn_auth_next_credentials(&creds,
710                                          conn->ssl_client_auth_state,
711                                          session->pool));
712      }
713
714    if (creds)
715      {
716        svn_auth_cred_ssl_client_cert_t *client_creds;
717        client_creds = creds;
718        *cert_path = client_creds->cert_file;
719      }
720
721    return SVN_NO_ERROR;
722}
723
724/* Implements serf_ssl_need_client_cert_t for handle_client_cert */
725apr_status_t svn_ra_serf__handle_client_cert(void *data,
726                                             const char **cert_path)
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(data, cert_path, session->pool));
733
734  return save_error(session, err);
735}
736
737/* Implementation for svn_ra_serf__handle_client_cert_pw */
738static svn_error_t *
739handle_client_cert_pw(void *data,
740                      const char *cert_path,
741                      const char **password,
742                      apr_pool_t *pool)
743{
744    svn_ra_serf__connection_t *conn = data;
745    svn_ra_serf__session_t *session = conn->session;
746    void *creds;
747
748    *password = NULL;
749
750    if (!conn->ssl_client_pw_auth_state)
751      {
752        SVN_ERR(svn_auth_first_credentials(&creds,
753                                           &conn->ssl_client_pw_auth_state,
754                                           SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
755                                           cert_path,
756                                           session->wc_callbacks->auth_baton,
757                                           pool));
758      }
759    else
760      {
761        SVN_ERR(svn_auth_next_credentials(&creds,
762                                          conn->ssl_client_pw_auth_state,
763                                          pool));
764      }
765
766    if (creds)
767      {
768        svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
769        pw_creds = creds;
770        *password = pw_creds->password;
771      }
772
773    return APR_SUCCESS;
774}
775
776/* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
777apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
778                                                const char *cert_path,
779                                                const char **password)
780{
781  svn_ra_serf__connection_t *conn = data;
782  svn_ra_serf__session_t *session = conn->session;
783  svn_error_t *err;
784
785  err = svn_error_trace(handle_client_cert_pw(data,
786                                              cert_path,
787                                              password,
788                                              session->pool));
789
790  return save_error(session, err);
791}
792
793
794/*
795 * Given a REQUEST on connection CONN, construct a request bucket for it,
796 * returning the bucket in *REQ_BKT.
797 *
798 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
799 * corresponds to the new request.
800 *
801 * The request will be METHOD at URL.
802 *
803 * If BODY_BKT is not-NULL, it will be sent as the request body.
804 *
805 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
806 *
807 * REQUEST_POOL should live for the duration of the request. Serf will
808 * construct this and provide it to the request_setup callback, so we
809 * should just use that one.
810 */
811static svn_error_t *
812setup_serf_req(serf_request_t *request,
813               serf_bucket_t **req_bkt,
814               serf_bucket_t **hdrs_bkt,
815               svn_ra_serf__session_t *session,
816               const char *method, const char *url,
817               serf_bucket_t *body_bkt, const char *content_type,
818               const char *accept_encoding,
819               apr_pool_t *request_pool,
820               apr_pool_t *scratch_pool)
821{
822  serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
823
824  svn_spillbuf_t *buf;
825  svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
826
827  if (set_CL && body_bkt != NULL)
828    {
829      /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
830         it speaks HTTP/1.1 (and thus, chunked requests), or because the
831         server actually responded as only supporting HTTP/1.0.
832
833         We'll take the existing body_bkt, spool it into a spillbuf, and
834         then wrap a bucket around that spillbuf. The spillbuf will give
835         us the Content-Length value.  */
836      SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
837                                              request_pool,
838                                              scratch_pool));
839      /* Destroy original bucket since it content is already copied
840         to spillbuf. */
841      serf_bucket_destroy(body_bkt);
842
843      body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
844                                               request_pool,
845                                               scratch_pool);
846    }
847
848  /* Create a request bucket.  Note that this sucker is kind enough to
849     add a "Host" header for us.  */
850  *req_bkt = serf_request_bucket_request_create(request, method, url,
851                                                body_bkt, allocator);
852
853  /* Set the Content-Length value. This will also trigger an HTTP/1.0
854     request (rather than the default chunked request).  */
855  if (set_CL)
856    {
857      if (body_bkt == NULL)
858        serf_bucket_request_set_CL(*req_bkt, 0);
859      else
860        serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
861    }
862
863  *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
864
865  /* We use serf_bucket_headers_setn() because the USERAGENT has a
866     lifetime longer than this bucket. Thus, there is no need to copy
867     the header values.  */
868  serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
869
870  if (content_type)
871    {
872      serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
873    }
874
875  if (session->http10)
876    {
877      serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
878    }
879
880  if (accept_encoding)
881    {
882      serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
883    }
884
885  /* These headers need to be sent with every request; see issue #3255
886     ("mod_dav_svn does not pass client capabilities to start-commit
887     hooks") for why. */
888  serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
889  serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
890  serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
891
892  return SVN_NO_ERROR;
893}
894
895svn_error_t *
896svn_ra_serf__context_run_wait(svn_boolean_t *done,
897                              svn_ra_serf__session_t *sess,
898                              apr_pool_t *scratch_pool)
899{
900  apr_pool_t *iterpool;
901  apr_interval_time_t waittime_left = sess->timeout;
902
903  assert(sess->pending_error == SVN_NO_ERROR);
904
905  iterpool = svn_pool_create(scratch_pool);
906  while (!*done)
907    {
908      apr_status_t status;
909      svn_error_t *err;
910      int i;
911
912      svn_pool_clear(iterpool);
913
914      if (sess->cancel_func)
915        SVN_ERR((*sess->cancel_func)(sess->cancel_baton));
916
917      status = serf_context_run(sess->context,
918                                SVN_RA_SERF__CONTEXT_RUN_DURATION,
919                                iterpool);
920
921      err = sess->pending_error;
922      sess->pending_error = SVN_NO_ERROR;
923
924      /* If the context duration timeout is up, we'll subtract that
925         duration from the total time alloted for such things.  If
926         there's no time left, we fail with a message indicating that
927         the connection timed out.  */
928      if (APR_STATUS_IS_TIMEUP(status))
929        {
930          status = 0;
931
932          if (sess->timeout)
933            {
934              if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
935                {
936                  waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
937                }
938              else
939                {
940                  return
941                      svn_error_compose_create(
942                            err,
943                            svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
944                                             _("Connection timed out")));
945                }
946            }
947        }
948      else
949        {
950          waittime_left = sess->timeout;
951        }
952
953      SVN_ERR(err);
954      if (status)
955        {
956          if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
957            {
958              /* apr can't translate subversion errors to text */
959              SVN_ERR_W(svn_error_create(status, NULL, NULL),
960                        _("Error running context"));
961            }
962
963          return svn_ra_serf__wrap_err(status, _("Error running context"));
964        }
965
966      /* Debugging purposes only! */
967      for (i = 0; i < sess->num_conns; i++)
968        {
969          serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
970        }
971    }
972  svn_pool_destroy(iterpool);
973
974  return SVN_NO_ERROR;
975}
976
977
978svn_error_t *
979svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
980                             apr_pool_t *scratch_pool)
981{
982  svn_error_t *err;
983
984  /* Create a serf request based on HANDLER.  */
985  svn_ra_serf__request_create(handler);
986
987  /* Wait until the response logic marks its DONE status.  */
988  err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
989                                      scratch_pool);
990
991  /* A callback invocation has been canceled. In this simple case of
992     context_run_one, we can keep the ra-session operational by resetting
993     the connection.
994
995     If we don't do this, the next context run will notice that the connection
996     is still in the error state and will just return SVN_ERR_CEASE_INVOCATION
997     (=the last error for the connection) again  */
998  if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION)
999    {
1000      apr_status_t status = serf_connection_reset(handler->conn->conn);
1001
1002      if (status)
1003        err = svn_error_compose_create(err,
1004                                       svn_ra_serf__wrap_err(status, NULL));
1005    }
1006
1007  if (handler->server_error)
1008    {
1009      err = svn_error_compose_create(err, handler->server_error->error);
1010      handler->server_error = NULL;
1011    }
1012
1013  return svn_error_trace(err);
1014}
1015
1016
1017/*
1018 * Expat callback invoked on a start element tag for an error response.
1019 */
1020static svn_error_t *
1021start_error(svn_ra_serf__xml_parser_t *parser,
1022            svn_ra_serf__dav_props_t name,
1023            const char **attrs,
1024            apr_pool_t *scratch_pool)
1025{
1026  svn_ra_serf__server_error_t *ctx = parser->user_data;
1027
1028  if (!ctx->in_error &&
1029      strcmp(name.namespace, "DAV:") == 0 &&
1030      strcmp(name.name, "error") == 0)
1031    {
1032      ctx->in_error = TRUE;
1033    }
1034  else if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
1035    {
1036      const char *err_code;
1037
1038      err_code = svn_xml_get_attr_value("errcode", attrs);
1039      if (err_code)
1040        {
1041          apr_int64_t val;
1042
1043          SVN_ERR(svn_cstring_atoi64(&val, err_code));
1044          ctx->error->apr_err = (apr_status_t)val;
1045        }
1046
1047      /* If there's no error code provided, or if the provided code is
1048         0 (which can happen sometimes depending on how the error is
1049         constructed on the server-side), just pick a generic error
1050         code to run with. */
1051      if (! ctx->error->apr_err)
1052        {
1053          ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
1054        }
1055
1056      /* Start collecting cdata. */
1057      svn_stringbuf_setempty(ctx->cdata);
1058      ctx->collect_cdata = TRUE;
1059    }
1060
1061  return SVN_NO_ERROR;
1062}
1063
1064/*
1065 * Expat callback invoked on an end element tag for a PROPFIND response.
1066 */
1067static svn_error_t *
1068end_error(svn_ra_serf__xml_parser_t *parser,
1069          svn_ra_serf__dav_props_t name,
1070          apr_pool_t *scratch_pool)
1071{
1072  svn_ra_serf__server_error_t *ctx = parser->user_data;
1073
1074  if (ctx->in_error &&
1075      strcmp(name.namespace, "DAV:") == 0 &&
1076      strcmp(name.name, "error") == 0)
1077    {
1078      ctx->in_error = FALSE;
1079    }
1080  if (ctx->in_error && strcmp(name.name, "human-readable") == 0)
1081    {
1082      /* On the server dav_error_response_tag() will add a leading
1083         and trailing newline if DEBUG_CR is defined in mod_dav.h,
1084         so remove any such characters here. */
1085      svn_stringbuf_strip_whitespace(ctx->cdata);
1086
1087      ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
1088                                           ctx->cdata->len);
1089      ctx->collect_cdata = FALSE;
1090    }
1091
1092  return SVN_NO_ERROR;
1093}
1094
1095/*
1096 * Expat callback invoked on CDATA elements in an error response.
1097 *
1098 * This callback can be called multiple times.
1099 */
1100static svn_error_t *
1101cdata_error(svn_ra_serf__xml_parser_t *parser,
1102            const char *data,
1103            apr_size_t len,
1104            apr_pool_t *scratch_pool)
1105{
1106  svn_ra_serf__server_error_t *ctx = parser->user_data;
1107
1108  if (ctx->collect_cdata)
1109    {
1110      svn_stringbuf_appendbytes(ctx->cdata, data, len);
1111    }
1112
1113  return SVN_NO_ERROR;
1114}
1115
1116
1117static apr_status_t
1118drain_bucket(serf_bucket_t *bucket)
1119{
1120  /* Read whatever is in the bucket, and just drop it.  */
1121  while (1)
1122    {
1123      apr_status_t status;
1124      const char *data;
1125      apr_size_t len;
1126
1127      status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
1128      if (status)
1129        return status;
1130    }
1131}
1132
1133
1134static svn_ra_serf__server_error_t *
1135begin_error_parsing(svn_ra_serf__xml_start_element_t start,
1136                    svn_ra_serf__xml_end_element_t end,
1137                    svn_ra_serf__xml_cdata_chunk_handler_t cdata,
1138                    apr_pool_t *result_pool)
1139{
1140  svn_ra_serf__server_error_t *server_err;
1141
1142  server_err = apr_pcalloc(result_pool, sizeof(*server_err));
1143  server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL);
1144  server_err->contains_precondition_error = FALSE;
1145  server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool);
1146  server_err->collect_cdata = FALSE;
1147  server_err->parser.pool = server_err->error->pool;
1148  server_err->parser.user_data = server_err;
1149  server_err->parser.start = start;
1150  server_err->parser.end = end;
1151  server_err->parser.cdata = cdata;
1152  server_err->parser.ignore_errors = TRUE;
1153
1154  return server_err;
1155}
1156
1157/* Implements svn_ra_serf__response_handler_t */
1158svn_error_t *
1159svn_ra_serf__handle_discard_body(serf_request_t *request,
1160                                 serf_bucket_t *response,
1161                                 void *baton,
1162                                 apr_pool_t *pool)
1163{
1164  apr_status_t status;
1165
1166  status = drain_bucket(response);
1167  if (status)
1168    return svn_ra_serf__wrap_err(status, NULL);
1169
1170  return SVN_NO_ERROR;
1171}
1172
1173apr_status_t
1174svn_ra_serf__response_discard_handler(serf_request_t *request,
1175                                      serf_bucket_t *response,
1176                                      void *baton,
1177                                      apr_pool_t *pool)
1178{
1179  return drain_bucket(response);
1180}
1181
1182
1183/* Return the value of the RESPONSE's Location header if any, or NULL
1184   otherwise.  */
1185static const char *
1186response_get_location(serf_bucket_t *response,
1187                      const char *base_url,
1188                      apr_pool_t *result_pool,
1189                      apr_pool_t *scratch_pool)
1190{
1191  serf_bucket_t *headers;
1192  const char *location;
1193
1194  headers = serf_bucket_response_get_headers(response);
1195  location = serf_bucket_headers_get(headers, "Location");
1196  if (location == NULL)
1197    return NULL;
1198
1199  /* The RFCs say we should have received a full url in LOCATION, but
1200     older apache versions and many custom web handlers just return a
1201     relative path here...
1202
1203     And we can't trust anything because it is network data.
1204   */
1205  if (*location == '/')
1206    {
1207      apr_uri_t uri;
1208      apr_status_t status;
1209
1210      status = apr_uri_parse(scratch_pool, base_url, &uri);
1211
1212      if (status != APR_SUCCESS)
1213        return NULL;
1214
1215      /* Replace the path path with what we got */
1216      uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
1217
1218      /* And make APR produce a proper full url for us */
1219      location = apr_uri_unparse(scratch_pool, &uri, 0);
1220
1221      /* Fall through to ensure our canonicalization rules */
1222    }
1223  else if (!svn_path_is_url(location))
1224    {
1225      return NULL; /* Any other formats we should support? */
1226    }
1227
1228  return svn_uri_canonicalize(location, result_pool);
1229}
1230
1231
1232/* Implements svn_ra_serf__response_handler_t */
1233svn_error_t *
1234svn_ra_serf__expect_empty_body(serf_request_t *request,
1235                               serf_bucket_t *response,
1236                               void *baton,
1237                               apr_pool_t *scratch_pool)
1238{
1239  svn_ra_serf__handler_t *handler = baton;
1240  serf_bucket_t *hdrs;
1241  const char *val;
1242
1243  /* This function is just like handle_multistatus_only() except for the
1244     XML parsing callbacks. We want to look for the human-readable element.  */
1245
1246  /* We should see this just once, in order to initialize SERVER_ERROR.
1247     At that point, the core error processing will take over. If we choose
1248     not to parse an error, then we'll never return here (because we
1249     change the response handler).  */
1250  SVN_ERR_ASSERT(handler->server_error == NULL);
1251
1252  hdrs = serf_bucket_response_get_headers(response);
1253  val = serf_bucket_headers_get(hdrs, "Content-Type");
1254  if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1255    {
1256      svn_ra_serf__server_error_t *server_err;
1257
1258      server_err = begin_error_parsing(start_error, end_error, cdata_error,
1259                                       handler->handler_pool);
1260
1261      /* Get the parser to set our DONE flag.  */
1262      server_err->parser.done = &handler->done;
1263
1264      handler->server_error = server_err;
1265    }
1266  else
1267    {
1268      /* The body was not text/xml, so we don't know what to do with it.
1269         Toss anything that arrives.  */
1270      handler->discard_body = TRUE;
1271    }
1272
1273  /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1274     to call the response handler again. That will start up the XML parsing,
1275     or it will be dropped on the floor (per the decision above).  */
1276  return SVN_NO_ERROR;
1277}
1278
1279
1280/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric
1281   status code into *STATUS_CODE_OUT.  Ignores leading whitespace. */
1282static svn_error_t *
1283parse_dav_status(int *status_code_out, svn_stringbuf_t *buf,
1284                 apr_pool_t *scratch_pool)
1285{
1286  svn_error_t *err;
1287  const char *token;
1288  char *tok_status;
1289  svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool);
1290
1291  svn_stringbuf_strip_whitespace(temp_buf);
1292  token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status);
1293  if (token)
1294    token = apr_strtok(NULL, " \t\r\n", &tok_status);
1295  if (!token)
1296    return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1297                             _("Malformed DAV:status CDATA '%s'"),
1298                             buf->data);
1299  err = svn_cstring_atoi(status_code_out, token);
1300  if (err)
1301    return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err,
1302                             _("Malformed DAV:status CDATA '%s'"),
1303                             buf->data);
1304
1305  return SVN_NO_ERROR;
1306}
1307
1308/*
1309 * Expat callback invoked on a start element tag for a 207 response.
1310 */
1311static svn_error_t *
1312start_207(svn_ra_serf__xml_parser_t *parser,
1313          svn_ra_serf__dav_props_t name,
1314          const char **attrs,
1315          apr_pool_t *scratch_pool)
1316{
1317  svn_ra_serf__server_error_t *ctx = parser->user_data;
1318
1319  if (!ctx->in_error &&
1320      strcmp(name.namespace, "DAV:") == 0 &&
1321      strcmp(name.name, "multistatus") == 0)
1322    {
1323      ctx->in_error = TRUE;
1324    }
1325  else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
1326    {
1327      /* Start collecting cdata. */
1328      svn_stringbuf_setempty(ctx->cdata);
1329      ctx->collect_cdata = TRUE;
1330    }
1331  else if (ctx->in_error &&
1332           strcmp(name.namespace, "DAV:") == 0 &&
1333           strcmp(name.name, "status") == 0)
1334    {
1335      /* Start collecting cdata. */
1336      svn_stringbuf_setempty(ctx->cdata);
1337      ctx->collect_cdata = TRUE;
1338    }
1339
1340  return SVN_NO_ERROR;
1341}
1342
1343/*
1344 * Expat callback invoked on an end element tag for a 207 response.
1345 */
1346static svn_error_t *
1347end_207(svn_ra_serf__xml_parser_t *parser,
1348        svn_ra_serf__dav_props_t name,
1349        apr_pool_t *scratch_pool)
1350{
1351  svn_ra_serf__server_error_t *ctx = parser->user_data;
1352
1353  if (ctx->in_error &&
1354      strcmp(name.namespace, "DAV:") == 0 &&
1355      strcmp(name.name, "multistatus") == 0)
1356    {
1357      ctx->in_error = FALSE;
1358    }
1359  if (ctx->in_error && strcmp(name.name, "responsedescription") == 0)
1360    {
1361      /* Remove leading newline added by DEBUG_CR on server */
1362      svn_stringbuf_strip_whitespace(ctx->cdata);
1363
1364      ctx->collect_cdata = FALSE;
1365      ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data,
1366                                           ctx->cdata->len);
1367      if (ctx->contains_precondition_error)
1368        ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH;
1369      else
1370        ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
1371    }
1372  else if (ctx->in_error &&
1373           strcmp(name.namespace, "DAV:") == 0 &&
1374           strcmp(name.name, "status") == 0)
1375    {
1376      int status_code;
1377
1378      ctx->collect_cdata = FALSE;
1379
1380      SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool));
1381      if (status_code == 412)
1382        ctx->contains_precondition_error = TRUE;
1383    }
1384
1385  return SVN_NO_ERROR;
1386}
1387
1388/*
1389 * Expat callback invoked on CDATA elements in a 207 response.
1390 *
1391 * This callback can be called multiple times.
1392 */
1393static svn_error_t *
1394cdata_207(svn_ra_serf__xml_parser_t *parser,
1395          const char *data,
1396          apr_size_t len,
1397          apr_pool_t *scratch_pool)
1398{
1399  svn_ra_serf__server_error_t *ctx = parser->user_data;
1400
1401  if (ctx->collect_cdata)
1402    {
1403      svn_stringbuf_appendbytes(ctx->cdata, data, len);
1404    }
1405
1406  return SVN_NO_ERROR;
1407}
1408
1409/* Implements svn_ra_serf__response_handler_t */
1410svn_error_t *
1411svn_ra_serf__handle_multistatus_only(serf_request_t *request,
1412                                     serf_bucket_t *response,
1413                                     void *baton,
1414                                     apr_pool_t *scratch_pool)
1415{
1416  svn_ra_serf__handler_t *handler = baton;
1417
1418  /* This function is just like expect_empty_body() except for the
1419     XML parsing callbacks. We are looking for very limited pieces of
1420     the multistatus response.  */
1421
1422  /* We should see this just once, in order to initialize SERVER_ERROR.
1423     At that point, the core error processing will take over. If we choose
1424     not to parse an error, then we'll never return here (because we
1425     change the response handler).  */
1426  SVN_ERR_ASSERT(handler->server_error == NULL);
1427
1428    {
1429      serf_bucket_t *hdrs;
1430      const char *val;
1431
1432      hdrs = serf_bucket_response_get_headers(response);
1433      val = serf_bucket_headers_get(hdrs, "Content-Type");
1434      if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1435        {
1436          svn_ra_serf__server_error_t *server_err;
1437
1438          server_err = begin_error_parsing(start_207, end_207, cdata_207,
1439                                           handler->handler_pool);
1440
1441          /* Get the parser to set our DONE flag.  */
1442          server_err->parser.done = &handler->done;
1443
1444          handler->server_error = server_err;
1445        }
1446      else
1447        {
1448          /* The body was not text/xml, so we don't know what to do with it.
1449             Toss anything that arrives.  */
1450          handler->discard_body = TRUE;
1451        }
1452    }
1453
1454  /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1455     to call the response handler again. That will start up the XML parsing,
1456     or it will be dropped on the floor (per the decision above).  */
1457  return SVN_NO_ERROR;
1458}
1459
1460
1461/* Conforms to Expat's XML_StartElementHandler  */
1462static void
1463start_xml(void *userData, const char *raw_name, const char **attrs)
1464{
1465  svn_ra_serf__xml_parser_t *parser = userData;
1466  svn_ra_serf__dav_props_t name;
1467  apr_pool_t *scratch_pool;
1468  svn_error_t *err;
1469
1470  if (parser->error)
1471    return;
1472
1473  if (!parser->state)
1474    svn_ra_serf__xml_push_state(parser, 0);
1475
1476  /* ### get a real scratch_pool  */
1477  scratch_pool = parser->state->pool;
1478
1479  svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool);
1480
1481  svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
1482
1483  err = parser->start(parser, name, attrs, scratch_pool);
1484  if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1485    err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1486
1487  parser->error = err;
1488}
1489
1490
1491/* Conforms to Expat's XML_EndElementHandler  */
1492static void
1493end_xml(void *userData, const char *raw_name)
1494{
1495  svn_ra_serf__xml_parser_t *parser = userData;
1496  svn_ra_serf__dav_props_t name;
1497  svn_error_t *err;
1498  apr_pool_t *scratch_pool;
1499
1500  if (parser->error)
1501    return;
1502
1503  /* ### get a real scratch_pool  */
1504  scratch_pool = parser->state->pool;
1505
1506  svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name);
1507
1508  err = parser->end(parser, name, scratch_pool);
1509  if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1510    err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1511
1512  parser->error = err;
1513}
1514
1515
1516/* Conforms to Expat's XML_CharacterDataHandler  */
1517static void
1518cdata_xml(void *userData, const char *data, int len)
1519{
1520  svn_ra_serf__xml_parser_t *parser = userData;
1521  svn_error_t *err;
1522  apr_pool_t *scratch_pool;
1523
1524  if (parser->error)
1525    return;
1526
1527  if (!parser->state)
1528    svn_ra_serf__xml_push_state(parser, 0);
1529
1530  /* ### get a real scratch_pool  */
1531  scratch_pool = parser->state->pool;
1532
1533  err = parser->cdata(parser, data, len, scratch_pool);
1534  if (err && !SERF_BUCKET_READ_ERROR(err->apr_err))
1535    err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL);
1536
1537  parser->error = err;
1538}
1539
1540/* Flip the requisite bits in CTX to indicate that processing of the
1541   response is complete, adding the current "done item" to the list of
1542   completed items. */
1543static void
1544add_done_item(svn_ra_serf__xml_parser_t *ctx)
1545{
1546  /* Make sure we don't add to DONE_LIST twice.  */
1547  if (!*ctx->done)
1548    {
1549      *ctx->done = TRUE;
1550      if (ctx->done_list)
1551        {
1552          ctx->done_item->data = ctx->user_data;
1553          ctx->done_item->next = *ctx->done_list;
1554          *ctx->done_list = ctx->done_item;
1555        }
1556    }
1557}
1558
1559
1560static svn_error_t *
1561write_to_pending(svn_ra_serf__xml_parser_t *ctx,
1562                 const char *data,
1563                 apr_size_t len,
1564                 apr_pool_t *scratch_pool)
1565{
1566  if (ctx->pending == NULL)
1567    {
1568      ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending));
1569      ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE,
1570                                               SPILL_SIZE,
1571                                               ctx->pool);
1572    }
1573
1574  /* Copy the data into one or more chunks in the spill buffer.  */
1575  return svn_error_trace(svn_spillbuf__write(ctx->pending->buf,
1576                                             data, len,
1577                                             scratch_pool));
1578}
1579
1580
1581static svn_error_t *
1582inject_to_parser(svn_ra_serf__xml_parser_t *ctx,
1583                 const char *data,
1584                 apr_size_t len,
1585                 const serf_status_line *sl)
1586{
1587  int xml_status;
1588
1589  xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0);
1590
1591  if (! ctx->ignore_errors)
1592    {
1593      SVN_ERR(ctx->error);
1594
1595      if (xml_status != XML_STATUS_OK)
1596        {
1597          if (sl == NULL)
1598            return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1599                                     _("XML parsing failed"));
1600
1601          return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1602                                   _("XML parsing failed: (%d %s)"),
1603                                   sl->code, sl->reason);
1604        }
1605     }
1606
1607  return SVN_NO_ERROR;
1608}
1609
1610/* Apr pool cleanup handler to release an XML_Parser in success and error
1611   conditions */
1612static apr_status_t
1613xml_parser_cleanup(void *baton)
1614{
1615  XML_Parser *xmlp = baton;
1616
1617  if (*xmlp)
1618    {
1619      (void) XML_ParserFree(*xmlp);
1620      *xmlp = NULL;
1621    }
1622
1623  return APR_SUCCESS;
1624}
1625
1626/* Limit the amount of pending content to parse at once to < 100KB per
1627   iteration. This number is chosen somewhat arbitrarely. Making it lower
1628   will have a drastical negative impact on performance, whereas increasing it
1629   increases the risk for connection timeouts.
1630 */
1631#define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5
1632
1633svn_error_t *
1634svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser,
1635                             svn_boolean_t *network_eof,
1636                             apr_pool_t *scratch_pool)
1637{
1638  svn_boolean_t pending_empty = FALSE;
1639  apr_size_t cur_read = 0;
1640
1641  /* Fast path exit: already paused, nothing to do, or already done.  */
1642  if (parser->paused || parser->pending == NULL || *parser->done)
1643    {
1644      *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
1645      return SVN_NO_ERROR;
1646    }
1647
1648  /* Parsing the pending conten in the spillbuf will result in many disc i/o
1649     operations. This can be so slow that we don't run the network event
1650     processing loop often enough, resulting in timed out connections.
1651
1652     So we limit the amounts of bytes parsed per iteration.
1653   */
1654  while (cur_read < PENDING_TO_PARSE)
1655    {
1656      const char *data;
1657      apr_size_t len;
1658
1659      /* Get a block of content, stopping the loop when we run out.  */
1660      SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf,
1661                             scratch_pool));
1662      if (data)
1663        {
1664          /* Inject the content into the XML parser.  */
1665          SVN_ERR(inject_to_parser(parser, data, len, NULL));
1666
1667          /* If the XML parsing callbacks paused us, then we're done for now.  */
1668          if (parser->paused)
1669            break;
1670
1671          cur_read += len;
1672        }
1673      else
1674        {
1675          /* The buffer is empty. */
1676          pending_empty = TRUE;
1677          break;
1678        }
1679    }
1680
1681  /* If the PENDING structures are empty *and* we consumed all content from
1682     the network, then we're completely done with the parsing.  */
1683  if (pending_empty &&
1684      parser->pending->network_eof)
1685    {
1686      int xml_status;
1687      SVN_ERR_ASSERT(parser->xmlp != NULL);
1688
1689      /* Tell the parser that no more content will be parsed. */
1690      xml_status = XML_Parse(parser->xmlp, NULL, 0, 1);
1691
1692      apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup);
1693      parser->xmlp = NULL;
1694
1695      if (! parser->ignore_errors)
1696        {
1697          SVN_ERR(parser->error);
1698
1699          if (xml_status != XML_STATUS_OK)
1700            {
1701              return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
1702                                       _("XML parsing failed"));
1703            }
1704        }
1705
1706      add_done_item(parser);
1707    }
1708
1709  *network_eof = parser->pending ? parser->pending->network_eof : FALSE;
1710
1711  return SVN_NO_ERROR;
1712}
1713#undef PENDING_TO_PARSE
1714
1715
1716/* ### this is still broken conceptually. just shifting incrementally... */
1717static svn_error_t *
1718handle_server_error(serf_request_t *request,
1719                    serf_bucket_t *response,
1720                    apr_pool_t *scratch_pool)
1721{
1722  svn_ra_serf__server_error_t server_err = { 0 };
1723  serf_bucket_t *hdrs;
1724  const char *val;
1725  apr_status_t err;
1726
1727  hdrs = serf_bucket_response_get_headers(response);
1728  val = serf_bucket_headers_get(hdrs, "Content-Type");
1729  if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1730    {
1731      /* ### we should figure out how to reuse begin_error_parsing  */
1732
1733      server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL);
1734      server_err.contains_precondition_error = FALSE;
1735      server_err.cdata = svn_stringbuf_create_empty(scratch_pool);
1736      server_err.collect_cdata = FALSE;
1737      server_err.parser.pool = server_err.error->pool;
1738      server_err.parser.user_data = &server_err;
1739      server_err.parser.start = start_error;
1740      server_err.parser.end = end_error;
1741      server_err.parser.cdata = cdata_error;
1742      server_err.parser.done = &server_err.done;
1743      server_err.parser.ignore_errors = TRUE;
1744
1745      /* We don't care about any errors except for SERVER_ERR.ERROR  */
1746      svn_error_clear(svn_ra_serf__handle_xml_parser(request,
1747                                                     response,
1748                                                     &server_err.parser,
1749                                                     scratch_pool));
1750
1751      /* ### checking DONE is silly. the above only parses whatever has
1752         ### been received at the network interface. totally wrong. but
1753         ### it is what we have for now (maintaining historical code),
1754         ### until we fully migrate.  */
1755      if (server_err.done && server_err.error->apr_err == APR_SUCCESS)
1756        {
1757          svn_error_clear(server_err.error);
1758          server_err.error = SVN_NO_ERROR;
1759        }
1760
1761      return svn_error_trace(server_err.error);
1762    }
1763
1764  /* The only error that we will return is from the XML response body.
1765     Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to
1766     surface. */
1767  err = drain_bucket(response);
1768  if (err && !SERF_BUCKET_READ_ERROR(err))
1769    return svn_ra_serf__wrap_err(err, NULL);
1770
1771  return SVN_NO_ERROR;
1772}
1773
1774
1775/* Implements svn_ra_serf__response_handler_t */
1776svn_error_t *
1777svn_ra_serf__handle_xml_parser(serf_request_t *request,
1778                               serf_bucket_t *response,
1779                               void *baton,
1780                               apr_pool_t *pool)
1781{
1782  serf_status_line sl;
1783  apr_status_t status;
1784  svn_ra_serf__xml_parser_t *ctx = baton;
1785  svn_error_t *err;
1786
1787  /* ### get the HANDLER rather than fetching this.  */
1788  status = serf_bucket_response_status(response, &sl);
1789  if (SERF_BUCKET_READ_ERROR(status))
1790    {
1791      return svn_ra_serf__wrap_err(status, NULL);
1792    }
1793
1794  /* Woo-hoo.  Nothing here to see.  */
1795  if (sl.code == 404 && !ctx->ignore_errors)
1796    {
1797      err = handle_server_error(request, response, pool);
1798
1799      if (err && APR_STATUS_IS_EOF(err->apr_err))
1800        add_done_item(ctx);
1801
1802      return svn_error_trace(err);
1803    }
1804
1805  if (!ctx->xmlp)
1806    {
1807      ctx->xmlp = XML_ParserCreate(NULL);
1808      apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup,
1809                                apr_pool_cleanup_null);
1810      XML_SetUserData(ctx->xmlp, ctx);
1811      XML_SetElementHandler(ctx->xmlp, start_xml, end_xml);
1812      if (ctx->cdata)
1813        {
1814          XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml);
1815        }
1816    }
1817
1818  while (1)
1819    {
1820      const char *data;
1821      apr_size_t len;
1822
1823      status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
1824      if (SERF_BUCKET_READ_ERROR(status))
1825        {
1826          return svn_ra_serf__wrap_err(status, NULL);
1827        }
1828
1829      /* Note: once the callbacks invoked by inject_to_parser() sets the
1830         PAUSED flag, then it will not be cleared. write_to_pending() will
1831         only save the content. Logic outside of serf_context_run() will
1832         clear that flag, as appropriate, along with processing the
1833         content that we have placed into the PENDING buffer.
1834
1835         We want to save arriving content into the PENDING structures if
1836         the parser has been paused, or we already have data in there (so
1837         the arriving data is appended, rather than injected out of order)  */
1838      if (ctx->paused || HAS_PENDING_DATA(ctx->pending))
1839        {
1840          err = write_to_pending(ctx, data, len, pool);
1841        }
1842      else
1843        {
1844          err = inject_to_parser(ctx, data, len, &sl);
1845          if (err)
1846            {
1847              /* Should have no errors if IGNORE_ERRORS is set.  */
1848              SVN_ERR_ASSERT(!ctx->ignore_errors);
1849            }
1850        }
1851      if (err)
1852        {
1853          SVN_ERR_ASSERT(ctx->xmlp != NULL);
1854
1855          apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
1856          add_done_item(ctx);
1857          return svn_error_trace(err);
1858        }
1859
1860      if (APR_STATUS_IS_EAGAIN(status))
1861        {
1862          return svn_ra_serf__wrap_err(status, NULL);
1863        }
1864
1865      if (APR_STATUS_IS_EOF(status))
1866        {
1867          if (ctx->pending != NULL)
1868            ctx->pending->network_eof = TRUE;
1869
1870          /* We just hit the end of the network content. If we have nothing
1871             in the PENDING structures, then we're completely done.  */
1872          if (!HAS_PENDING_DATA(ctx->pending))
1873            {
1874              int xml_status;
1875              SVN_ERR_ASSERT(ctx->xmlp != NULL);
1876
1877              xml_status = XML_Parse(ctx->xmlp, NULL, 0, 1);
1878
1879              apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup);
1880
1881              if (! ctx->ignore_errors)
1882                {
1883                  SVN_ERR(ctx->error);
1884
1885                  if (xml_status != XML_STATUS_OK)
1886                    {
1887                      return svn_error_create(
1888                                    SVN_ERR_XML_MALFORMED, NULL,
1889                                    _("The XML response contains invalid XML"));
1890                    }
1891                }
1892
1893              add_done_item(ctx);
1894            }
1895
1896          return svn_ra_serf__wrap_err(status, NULL);
1897        }
1898
1899      /* feed me! */
1900    }
1901  /* not reached */
1902}
1903
1904
1905apr_status_t
1906svn_ra_serf__credentials_callback(char **username, char **password,
1907                                  serf_request_t *request, void *baton,
1908                                  int code, const char *authn_type,
1909                                  const char *realm,
1910                                  apr_pool_t *pool)
1911{
1912  svn_ra_serf__handler_t *handler = baton;
1913  svn_ra_serf__session_t *session = handler->session;
1914  void *creds;
1915  svn_auth_cred_simple_t *simple_creds;
1916  svn_error_t *err;
1917
1918  if (code == 401)
1919    {
1920      /* Use svn_auth_first_credentials if this is the first time we ask for
1921         credentials during this session OR if the last time we asked
1922         session->auth_state wasn't set (eg. if the credentials provider was
1923         cancelled by the user). */
1924      if (!session->auth_state)
1925        {
1926          err = svn_auth_first_credentials(&creds,
1927                                           &session->auth_state,
1928                                           SVN_AUTH_CRED_SIMPLE,
1929                                           realm,
1930                                           session->wc_callbacks->auth_baton,
1931                                           session->pool);
1932        }
1933      else
1934        {
1935          err = svn_auth_next_credentials(&creds,
1936                                          session->auth_state,
1937                                          session->pool);
1938        }
1939
1940      if (err)
1941        {
1942          (void) save_error(session, err);
1943          return err->apr_err;
1944        }
1945
1946      session->auth_attempts++;
1947
1948      if (!creds || session->auth_attempts > 4)
1949        {
1950          /* No more credentials. */
1951          (void) save_error(session,
1952                            svn_error_create(
1953                              SVN_ERR_AUTHN_FAILED, NULL,
1954                              _("No more credentials or we tried too many "
1955                                "times.\nAuthentication failed")));
1956          return SVN_ERR_AUTHN_FAILED;
1957        }
1958
1959      simple_creds = creds;
1960      *username = apr_pstrdup(pool, simple_creds->username);
1961      *password = apr_pstrdup(pool, simple_creds->password);
1962    }
1963  else
1964    {
1965      *username = apr_pstrdup(pool, session->proxy_username);
1966      *password = apr_pstrdup(pool, session->proxy_password);
1967
1968      session->proxy_auth_attempts++;
1969
1970      if (!session->proxy_username || session->proxy_auth_attempts > 4)
1971        {
1972          /* No more credentials. */
1973          (void) save_error(session,
1974                            svn_error_create(
1975                              SVN_ERR_AUTHN_FAILED, NULL,
1976                              _("Proxy authentication failed")));
1977          return SVN_ERR_AUTHN_FAILED;
1978        }
1979    }
1980
1981  handler->conn->last_status_code = code;
1982
1983  return APR_SUCCESS;
1984}
1985
1986/* Wait for HTTP response status and headers, and invoke HANDLER->
1987   response_handler() to carry out operation-specific processing.
1988   Afterwards, check for connection close.
1989
1990   SERF_STATUS allows returning errors to serf without creating a
1991   subversion error object.
1992   */
1993static svn_error_t *
1994handle_response(serf_request_t *request,
1995                serf_bucket_t *response,
1996                svn_ra_serf__handler_t *handler,
1997                apr_status_t *serf_status,
1998                apr_pool_t *scratch_pool)
1999{
2000  apr_status_t status;
2001  svn_error_t *err;
2002
2003  /* ### need to verify whether this already gets init'd on every
2004     ### successful exit. for an error-exit, it will (properly) be
2005     ### ignored by the caller.  */
2006  *serf_status = APR_SUCCESS;
2007
2008  if (!response)
2009    {
2010      /* Uh-oh. Our connection died.  */
2011      if (handler->response_error)
2012        {
2013          /* Give a handler chance to prevent request requeue. */
2014          SVN_ERR(handler->response_error(request, response, 0,
2015                                          handler->response_error_baton));
2016
2017          svn_ra_serf__request_create(handler);
2018        }
2019      /* Response error callback is not configured. Requeue another request
2020         for this handler only if we didn't started to process body.
2021         Return error otherwise. */
2022      else if (!handler->reading_body)
2023        {
2024          svn_ra_serf__request_create(handler);
2025        }
2026      else
2027        {
2028          return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2029                                    _("%s request on '%s' failed"),
2030                                   handler->method, handler->path);
2031        }
2032
2033      return SVN_NO_ERROR;
2034    }
2035
2036  /* If we're reading the body, then skip all this preparation.  */
2037  if (handler->reading_body)
2038    goto process_body;
2039
2040  /* Copy the Status-Line info into HANDLER, if we don't yet have it.  */
2041  if (handler->sline.version == 0)
2042    {
2043      serf_status_line sl;
2044
2045      status = serf_bucket_response_status(response, &sl);
2046      if (status != APR_SUCCESS)
2047        {
2048          /* The response line is not (yet) ready, or some other error.  */
2049          *serf_status = status;
2050          return SVN_NO_ERROR; /* Handled by serf */
2051        }
2052
2053      /* If we got APR_SUCCESS, then we should have Status-Line info.  */
2054      SVN_ERR_ASSERT(sl.version != 0);
2055
2056      handler->sline = sl;
2057      handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
2058
2059      /* HTTP/1.1? (or later)  */
2060      if (sl.version != SERF_HTTP_10)
2061        handler->session->http10 = FALSE;
2062    }
2063
2064  /* Keep reading from the network until we've read all the headers.  */
2065  status = serf_bucket_response_wait_for_headers(response);
2066  if (status)
2067    {
2068      /* The typical "error" will be APR_EAGAIN, meaning that more input
2069         from the network is required to complete the reading of the
2070         headers.  */
2071      if (!APR_STATUS_IS_EOF(status))
2072        {
2073          /* Either the headers are not (yet) complete, or there really
2074             was an error.  */
2075          *serf_status = status;
2076          return SVN_NO_ERROR;
2077        }
2078
2079      /* wait_for_headers() will return EOF if there is no body in this
2080         response, or if we completely read the body. The latter is not
2081         true since we would have set READING_BODY to get the body read,
2082         and we would not be back to this code block.
2083
2084         It can also return EOF if we truly hit EOF while (say) processing
2085         the headers. aka Badness.  */
2086
2087      /* Cases where a lack of a response body (via EOF) is okay:
2088       *  - A HEAD request
2089       *  - 204/304 response
2090       *
2091       * Otherwise, if we get an EOF here, something went really wrong: either
2092       * the server closed on us early or we're reading too much.  Either way,
2093       * scream loudly.
2094       */
2095      if (strcmp(handler->method, "HEAD") != 0
2096          && handler->sline.code != 204
2097          && handler->sline.code != 304)
2098        {
2099          err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
2100                                  svn_ra_serf__wrap_err(status, NULL),
2101                                  _("Premature EOF seen from server"
2102                                    " (http status=%d)"),
2103                                  handler->sline.code);
2104
2105          /* In case anything else arrives... discard it.  */
2106          handler->discard_body = TRUE;
2107
2108          return err;
2109        }
2110    }
2111
2112  /* ... and set up the header fields in HANDLER.  */
2113  handler->location = response_get_location(response,
2114                                            handler->session->session_url_str,
2115                                            handler->handler_pool,
2116                                            scratch_pool);
2117
2118  /* On the last request, we failed authentication. We succeeded this time,
2119     so let's save away these credentials.  */
2120  if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
2121    {
2122      SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
2123                                        handler->session->pool));
2124      handler->session->auth_attempts = 0;
2125      handler->session->auth_state = NULL;
2126    }
2127  handler->conn->last_status_code = handler->sline.code;
2128
2129  if (handler->sline.code == 405
2130      || handler->sline.code == 408
2131      || handler->sline.code == 409
2132      || handler->sline.code >= 500)
2133    {
2134      /* 405 Method Not allowed.
2135         408 Request Timeout
2136         409 Conflict: can indicate a hook error.
2137         5xx (Internal) Server error. */
2138      serf_bucket_t *hdrs;
2139      const char *val;
2140
2141      hdrs = serf_bucket_response_get_headers(response);
2142      val = serf_bucket_headers_get(hdrs, "Content-Type");
2143      if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
2144        {
2145          svn_ra_serf__server_error_t *server_err;
2146
2147          server_err = begin_error_parsing(start_error, end_error, cdata_error,
2148                                           handler->handler_pool);
2149          /* Get the parser to set our DONE flag.  */
2150          server_err->parser.done = &handler->done;
2151
2152          handler->server_error = server_err;
2153        }
2154      else
2155        {
2156          handler->discard_body = TRUE;
2157
2158          if (!handler->session->pending_error)
2159            {
2160              apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED;
2161
2162              /* 405 == Method Not Allowed (Occurs when trying to lock a working
2163                copy path which no longer exists at HEAD in the repository. */
2164              if (handler->sline.code == 405
2165                  && strcmp(handler->method, "LOCK") == 0)
2166                apr_err = SVN_ERR_FS_OUT_OF_DATE;
2167
2168              handler->session->pending_error =
2169                  svn_error_createf(apr_err, NULL,
2170                                    _("%s request on '%s' failed: %d %s"),
2171                                   handler->method, handler->path,
2172                                   handler->sline.code, handler->sline.reason);
2173            }
2174        }
2175    }
2176
2177  /* Stop processing the above, on every packet arrival.  */
2178  handler->reading_body = TRUE;
2179
2180 process_body:
2181
2182  /* We've been instructed to ignore the body. Drain whatever is present.  */
2183  if (handler->discard_body)
2184    {
2185      *serf_status = drain_bucket(response);
2186
2187      /* If the handler hasn't set done (which it shouldn't have) and
2188         we now have the EOF, go ahead and set it so that we can stop
2189         our context loops.
2190       */
2191      if (!handler->done && APR_STATUS_IS_EOF(*serf_status))
2192          handler->done = TRUE;
2193
2194      return SVN_NO_ERROR;
2195    }
2196
2197  /* If we are supposed to parse the body as a server_error, then do
2198     that now.  */
2199  if (handler->server_error != NULL)
2200    {
2201      err = svn_ra_serf__handle_xml_parser(request, response,
2202                                           &handler->server_error->parser,
2203                                           scratch_pool);
2204
2205      /* If we do not receive an error or it is a non-transient error, return
2206         immediately.
2207
2208         APR_EOF will be returned when parsing is complete.
2209
2210         APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through
2211         parsing and the network has no more data right now.  If we receive that,
2212         clear the error and return - allowing serf to wait for more data.
2213         */
2214      if (!err || SERF_BUCKET_READ_ERROR(err->apr_err))
2215        return svn_error_trace(err);
2216
2217      if (!APR_STATUS_IS_EOF(err->apr_err))
2218        {
2219          *serf_status = err->apr_err;
2220          svn_error_clear(err);
2221          return SVN_NO_ERROR;
2222        }
2223
2224      /* Clear the EOF. We don't need it.  */
2225      svn_error_clear(err);
2226
2227      /* If the parsing is done, and we did not extract an error, then
2228         simply toss everything, and anything else that might arrive.
2229         The higher-level code will need to investigate HANDLER->SLINE,
2230         as we have no further information for them.  */
2231      if (handler->done
2232          && handler->server_error->error->apr_err == APR_SUCCESS)
2233        {
2234          svn_error_clear(handler->server_error->error);
2235
2236          /* Stop parsing for a server error.  */
2237          handler->server_error = NULL;
2238
2239          /* If anything arrives after this, then just discard it.  */
2240          handler->discard_body = TRUE;
2241        }
2242
2243      *serf_status = APR_EOF;
2244      return SVN_NO_ERROR;
2245    }
2246
2247  /* Pass the body along to the registered response handler.  */
2248  err = handler->response_handler(request, response,
2249                                  handler->response_baton,
2250                                  scratch_pool);
2251
2252  if (err
2253      && (!SERF_BUCKET_READ_ERROR(err->apr_err)
2254          || APR_STATUS_IS_ECONNRESET(err->apr_err)
2255          || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
2256    {
2257      /* These errors are special cased in serf
2258         ### We hope no handler returns these by accident. */
2259      *serf_status = err->apr_err;
2260      svn_error_clear(err);
2261      return SVN_NO_ERROR;
2262    }
2263
2264  return svn_error_trace(err);
2265}
2266
2267
2268/* Implements serf_response_handler_t for handle_response. Storing
2269   errors in handler->session->pending_error if appropriate. */
2270static apr_status_t
2271handle_response_cb(serf_request_t *request,
2272                   serf_bucket_t *response,
2273                   void *baton,
2274                   apr_pool_t *scratch_pool)
2275{
2276  svn_ra_serf__handler_t *handler = baton;
2277  svn_error_t *err;
2278  apr_status_t inner_status;
2279  apr_status_t outer_status;
2280
2281  err = svn_error_trace(handle_response(request, response,
2282                                        handler, &inner_status,
2283                                        scratch_pool));
2284
2285  /* Select the right status value to return.  */
2286  outer_status = save_error(handler->session, err);
2287  if (!outer_status)
2288    outer_status = inner_status;
2289
2290  /* Make sure the DONE flag is set properly.  */
2291  if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
2292    handler->done = TRUE;
2293
2294  return outer_status;
2295}
2296
2297/* Perform basic request setup, with special handling for HEAD requests,
2298   and finer-grained callbacks invoked (if non-NULL) to produce the request
2299   headers and body. */
2300static svn_error_t *
2301setup_request(serf_request_t *request,
2302              svn_ra_serf__handler_t *handler,
2303              serf_bucket_t **req_bkt,
2304              apr_pool_t *request_pool,
2305              apr_pool_t *scratch_pool)
2306{
2307  serf_bucket_t *body_bkt;
2308  serf_bucket_t *headers_bkt;
2309  const char *accept_encoding;
2310
2311  if (handler->body_delegate)
2312    {
2313      serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
2314
2315      /* ### should pass the scratch_pool  */
2316      SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
2317                                     bkt_alloc, request_pool));
2318    }
2319  else
2320    {
2321      body_bkt = NULL;
2322    }
2323
2324  if (handler->custom_accept_encoding)
2325    {
2326      accept_encoding = NULL;
2327    }
2328  else if (handler->session->using_compression)
2329    {
2330      /* Accept gzip compression if enabled. */
2331      accept_encoding = "gzip";
2332    }
2333  else
2334    {
2335      accept_encoding = NULL;
2336    }
2337
2338  SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
2339                         handler->session, handler->method, handler->path,
2340                         body_bkt, handler->body_type, accept_encoding,
2341                         request_pool, scratch_pool));
2342
2343  if (handler->header_delegate)
2344    {
2345      /* ### should pass the scratch_pool  */
2346      SVN_ERR(handler->header_delegate(headers_bkt,
2347                                       handler->header_delegate_baton,
2348                                       request_pool));
2349    }
2350
2351  return APR_SUCCESS;
2352}
2353
2354/* Implements the serf_request_setup_t interface (which sets up both a
2355   request and its response handler callback). Handles errors for
2356   setup_request_cb */
2357static apr_status_t
2358setup_request_cb(serf_request_t *request,
2359              void *setup_baton,
2360              serf_bucket_t **req_bkt,
2361              serf_response_acceptor_t *acceptor,
2362              void **acceptor_baton,
2363              serf_response_handler_t *s_handler,
2364              void **s_handler_baton,
2365              apr_pool_t *pool)
2366{
2367  svn_ra_serf__handler_t *handler = setup_baton;
2368  svn_error_t *err;
2369
2370  /* ### construct a scratch_pool? serf gives us a pool that will live for
2371     ### the duration of the request.  */
2372  apr_pool_t *scratch_pool = pool;
2373
2374  if (strcmp(handler->method, "HEAD") == 0)
2375    *acceptor = accept_head;
2376  else
2377    *acceptor = accept_response;
2378  *acceptor_baton = handler->session;
2379
2380  *s_handler = handle_response_cb;
2381  *s_handler_baton = handler;
2382
2383  err = svn_error_trace(setup_request(request, handler, req_bkt,
2384                                      pool /* request_pool */, scratch_pool));
2385
2386  return save_error(handler->session, err);
2387}
2388
2389void
2390svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
2391{
2392  SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL);
2393
2394  /* In case HANDLER is re-queued, reset the various transient fields.
2395
2396     ### prior to recent changes, HANDLER was constant. maybe we should
2397     ### break out these processing fields, apart from the request
2398     ### definition.  */
2399  handler->done = FALSE;
2400  handler->server_error = NULL;
2401  handler->sline.version = 0;
2402  handler->location = NULL;
2403  handler->reading_body = FALSE;
2404  handler->discard_body = FALSE;
2405
2406  /* ### do we ever alter the >response_handler?  */
2407
2408  /* ### do we need to hold onto the returned request object, or just
2409     ### not worry about it (the serf ctx will manage it).  */
2410  (void) serf_connection_request_create(handler->conn->conn,
2411                                        setup_request_cb, handler);
2412}
2413
2414
2415svn_error_t *
2416svn_ra_serf__discover_vcc(const char **vcc_url,
2417                          svn_ra_serf__session_t *session,
2418                          svn_ra_serf__connection_t *conn,
2419                          apr_pool_t *pool)
2420{
2421  const char *path;
2422  const char *relative_path;
2423  const char *uuid;
2424
2425  /* If we've already got the information our caller seeks, just return it.  */
2426  if (session->vcc_url && session->repos_root_str)
2427    {
2428      *vcc_url = session->vcc_url;
2429      return SVN_NO_ERROR;
2430    }
2431
2432  /* If no connection is provided, use the default one. */
2433  if (! conn)
2434    {
2435      conn = session->conns[0];
2436    }
2437
2438  path = session->session_url.path;
2439  *vcc_url = NULL;
2440  uuid = NULL;
2441
2442  do
2443    {
2444      apr_hash_t *props;
2445      svn_error_t *err;
2446
2447      err = svn_ra_serf__fetch_node_props(&props, conn,
2448                                          path, SVN_INVALID_REVNUM,
2449                                          base_props, pool, pool);
2450      if (! err)
2451        {
2452          apr_hash_t *ns_props;
2453
2454          ns_props = apr_hash_get(props, "DAV:", 4);
2455          *vcc_url = svn_prop_get_value(ns_props,
2456                                        "version-controlled-configuration");
2457
2458          ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
2459          relative_path = svn_prop_get_value(ns_props,
2460                                             "baseline-relative-path");
2461          uuid = svn_prop_get_value(ns_props, "repository-uuid");
2462          break;
2463        }
2464      else
2465        {
2466          if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
2467              (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
2468            {
2469              return svn_error_trace(err);  /* found a _real_ error */
2470            }
2471          else
2472            {
2473              /* This happens when the file is missing in HEAD. */
2474              svn_error_clear(err);
2475
2476              /* Okay, strip off a component from PATH. */
2477              path = svn_urlpath__dirname(path, pool);
2478
2479              /* An error occurred on conns. serf 0.4.0 remembers that
2480                 the connection had a problem. We need to reset it, in
2481                 order to use it again.  */
2482              serf_connection_reset(conn->conn);
2483            }
2484        }
2485    }
2486  while ((path[0] != '\0')
2487         && (! (path[0] == '/' && path[1] == '\0')));
2488
2489  if (!*vcc_url)
2490    {
2491      return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
2492                              _("The PROPFIND response did not include the "
2493                                "requested version-controlled-configuration "
2494                                "value"));
2495    }
2496
2497  /* Store our VCC in our cache. */
2498  if (!session->vcc_url)
2499    {
2500      session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
2501    }
2502
2503  /* Update our cached repository root URL. */
2504  if (!session->repos_root_str)
2505    {
2506      svn_stringbuf_t *url_buf;
2507
2508      url_buf = svn_stringbuf_create(path, pool);
2509
2510      svn_path_remove_components(url_buf,
2511                                 svn_path_component_count(relative_path));
2512
2513      /* Now recreate the root_url. */
2514      session->repos_root = session->session_url;
2515      session->repos_root.path =
2516        (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
2517      session->repos_root_str =
2518        svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
2519                                                  &session->repos_root, 0),
2520                                  session->pool);
2521    }
2522
2523  /* Store the repository UUID in the cache. */
2524  if (!session->uuid)
2525    {
2526      session->uuid = apr_pstrdup(session->pool, uuid);
2527    }
2528
2529  return SVN_NO_ERROR;
2530}
2531
2532svn_error_t *
2533svn_ra_serf__get_relative_path(const char **rel_path,
2534                               const char *orig_path,
2535                               svn_ra_serf__session_t *session,
2536                               svn_ra_serf__connection_t *conn,
2537                               apr_pool_t *pool)
2538{
2539  const char *decoded_root, *decoded_orig;
2540
2541  if (! session->repos_root.path)
2542    {
2543      const char *vcc_url;
2544
2545      /* This should only happen if we haven't detected HTTP v2
2546         support from the server.  */
2547      assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
2548
2549      /* We don't actually care about the VCC_URL, but this API
2550         promises to populate the session's root-url cache, and that's
2551         what we really want. */
2552      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
2553                                        conn ? conn : session->conns[0],
2554                                        pool));
2555    }
2556
2557  decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
2558  decoded_orig = svn_path_uri_decode(orig_path, pool);
2559  *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
2560  SVN_ERR_ASSERT(*rel_path != NULL);
2561  return SVN_NO_ERROR;
2562}
2563
2564svn_error_t *
2565svn_ra_serf__report_resource(const char **report_target,
2566                             svn_ra_serf__session_t *session,
2567                             svn_ra_serf__connection_t *conn,
2568                             apr_pool_t *pool)
2569{
2570  /* If we have HTTP v2 support, we want to report against the 'me'
2571     resource. */
2572  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
2573    *report_target = apr_pstrdup(pool, session->me_resource);
2574
2575  /* Otherwise, we'll use the default VCC. */
2576  else
2577    SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool));
2578
2579  return SVN_NO_ERROR;
2580}
2581
2582svn_error_t *
2583svn_ra_serf__error_on_status(serf_status_line sline,
2584                             const char *path,
2585                             const char *location)
2586{
2587  switch(sline.code)
2588    {
2589      case 301:
2590      case 302:
2591      case 307:
2592        return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
2593                                 (sline.code == 301)
2594                                 ? _("Repository moved permanently to '%s';"
2595                                     " please relocate")
2596                                 : _("Repository moved temporarily to '%s';"
2597                                     " please relocate"), location);
2598      case 403:
2599        return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
2600                                 _("Access to '%s' forbidden"), path);
2601
2602      case 404:
2603        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
2604                                 _("'%s' path not found"), path);
2605      case 423:
2606        return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
2607                                 _("'%s': no lock token available"), path);
2608
2609      case 411:
2610        return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2611                    _("DAV request failed: 411 Content length required. The "
2612                      "server or an intermediate proxy does not accept "
2613                      "chunked encoding. Try setting 'http-chunked-requests' "
2614                      "to 'auto' or 'no' in your client configuration."));
2615      case 501:
2616        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2617                                 _("The requested feature is not supported by "
2618                                   "'%s'"), path);
2619    }
2620
2621  if (sline.code >= 300)
2622    return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
2623                             _("Unexpected HTTP status %d '%s' on '%s'\n"),
2624                             sline.code, sline.reason, path);
2625
2626  return SVN_NO_ERROR;
2627}
2628
2629svn_error_t *
2630svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
2631                                    svn_delta_shim_callbacks_t *callbacks)
2632{
2633  svn_ra_serf__session_t *session = ra_session->priv;
2634
2635  session->shim_callbacks = callbacks;
2636  return SVN_NO_ERROR;
2637}
2638
2639
2640/* Conforms to Expat's XML_StartElementHandler  */
2641static void
2642expat_start(void *userData, const char *raw_name, const char **attrs)
2643{
2644  struct expat_ctx_t *ectx = userData;
2645
2646  if (ectx->inner_error != NULL)
2647    return;
2648
2649  ectx->inner_error = svn_error_trace(
2650                        svn_ra_serf__xml_cb_start(ectx->xmlctx,
2651                                                  raw_name, attrs));
2652
2653#ifdef EXPAT_HAS_STOPPARSER
2654  if (ectx->inner_error)
2655    (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2656#endif
2657}
2658
2659
2660/* Conforms to Expat's XML_EndElementHandler  */
2661static void
2662expat_end(void *userData, const char *raw_name)
2663{
2664  struct expat_ctx_t *ectx = userData;
2665
2666  if (ectx->inner_error != NULL)
2667    return;
2668
2669  ectx->inner_error = svn_error_trace(
2670                        svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name));
2671
2672#ifdef EXPAT_HAS_STOPPARSER
2673  if (ectx->inner_error)
2674    (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2675#endif
2676}
2677
2678
2679/* Conforms to Expat's XML_CharacterDataHandler  */
2680static void
2681expat_cdata(void *userData, const char *data, int len)
2682{
2683  struct expat_ctx_t *ectx = userData;
2684
2685  if (ectx->inner_error != NULL)
2686    return;
2687
2688  ectx->inner_error = svn_error_trace(
2689                        svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len));
2690
2691#ifdef EXPAT_HAS_STOPPARSER
2692  if (ectx->inner_error)
2693    (void) XML_StopParser(ectx->parser, 0 /* resumable */);
2694#endif
2695}
2696
2697
2698/* Implements svn_ra_serf__response_handler_t */
2699static svn_error_t *
2700expat_response_handler(serf_request_t *request,
2701                       serf_bucket_t *response,
2702                       void *baton,
2703                       apr_pool_t *scratch_pool)
2704{
2705  struct expat_ctx_t *ectx = baton;
2706
2707  if (!ectx->parser)
2708    {
2709      ectx->parser = XML_ParserCreate(NULL);
2710      apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser,
2711                                xml_parser_cleanup, apr_pool_cleanup_null);
2712      XML_SetUserData(ectx->parser, ectx);
2713      XML_SetElementHandler(ectx->parser, expat_start, expat_end);
2714      XML_SetCharacterDataHandler(ectx->parser, expat_cdata);
2715    }
2716
2717  /* ### TODO: sline.code < 200 should really be handled by the core */
2718  if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300))
2719    {
2720      /* By deferring to expect_empty_body(), it will make a choice on
2721         how to handle the body. Whatever the decision, the core handler
2722         will take over, and we will not be called again.  */
2723      return svn_error_trace(svn_ra_serf__expect_empty_body(
2724                               request, response, ectx->handler,
2725                               scratch_pool));
2726    }
2727
2728  while (1)
2729    {
2730      apr_status_t status;
2731      const char *data;
2732      apr_size_t len;
2733      int expat_status;
2734
2735      status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len);
2736      if (SERF_BUCKET_READ_ERROR(status))
2737        return svn_ra_serf__wrap_err(status, NULL);
2738
2739#if 0
2740      /* ### move restart/skip into the core handler  */
2741      ectx->handler->read_size += len;
2742#endif
2743
2744      /* ### move PAUSED behavior to a new response handler that can feed
2745         ### an inner handler, or can pause for a while.  */
2746
2747      /* ### should we have an IGNORE_ERRORS flag like the v1 parser?  */
2748
2749      expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */);
2750
2751      /* We need to check INNER_ERROR first. This is an error from the
2752         callbacks that has been "dropped off" for us to retrieve. On
2753         current Expat parsers, we stop the parser when an error occurs,
2754         so we want to ignore EXPAT_STATUS (which reports the stoppage).
2755
2756         If an error is not present, THEN we go ahead and look for parsing
2757         errors.  */
2758      if (ectx->inner_error)
2759        {
2760          apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
2761                               xml_parser_cleanup);
2762          return svn_error_trace(ectx->inner_error);
2763        }
2764      if (expat_status == XML_STATUS_ERROR)
2765        return svn_error_createf(SVN_ERR_XML_MALFORMED,
2766                                 ectx->inner_error,
2767                                 _("The %s response contains invalid XML"
2768                                   " (%d %s)"),
2769                                 ectx->handler->method,
2770                                 ectx->handler->sline.code,
2771                                 ectx->handler->sline.reason);
2772
2773      /* The parsing went fine. What has the bucket told us?  */
2774
2775      if (APR_STATUS_IS_EOF(status))
2776        {
2777          /* Tell expat we've reached the end of the content. Ignore the
2778             return status. We just don't care.  */
2779          (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */);
2780
2781          svn_ra_serf__xml_context_destroy(ectx->xmlctx);
2782          apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser,
2783                               xml_parser_cleanup);
2784
2785          /* ### should check XMLCTX to see if it has returned to the
2786             ### INITIAL state. we may have ended early...  */
2787        }
2788
2789      if (status && !SERF_BUCKET_READ_ERROR(status))
2790        {
2791          return svn_ra_serf__wrap_err(status, NULL);
2792        }
2793    }
2794
2795  /* NOTREACHED */
2796}
2797
2798
2799svn_ra_serf__handler_t *
2800svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx,
2801                                  apr_pool_t *result_pool)
2802{
2803  svn_ra_serf__handler_t *handler;
2804  struct expat_ctx_t *ectx;
2805
2806  ectx = apr_pcalloc(result_pool, sizeof(*ectx));
2807  ectx->xmlctx = xmlctx;
2808  ectx->parser = NULL;
2809  ectx->cleanup_pool = result_pool;
2810
2811
2812  handler = apr_pcalloc(result_pool, sizeof(*handler));
2813  handler->handler_pool = result_pool;
2814  handler->response_handler = expat_response_handler;
2815  handler->response_baton = ectx;
2816
2817  ectx->handler = handler;
2818
2819  return handler;
2820}
2821