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