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