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