]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_serf/util.c
THIS BRANCH IS OBSOLETE, PLEASE READ:
[FreeBSD/FreeBSD.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 "svn_hash.h"
36 #include "svn_dirent_uri.h"
37 #include "svn_path.h"
38 #include "svn_private_config.h"
39 #include "svn_string.h"
40 #include "svn_props.h"
41 #include "svn_dirent_uri.h"
42
43 #include "../libsvn_ra/ra_loader.h"
44 #include "private/svn_dep_compat.h"
45 #include "private/svn_fspath.h"
46 #include "private/svn_auth_private.h"
47 #include "private/svn_cert.h"
48
49 #include "ra_serf.h"
50
51 static const apr_uint32_t serf_failure_map[][2] =
52 {
53   { SERF_SSL_CERT_NOTYETVALID,   SVN_AUTH_SSL_NOTYETVALID },
54   { SERF_SSL_CERT_EXPIRED,       SVN_AUTH_SSL_EXPIRED },
55   { SERF_SSL_CERT_SELF_SIGNED,   SVN_AUTH_SSL_UNKNOWNCA },
56   { SERF_SSL_CERT_UNKNOWNCA,     SVN_AUTH_SSL_UNKNOWNCA }
57 };
58
59 /* Return a Subversion failure mask based on FAILURES, a serf SSL
60    failure mask.  If anything in FAILURES is not directly mappable to
61    Subversion failures, set SVN_AUTH_SSL_OTHER in the returned mask. */
62 static apr_uint32_t
63 ssl_convert_serf_failures(int failures)
64 {
65   apr_uint32_t svn_failures = 0;
66   apr_size_t i;
67
68   for (i = 0;
69        i < sizeof(serf_failure_map) / (sizeof(serf_failure_map[0]));
70        ++i)
71     {
72       if (failures & serf_failure_map[i][0])
73         {
74           svn_failures |= serf_failure_map[i][1];
75           failures &= ~serf_failure_map[i][0];
76         }
77     }
78
79   /* Map any remaining failure bits to our OTHER bit. */
80   if (failures)
81     {
82       svn_failures |= SVN_AUTH_SSL_OTHER;
83     }
84
85   return svn_failures;
86 }
87
88
89 static apr_status_t
90 save_error(svn_ra_serf__session_t *session,
91            svn_error_t *err)
92 {
93   if (err || session->pending_error)
94     {
95       session->pending_error = svn_error_compose_create(
96                                   session->pending_error,
97                                   err);
98       return session->pending_error->apr_err;
99     }
100
101   return APR_SUCCESS;
102 }
103
104
105 /* Construct the realmstring, e.g. https://svn.collab.net:443. */
106 static const char *
107 construct_realm(svn_ra_serf__session_t *session,
108                 apr_pool_t *pool)
109 {
110   const char *realm;
111   apr_port_t port;
112
113   if (session->session_url.port_str)
114     {
115       port = session->session_url.port;
116     }
117   else
118     {
119       port = apr_uri_port_of_scheme(session->session_url.scheme);
120     }
121
122   realm = apr_psprintf(pool, "%s://%s:%d",
123                        session->session_url.scheme,
124                        session->session_url.hostname,
125                        port);
126
127   return realm;
128 }
129
130 /* Convert a hash table containing the fields (as documented in X.509) of an
131    organisation to a string ORG, allocated in POOL. ORG is as returned by
132    serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
133 static char *
134 convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
135 {
136   const char *cn = svn_hash_gets(org, "CN");
137   const char *org_unit = svn_hash_gets(org, "OU");
138   const char *org_name = svn_hash_gets(org, "O");
139   const char *locality = svn_hash_gets(org, "L");
140   const char *state = svn_hash_gets(org, "ST");
141   const char *country = svn_hash_gets(org, "C");
142   const char *email = svn_hash_gets(org, "E");
143   svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
144
145   if (cn)
146     {
147       svn_stringbuf_appendcstr(buf, cn);
148       svn_stringbuf_appendcstr(buf, ", ");
149     }
150
151   if (org_unit)
152     {
153       svn_stringbuf_appendcstr(buf, org_unit);
154       svn_stringbuf_appendcstr(buf, ", ");
155     }
156
157   if (org_name)
158     {
159       svn_stringbuf_appendcstr(buf, org_name);
160       svn_stringbuf_appendcstr(buf, ", ");
161     }
162
163   if (locality)
164     {
165       svn_stringbuf_appendcstr(buf, locality);
166       svn_stringbuf_appendcstr(buf, ", ");
167     }
168
169   if (state)
170     {
171       svn_stringbuf_appendcstr(buf, state);
172       svn_stringbuf_appendcstr(buf, ", ");
173     }
174
175   if (country)
176     {
177       svn_stringbuf_appendcstr(buf, country);
178       svn_stringbuf_appendcstr(buf, ", ");
179     }
180
181   /* Chop ', ' if any. */
182   svn_stringbuf_chop(buf, 2);
183
184   if (email)
185     {
186       svn_stringbuf_appendcstr(buf, "(");
187       svn_stringbuf_appendcstr(buf, email);
188       svn_stringbuf_appendcstr(buf, ")");
189     }
190
191   return buf->data;
192 }
193
194 static void append_reason(svn_stringbuf_t *errmsg, const char *reason, int *reasons)
195 {
196   if (*reasons < 1)
197     svn_stringbuf_appendcstr(errmsg, _(": "));
198   else
199     svn_stringbuf_appendcstr(errmsg, _(", "));
200   svn_stringbuf_appendcstr(errmsg, reason);
201   (*reasons)++;
202 }
203
204 /* This function is called on receiving a ssl certificate of a server when
205    opening a https connection. It allows Subversion to override the initial
206    validation done by serf.
207    Serf provides us the @a baton as provided in the call to
208    serf_ssl_server_cert_callback_set. The result of serf's initial validation
209    of the certificate @a CERT is returned as a bitmask in FAILURES. */
210 static svn_error_t *
211 ssl_server_cert(void *baton, int failures,
212                 const serf_ssl_certificate_t *cert,
213                 apr_pool_t *scratch_pool)
214 {
215   svn_ra_serf__connection_t *conn = baton;
216   svn_auth_ssl_server_cert_info_t cert_info;
217   svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
218   svn_auth_iterstate_t *state;
219   const char *realmstring;
220   apr_uint32_t svn_failures;
221   apr_hash_t *issuer;
222   apr_hash_t *subject = NULL;
223   apr_hash_t *serf_cert = NULL;
224   void *creds;
225
226   svn_failures = (ssl_convert_serf_failures(failures)
227       | conn->server_cert_failures);
228
229   if (serf_ssl_cert_depth(cert) == 0)
230     {
231       /* If the depth is 0, the hostname must match the certificate.
232
233       ### This should really be handled by serf, which should pass an error
234           for this case, but that has backwards compatibility issues. */
235       apr_array_header_t *san;
236       svn_boolean_t found_matching_hostname = FALSE;
237       svn_string_t *actual_hostname =
238           svn_string_create(conn->session->session_url.hostname, scratch_pool);
239
240       serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
241
242       san = svn_hash_gets(serf_cert, "subjectAltName");
243       /* Match server certificate CN with the hostname of the server iff
244        * we didn't find any subjectAltName fields and try to match them.
245        * Per RFC 2818 they are authoritative if present and CommonName
246        * should be ignored.  NOTE: This isn't 100% correct since serf
247        * only loads the subjectAltName hash with dNSNames, technically
248        * we should ignore the CommonName if any subjectAltName entry
249        * exists even if it is one we don't support. */
250       if (san && san->nelts > 0)
251         {
252           int i;
253           for (i = 0; i < san->nelts; i++)
254             {
255               const char *s = APR_ARRAY_IDX(san, i, const char*);
256               svn_string_t *cert_hostname = svn_string_create(s, scratch_pool);
257
258               if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
259                 {
260                   found_matching_hostname = TRUE;
261                   break;
262                 }
263             }
264         }
265       else
266         {
267           const char *hostname = NULL;
268
269           subject = serf_ssl_cert_subject(cert, scratch_pool);
270
271           if (subject)
272             hostname = svn_hash_gets(subject, "CN");
273
274           if (hostname)
275             {
276               svn_string_t *cert_hostname = svn_string_create(hostname,
277                                                               scratch_pool);
278
279               if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
280                 {
281                   found_matching_hostname = TRUE;
282                 }
283             }
284         }
285
286       if (!found_matching_hostname)
287         svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
288     }
289
290   if (!svn_failures)
291     return SVN_NO_ERROR;
292
293   /* Extract the info from the certificate */
294   if (! subject)
295     subject = serf_ssl_cert_subject(cert, scratch_pool);
296   issuer = serf_ssl_cert_issuer(cert, scratch_pool);
297   if (! serf_cert)
298     serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
299
300   cert_info.hostname = svn_hash_gets(subject, "CN");
301   cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
302   if (! cert_info.fingerprint)
303     cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
304   cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
305   if (! cert_info.valid_from)
306     cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
307   cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
308   if (! cert_info.valid_until)
309     cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
310   cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
311   cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
312
313   /* Handle any non-server certs. */
314   if (serf_ssl_cert_depth(cert) > 0)
315     {
316       svn_error_t *err;
317
318       svn_auth_set_parameter(conn->session->auth_baton,
319                              SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
320                              &cert_info);
321
322       svn_auth_set_parameter(conn->session->auth_baton,
323                              SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
324                              &svn_failures);
325
326       realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s",
327                                  cert_info.fingerprint);
328
329       err = svn_auth_first_credentials(&creds, &state,
330                                        SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
331                                        realmstring,
332                                        conn->session->auth_baton,
333                                        scratch_pool);
334
335       svn_auth_set_parameter(conn->session->auth_baton,
336                              SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
337
338       svn_auth_set_parameter(conn->session->auth_baton,
339                              SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
340
341       if (err)
342         {
343           if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER)
344             return svn_error_trace(err);
345
346           /* No provider registered that handles server authorities */
347           svn_error_clear(err);
348           creds = NULL;
349         }
350
351       if (creds)
352         {
353           server_creds = creds;
354           SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
355
356           svn_failures &= ~server_creds->accepted_failures;
357         }
358
359       if (svn_failures)
360         conn->server_cert_failures |= svn_failures;
361
362       return APR_SUCCESS;
363     }
364
365   svn_auth_set_parameter(conn->session->auth_baton,
366                          SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
367                          &svn_failures);
368
369   svn_auth_set_parameter(conn->session->auth_baton,
370                          SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
371                          &cert_info);
372
373   realmstring = construct_realm(conn->session, conn->session->pool);
374
375   SVN_ERR(svn_auth_first_credentials(&creds, &state,
376                                      SVN_AUTH_CRED_SSL_SERVER_TRUST,
377                                      realmstring,
378                                      conn->session->auth_baton,
379                                      scratch_pool));
380   if (creds)
381     {
382       server_creds = creds;
383       svn_failures &= ~server_creds->accepted_failures;
384       SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
385     }
386
387   while (svn_failures && creds)
388     {
389       SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool));
390
391       if (creds)
392         {
393           server_creds = creds;
394           svn_failures &= ~server_creds->accepted_failures;
395           SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
396         }
397     }
398
399   svn_auth_set_parameter(conn->session->auth_baton,
400                          SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
401
402   /* Are there non accepted failures left? */
403   if (svn_failures)
404     {
405       svn_stringbuf_t *errmsg;
406       int reasons = 0;
407
408       errmsg = svn_stringbuf_create(
409                  _("Server SSL certificate verification failed"),
410                  scratch_pool);
411
412
413       if (svn_failures & SVN_AUTH_SSL_NOTYETVALID)
414         append_reason(errmsg, _("certificate is not yet valid"), &reasons);
415
416       if (svn_failures & SVN_AUTH_SSL_EXPIRED)
417         append_reason(errmsg, _("certificate has expired"), &reasons);
418
419       if (svn_failures & SVN_AUTH_SSL_CNMISMATCH)
420         append_reason(errmsg,
421                       _("certificate issued for a different hostname"),
422                       &reasons);
423
424       if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA)
425         append_reason(errmsg, _("issuer is not trusted"), &reasons);
426
427       if (svn_failures & SVN_AUTH_SSL_OTHER)
428         append_reason(errmsg, _("and other reason(s)"), &reasons);
429
430       return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL,
431                               errmsg->data);
432     }
433
434   return SVN_NO_ERROR;
435 }
436
437 /* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
438 static apr_status_t
439 ssl_server_cert_cb(void *baton, int failures,
440                 const serf_ssl_certificate_t *cert)
441 {
442   svn_ra_serf__connection_t *conn = baton;
443   svn_ra_serf__session_t *session = conn->session;
444   apr_pool_t *subpool;
445   svn_error_t *err;
446
447   subpool = svn_pool_create(session->pool);
448   err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
449   svn_pool_destroy(subpool);
450
451   return save_error(session, err);
452 }
453
454 static svn_error_t *
455 load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
456                  apr_pool_t *pool)
457 {
458   apr_array_header_t *files = svn_cstring_split(authorities, ";",
459                                                 TRUE /* chop_whitespace */,
460                                                 pool);
461   int i;
462
463   for (i = 0; i < files->nelts; ++i)
464     {
465       const char *file = APR_ARRAY_IDX(files, i, const char *);
466       serf_ssl_certificate_t *ca_cert;
467       apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
468
469       if (status == APR_SUCCESS)
470         status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
471
472       if (status != APR_SUCCESS)
473         {
474           return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
475              _("Invalid config: unable to load certificate file '%s'"),
476              svn_dirent_local_style(file, pool));
477         }
478     }
479
480   return SVN_NO_ERROR;
481 }
482
483 #if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2)
484 /* Implements serf_ssl_protocol_result_cb_t */
485 static apr_status_t
486 conn_negotiate_protocol(void *data,
487                         const char *protocol)
488 {
489   svn_ra_serf__connection_t *conn = data;
490
491   if (!strcmp(protocol, "h2"))
492     {
493       serf_connection_set_framing_type(
494             conn->conn,
495             SERF_CONNECTION_FRAMING_TYPE_HTTP2);
496
497       /* Disable generating content-length headers. */
498       conn->session->http10 = FALSE;
499       conn->session->http20 = TRUE;
500       conn->session->using_chunked_requests = TRUE;
501       conn->session->detect_chunking = FALSE;
502     }
503   else
504     {
505       /* protocol should be "" or "http/1.1" */
506       serf_connection_set_framing_type(
507             conn->conn,
508             SERF_CONNECTION_FRAMING_TYPE_HTTP1);
509     }
510
511   return APR_SUCCESS;
512 }
513 #endif
514
515 static svn_error_t *
516 conn_setup(apr_socket_t *sock,
517            serf_bucket_t **read_bkt,
518            serf_bucket_t **write_bkt,
519            void *baton,
520            apr_pool_t *pool)
521 {
522   svn_ra_serf__connection_t *conn = baton;
523
524   *read_bkt = serf_context_bucket_socket_create(conn->session->context,
525                                                sock, conn->bkt_alloc);
526
527   if (conn->session->using_ssl)
528     {
529       /* input stream */
530       *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
531                                                  conn->bkt_alloc);
532       if (!conn->ssl_context)
533         {
534           conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
535
536           serf_ssl_set_hostname(conn->ssl_context,
537                                 conn->session->session_url.hostname);
538
539           serf_ssl_client_cert_provider_set(conn->ssl_context,
540                                             svn_ra_serf__handle_client_cert,
541                                             conn, conn->session->pool);
542           serf_ssl_client_cert_password_set(conn->ssl_context,
543                                             svn_ra_serf__handle_client_cert_pw,
544                                             conn, conn->session->pool);
545           serf_ssl_server_cert_callback_set(conn->ssl_context,
546                                             ssl_server_cert_cb,
547                                             conn);
548
549           /* See if the user wants us to trust "default" openssl CAs. */
550           if (conn->session->trust_default_ca)
551             {
552               serf_ssl_use_default_certificates(conn->ssl_context);
553             }
554           /* Are there custom CAs to load? */
555           if (conn->session->ssl_authorities)
556             {
557               SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
558                                        conn->session->pool));
559             }
560 #if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2)
561           if (APR_SUCCESS ==
562                 serf_ssl_negotiate_protocol(conn->ssl_context, "h2,http/1.1",
563                                             conn_negotiate_protocol, conn))
564             {
565                 serf_connection_set_framing_type(
566                             conn->conn,
567                             SERF_CONNECTION_FRAMING_TYPE_NONE);
568             }
569 #endif
570         }
571
572       if (write_bkt)
573         {
574           /* output stream */
575           *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
576                                                       conn->ssl_context,
577                                                       conn->bkt_alloc);
578         }
579     }
580
581   return SVN_NO_ERROR;
582 }
583
584 /* svn_ra_serf__conn_setup is a callback for serf. This function
585    creates a read bucket and will wrap the write bucket if SSL
586    is needed. */
587 apr_status_t
588 svn_ra_serf__conn_setup(apr_socket_t *sock,
589                         serf_bucket_t **read_bkt,
590                         serf_bucket_t **write_bkt,
591                         void *baton,
592                         apr_pool_t *pool)
593 {
594   svn_ra_serf__connection_t *conn = baton;
595   svn_ra_serf__session_t *session = conn->session;
596   svn_error_t *err;
597
598   err = svn_error_trace(conn_setup(sock,
599                                    read_bkt,
600                                    write_bkt,
601                                    baton,
602                                    pool));
603   return save_error(session, err);
604 }
605
606
607 /* Our default serf response acceptor.  */
608 static serf_bucket_t *
609 accept_response(serf_request_t *request,
610                 serf_bucket_t *stream,
611                 void *acceptor_baton,
612                 apr_pool_t *pool)
613 {
614   /* svn_ra_serf__handler_t *handler = acceptor_baton; */
615   serf_bucket_t *c;
616   serf_bucket_alloc_t *bkt_alloc;
617
618   bkt_alloc = serf_request_get_alloc(request);
619   c = serf_bucket_barrier_create(stream, bkt_alloc);
620
621   return serf_bucket_response_create(c, bkt_alloc);
622 }
623
624
625 /* Custom response acceptor for HEAD requests.  */
626 static serf_bucket_t *
627 accept_head(serf_request_t *request,
628             serf_bucket_t *stream,
629             void *acceptor_baton,
630             apr_pool_t *pool)
631 {
632   /* svn_ra_serf__handler_t *handler = acceptor_baton; */
633   serf_bucket_t *response;
634
635   response = accept_response(request, stream, acceptor_baton, pool);
636
637   /* We know we shouldn't get a response body. */
638   serf_bucket_response_set_head(response);
639
640   return response;
641 }
642
643 static svn_error_t *
644 connection_closed(svn_ra_serf__connection_t *conn,
645                   apr_status_t why,
646                   apr_pool_t *pool)
647 {
648   if (why)
649     {
650       return svn_ra_serf__wrap_err(why, NULL);
651     }
652
653   if (conn->session->using_ssl)
654     conn->ssl_context = NULL;
655
656   return SVN_NO_ERROR;
657 }
658
659 void
660 svn_ra_serf__conn_closed(serf_connection_t *conn,
661                          void *closed_baton,
662                          apr_status_t why,
663                          apr_pool_t *pool)
664 {
665   svn_ra_serf__connection_t *ra_conn = closed_baton;
666   svn_error_t *err;
667
668   err = svn_error_trace(connection_closed(ra_conn, why, pool));
669
670   (void) save_error(ra_conn->session, err);
671 }
672
673
674 /* Implementation of svn_ra_serf__handle_client_cert */
675 static svn_error_t *
676 handle_client_cert(void *data,
677                    const char **cert_path,
678                    apr_pool_t *pool)
679 {
680     svn_ra_serf__connection_t *conn = data;
681     svn_ra_serf__session_t *session = conn->session;
682     const char *realm;
683     void *creds;
684
685     *cert_path = NULL;
686
687     realm = construct_realm(session, session->pool);
688
689     if (!conn->ssl_client_auth_state)
690       {
691         SVN_ERR(svn_auth_first_credentials(&creds,
692                                            &conn->ssl_client_auth_state,
693                                            SVN_AUTH_CRED_SSL_CLIENT_CERT,
694                                            realm,
695                                            session->auth_baton,
696                                            pool));
697       }
698     else
699       {
700         SVN_ERR(svn_auth_next_credentials(&creds,
701                                           conn->ssl_client_auth_state,
702                                           session->pool));
703       }
704
705     if (creds)
706       {
707         svn_auth_cred_ssl_client_cert_t *client_creds;
708         client_creds = creds;
709         *cert_path = client_creds->cert_file;
710       }
711
712     return SVN_NO_ERROR;
713 }
714
715 /* Implements serf_ssl_need_client_cert_t for handle_client_cert */
716 apr_status_t svn_ra_serf__handle_client_cert(void *data,
717                                              const char **cert_path)
718 {
719   svn_ra_serf__connection_t *conn = data;
720   svn_ra_serf__session_t *session = conn->session;
721   svn_error_t *err;
722
723   err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
724
725   return save_error(session, err);
726 }
727
728 /* Implementation for svn_ra_serf__handle_client_cert_pw */
729 static svn_error_t *
730 handle_client_cert_pw(void *data,
731                       const char *cert_path,
732                       const char **password,
733                       apr_pool_t *pool)
734 {
735     svn_ra_serf__connection_t *conn = data;
736     svn_ra_serf__session_t *session = conn->session;
737     void *creds;
738
739     *password = NULL;
740
741     if (!conn->ssl_client_pw_auth_state)
742       {
743         SVN_ERR(svn_auth_first_credentials(&creds,
744                                            &conn->ssl_client_pw_auth_state,
745                                            SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
746                                            cert_path,
747                                            session->auth_baton,
748                                            pool));
749       }
750     else
751       {
752         SVN_ERR(svn_auth_next_credentials(&creds,
753                                           conn->ssl_client_pw_auth_state,
754                                           pool));
755       }
756
757     if (creds)
758       {
759         /* At this stage we are unable to check whether the password
760            is correct; if it is incorrect serf will fail to establish
761            an SSL connection and will return a generic SSL error. */
762         svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
763         pw_creds = creds;
764         *password = pw_creds->password;
765       }
766
767     return APR_SUCCESS;
768 }
769
770 /* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
771 apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
772                                                 const char *cert_path,
773                                                 const char **password)
774 {
775   svn_ra_serf__connection_t *conn = data;
776   svn_ra_serf__session_t *session = conn->session;
777   svn_error_t *err;
778
779   err = svn_error_trace(handle_client_cert_pw(data,
780                                               cert_path,
781                                               password,
782                                               session->pool));
783
784   return save_error(session, err);
785 }
786
787
788 /*
789  * Given a REQUEST on connection CONN, construct a request bucket for it,
790  * returning the bucket in *REQ_BKT.
791  *
792  * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
793  * corresponds to the new request.
794  *
795  * The request will be METHOD at URL.
796  *
797  * If BODY_BKT is not-NULL, it will be sent as the request body.
798  *
799  * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
800  *
801  * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers
802  * to request.
803  *
804  * REQUEST_POOL should live for the duration of the request. Serf will
805  * construct this and provide it to the request_setup callback, so we
806  * should just use that one.
807  */
808 static svn_error_t *
809 setup_serf_req(serf_request_t *request,
810                serf_bucket_t **req_bkt,
811                serf_bucket_t **hdrs_bkt,
812                svn_ra_serf__session_t *session,
813                const char *method, const char *url,
814                serf_bucket_t *body_bkt, const char *content_type,
815                const char *accept_encoding,
816                svn_boolean_t dav_headers,
817                apr_pool_t *request_pool,
818                apr_pool_t *scratch_pool)
819 {
820   serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
821
822   svn_spillbuf_t *buf;
823   svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
824
825   if (set_CL && body_bkt != NULL)
826     {
827       /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
828          it speaks HTTP/1.1 (and thus, chunked requests), or because the
829          server actually responded as only supporting HTTP/1.0.
830
831          We'll take the existing body_bkt, spool it into a spillbuf, and
832          then wrap a bucket around that spillbuf. The spillbuf will give
833          us the Content-Length value.  */
834       SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
835                                               request_pool,
836                                               scratch_pool));
837       /* Destroy original bucket since it content is already copied
838          to spillbuf. */
839       serf_bucket_destroy(body_bkt);
840
841       body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
842                                                request_pool,
843                                                scratch_pool);
844     }
845
846   /* Create a request bucket.  Note that this sucker is kind enough to
847      add a "Host" header for us.  */
848   *req_bkt = serf_request_bucket_request_create(request, method, url,
849                                                 body_bkt, allocator);
850
851   /* Set the Content-Length value. This will also trigger an HTTP/1.0
852      request (rather than the default chunked request).  */
853   if (set_CL)
854     {
855       if (body_bkt == NULL)
856         serf_bucket_request_set_CL(*req_bkt, 0);
857       else
858         serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
859     }
860
861   *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
862
863   /* We use serf_bucket_headers_setn() because the USERAGENT has a
864      lifetime longer than this bucket. Thus, there is no need to copy
865      the header values.  */
866   serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
867
868   if (content_type)
869     {
870       serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
871     }
872
873   if (session->http10)
874     {
875       serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
876     }
877
878   if (accept_encoding)
879     {
880       serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
881     }
882
883   /* These headers need to be sent with every request that might need
884      capability processing (e.g. during commit, reports, etc.), see
885      issue #3255 ("mod_dav_svn does not pass client capabilities to
886      start-commit hooks") for why.
887
888      Some request types like GET/HEAD/PROPFIND are unaware of capability
889      handling; and in some cases the responses can even be cached by
890      proxies, so we don't have to send these hearders there. */
891   if (dav_headers)
892     {
893       serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
894       serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
895       serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
896     }
897
898   return SVN_NO_ERROR;
899 }
900
901 svn_error_t *
902 svn_ra_serf__context_run(svn_ra_serf__session_t *sess,
903                          apr_interval_time_t *waittime_left,
904                          apr_pool_t *scratch_pool)
905 {
906   apr_status_t status;
907   svn_error_t *err;
908   assert(sess->pending_error == SVN_NO_ERROR);
909
910   if (sess->cancel_func)
911     SVN_ERR(sess->cancel_func(sess->cancel_baton));
912
913   status = serf_context_run(sess->context,
914                             SVN_RA_SERF__CONTEXT_RUN_DURATION,
915                             scratch_pool);
916
917   err = sess->pending_error;
918   sess->pending_error = SVN_NO_ERROR;
919
920    /* If the context duration timeout is up, we'll subtract that
921       duration from the total time alloted for such things.  If
922       there's no time left, we fail with a message indicating that
923       the connection timed out.  */
924   if (APR_STATUS_IS_TIMEUP(status))
925     {
926       status = 0;
927
928       if (sess->timeout)
929         {
930           if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
931             {
932               *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
933             }
934           else
935             {
936               return
937                   svn_error_compose_create(
938                         err,
939                         svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
940                                          _("Connection timed out")));
941             }
942         }
943     }
944   else
945     {
946       *waittime_left = sess->timeout;
947     }
948
949   SVN_ERR(err);
950   if (status)
951     {
952       /* ### This omits SVN_WARNING, and possibly relies on the fact that
953          ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */
954       if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
955         {
956           /* apr can't translate subversion errors to text */
957           SVN_ERR_W(svn_error_create(status, NULL, NULL),
958                     _("Error running context"));
959         }
960
961       return svn_ra_serf__wrap_err(status, _("Error running context"));
962     }
963
964   return SVN_NO_ERROR;
965 }
966
967 svn_error_t *
968 svn_ra_serf__context_run_wait(svn_boolean_t *done,
969                               svn_ra_serf__session_t *sess,
970                               apr_pool_t *scratch_pool)
971 {
972   apr_pool_t *iterpool;
973   apr_interval_time_t waittime_left = sess->timeout;
974
975   assert(sess->pending_error == SVN_NO_ERROR);
976
977   iterpool = svn_pool_create(scratch_pool);
978   while (!*done)
979     {
980       int i;
981
982       svn_pool_clear(iterpool);
983
984       SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
985
986       /* Debugging purposes only! */
987       for (i = 0; i < sess->num_conns; i++)
988         {
989           serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
990         }
991     }
992   svn_pool_destroy(iterpool);
993
994   return SVN_NO_ERROR;
995 }
996
997 /* Ensure that a handler is no longer scheduled on the connection.
998
999    Eventually serf will have a reliable way to cancel existing requests,
1000    but currently it doesn't even have a way to relyable identify a request
1001    after rescheduling, for auth reasons.
1002
1003    So the only thing we can do today is reset the connection, which
1004    will cancel all outstanding requests and prepare the connection
1005    for re-use.
1006 */
1007 void
1008 svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler)
1009 {
1010   serf_connection_reset(handler->conn->conn);
1011   handler->scheduled = FALSE;
1012 }
1013
1014 svn_error_t *
1015 svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
1016                              apr_pool_t *scratch_pool)
1017 {
1018   svn_error_t *err;
1019
1020   /* Create a serf request based on HANDLER.  */
1021   svn_ra_serf__request_create(handler);
1022
1023   /* Wait until the response logic marks its DONE status.  */
1024   err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
1025                                       scratch_pool);
1026
1027   if (handler->scheduled)
1028     {
1029       /* We reset the connection (breaking  pipelining, etc.), as
1030          if we didn't the next data would still be handled by this handler,
1031          which is done as far as our caller is concerned. */
1032       svn_ra_serf__unschedule_handler(handler);
1033     }
1034
1035   return svn_error_trace(err);
1036 }
1037
1038
1039
1040
1041 static apr_status_t
1042 drain_bucket(serf_bucket_t *bucket)
1043 {
1044   /* Read whatever is in the bucket, and just drop it.  */
1045   while (1)
1046     {
1047       apr_status_t status;
1048       const char *data;
1049       apr_size_t len;
1050
1051       status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
1052       if (status)
1053         return status;
1054     }
1055 }
1056
1057
1058
1059
1060 /* Implements svn_ra_serf__response_handler_t */
1061 svn_error_t *
1062 svn_ra_serf__handle_discard_body(serf_request_t *request,
1063                                  serf_bucket_t *response,
1064                                  void *baton,
1065                                  apr_pool_t *pool)
1066 {
1067   apr_status_t status;
1068
1069   status = drain_bucket(response);
1070   if (status)
1071     return svn_ra_serf__wrap_err(status, NULL);
1072
1073   return SVN_NO_ERROR;
1074 }
1075
1076 apr_status_t
1077 svn_ra_serf__response_discard_handler(serf_request_t *request,
1078                                       serf_bucket_t *response,
1079                                       void *baton,
1080                                       apr_pool_t *pool)
1081 {
1082   return drain_bucket(response);
1083 }
1084
1085
1086 /* Return the value of the RESPONSE's Location header if any, or NULL
1087    otherwise.  */
1088 static const char *
1089 response_get_location(serf_bucket_t *response,
1090                       const char *base_url,
1091                       apr_pool_t *result_pool,
1092                       apr_pool_t *scratch_pool)
1093 {
1094   serf_bucket_t *headers;
1095   const char *location;
1096
1097   headers = serf_bucket_response_get_headers(response);
1098   location = serf_bucket_headers_get(headers, "Location");
1099   if (location == NULL)
1100     return NULL;
1101
1102   /* The RFCs say we should have received a full url in LOCATION, but
1103      older apache versions and many custom web handlers just return a
1104      relative path here...
1105
1106      And we can't trust anything because it is network data.
1107    */
1108   if (*location == '/')
1109     {
1110       apr_uri_t uri;
1111       apr_status_t status;
1112
1113       status = apr_uri_parse(scratch_pool, base_url, &uri);
1114
1115       if (status != APR_SUCCESS)
1116         return NULL;
1117
1118       /* Replace the path path with what we got */
1119       uri.path = apr_pstrdup(scratch_pool, location);
1120
1121       /* And make APR produce a proper full url for us */
1122       return apr_uri_unparse(result_pool, &uri, 0);
1123     }
1124   else if (!svn_path_is_url(location))
1125     {
1126       return NULL; /* Any other formats we should support? */
1127     }
1128
1129   return apr_pstrdup(result_pool, location);
1130 }
1131
1132
1133 /* Implements svn_ra_serf__response_handler_t */
1134 svn_error_t *
1135 svn_ra_serf__expect_empty_body(serf_request_t *request,
1136                                serf_bucket_t *response,
1137                                void *baton,
1138                                apr_pool_t *scratch_pool)
1139 {
1140   svn_ra_serf__handler_t *handler = baton;
1141   serf_bucket_t *hdrs;
1142   const char *val;
1143
1144   /* This function is just like handle_multistatus_only() except for the
1145      XML parsing callbacks. We want to look for the -readable element.  */
1146
1147   /* We should see this just once, in order to initialize SERVER_ERROR.
1148      At that point, the core error processing will take over. If we choose
1149      not to parse an error, then we'll never return here (because we
1150      change the response handler).  */
1151   SVN_ERR_ASSERT(handler->server_error == NULL);
1152
1153   hdrs = serf_bucket_response_get_headers(response);
1154   val = serf_bucket_headers_get(hdrs, "Content-Type");
1155   if (val
1156       && (handler->sline.code < 200 || handler->sline.code >= 300)
1157       && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1158     {
1159       svn_ra_serf__server_error_t *server_err;
1160
1161       SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1162                                                FALSE,
1163                                                handler->handler_pool,
1164                                                handler->handler_pool));
1165
1166       handler->server_error = server_err;
1167     }
1168   else
1169     {
1170       /* The body was not text/xml, or we got a success code.
1171          Toss anything that arrives.  */
1172       handler->discard_body = TRUE;
1173     }
1174
1175   /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1176      to call the response handler again. That will start up the XML parsing,
1177      or it will be dropped on the floor (per the decision above).  */
1178   return SVN_NO_ERROR;
1179 }
1180
1181
1182 apr_status_t
1183 svn_ra_serf__credentials_callback(char **username, char **password,
1184                                   serf_request_t *request, void *baton,
1185                                   int code, const char *authn_type,
1186                                   const char *realm,
1187                                   apr_pool_t *pool)
1188 {
1189   svn_ra_serf__handler_t *handler = baton;
1190   svn_ra_serf__session_t *session = handler->session;
1191   void *creds;
1192   svn_auth_cred_simple_t *simple_creds;
1193   svn_error_t *err;
1194
1195   if (code == 401)
1196     {
1197       /* Use svn_auth_first_credentials if this is the first time we ask for
1198          credentials during this session OR if the last time we asked
1199          session->auth_state wasn't set (eg. if the credentials provider was
1200          cancelled by the user). */
1201       if (!session->auth_state)
1202         {
1203           err = svn_auth_first_credentials(&creds,
1204                                            &session->auth_state,
1205                                            SVN_AUTH_CRED_SIMPLE,
1206                                            realm,
1207                                            session->auth_baton,
1208                                            session->pool);
1209         }
1210       else
1211         {
1212           err = svn_auth_next_credentials(&creds,
1213                                           session->auth_state,
1214                                           session->pool);
1215         }
1216
1217       if (err)
1218         {
1219           (void) save_error(session, err);
1220           return err->apr_err;
1221         }
1222
1223       session->auth_attempts++;
1224
1225       if (!creds || session->auth_attempts > 4)
1226         {
1227           /* No more credentials. */
1228           (void) save_error(session,
1229                             svn_error_create(
1230                               SVN_ERR_AUTHN_FAILED, NULL,
1231                               _("No more credentials or we tried too many "
1232                                 "times.\nAuthentication failed")));
1233           return SVN_ERR_AUTHN_FAILED;
1234         }
1235
1236       simple_creds = creds;
1237       *username = apr_pstrdup(pool, simple_creds->username);
1238       *password = apr_pstrdup(pool, simple_creds->password);
1239     }
1240   else
1241     {
1242       *username = apr_pstrdup(pool, session->proxy_username);
1243       *password = apr_pstrdup(pool, session->proxy_password);
1244
1245       session->proxy_auth_attempts++;
1246
1247       if (!session->proxy_username || session->proxy_auth_attempts > 4)
1248         {
1249           /* No more credentials. */
1250           (void) save_error(session,
1251                             svn_error_create(
1252                               SVN_ERR_AUTHN_FAILED, NULL,
1253                               _("Proxy authentication failed")));
1254           return SVN_ERR_AUTHN_FAILED;
1255         }
1256     }
1257
1258   handler->conn->last_status_code = code;
1259
1260   return APR_SUCCESS;
1261 }
1262
1263 /* Wait for HTTP response status and headers, and invoke HANDLER->
1264    response_handler() to carry out operation-specific processing.
1265    Afterwards, check for connection close.
1266
1267    SERF_STATUS allows returning errors to serf without creating a
1268    subversion error object.
1269    */
1270 static svn_error_t *
1271 handle_response(serf_request_t *request,
1272                 serf_bucket_t *response,
1273                 svn_ra_serf__handler_t *handler,
1274                 apr_status_t *serf_status,
1275                 apr_pool_t *scratch_pool)
1276 {
1277   apr_status_t status;
1278   svn_error_t *err;
1279
1280   /* ### need to verify whether this already gets init'd on every
1281      ### successful exit. for an error-exit, it will (properly) be
1282      ### ignored by the caller.  */
1283   *serf_status = APR_SUCCESS;
1284
1285   if (!response)
1286     {
1287       /* Uh-oh. Our connection died.  */
1288       handler->scheduled = FALSE;
1289
1290       if (handler->response_error)
1291         {
1292           /* Give a handler chance to prevent request requeue. */
1293           SVN_ERR(handler->response_error(request, response, 0,
1294                                           handler->response_error_baton));
1295
1296           svn_ra_serf__request_create(handler);
1297         }
1298       /* Response error callback is not configured. Requeue another request
1299          for this handler only if we didn't started to process body.
1300          Return error otherwise. */
1301       else if (!handler->reading_body)
1302         {
1303           svn_ra_serf__request_create(handler);
1304         }
1305       else
1306         {
1307           return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1308                                     _("%s request on '%s' failed"),
1309                                    handler->method, handler->path);
1310         }
1311
1312       return SVN_NO_ERROR;
1313     }
1314
1315   /* If we're reading the body, then skip all this preparation.  */
1316   if (handler->reading_body)
1317     goto process_body;
1318
1319   /* Copy the Status-Line info into HANDLER, if we don't yet have it.  */
1320   if (handler->sline.version == 0)
1321     {
1322       serf_status_line sl;
1323
1324       status = serf_bucket_response_status(response, &sl);
1325       if (status != APR_SUCCESS)
1326         {
1327           /* The response line is not (yet) ready, or some other error.  */
1328           *serf_status = status;
1329           return SVN_NO_ERROR; /* Handled by serf */
1330         }
1331
1332       /* If we got APR_SUCCESS, then we should have Status-Line info.  */
1333       SVN_ERR_ASSERT(sl.version != 0);
1334
1335       handler->sline = sl;
1336       handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
1337
1338       /* HTTP/1.1? (or later)  */
1339       if (sl.version != SERF_HTTP_10)
1340         handler->session->http10 = FALSE;
1341
1342       if (sl.version >= SERF_HTTP_VERSION(2, 0)) {
1343         handler->session->http20 = TRUE;
1344       }
1345     }
1346
1347   /* Keep reading from the network until we've read all the headers.  */
1348   status = serf_bucket_response_wait_for_headers(response);
1349   if (status)
1350     {
1351       /* The typical "error" will be APR_EAGAIN, meaning that more input
1352          from the network is required to complete the reading of the
1353          headers.  */
1354       if (!APR_STATUS_IS_EOF(status))
1355         {
1356           /* Either the headers are not (yet) complete, or there really
1357              was an error.  */
1358           *serf_status = status;
1359           return SVN_NO_ERROR;
1360         }
1361
1362       /* wait_for_headers() will return EOF if there is no body in this
1363          response, or if we completely read the body. The latter is not
1364          true since we would have set READING_BODY to get the body read,
1365          and we would not be back to this code block.
1366
1367          It can also return EOF if we truly hit EOF while (say) processing
1368          the headers. aka Badness.  */
1369
1370       /* Cases where a lack of a response body (via EOF) is okay:
1371        *  - A HEAD request
1372        *  - 204/304 response
1373        *
1374        * Otherwise, if we get an EOF here, something went really wrong: either
1375        * the server closed on us early or we're reading too much.  Either way,
1376        * scream loudly.
1377        */
1378       if (strcmp(handler->method, "HEAD") != 0
1379           && handler->sline.code != 204
1380           && handler->sline.code != 304)
1381         {
1382           err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
1383                                   svn_ra_serf__wrap_err(status, NULL),
1384                                   _("Premature EOF seen from server"
1385                                     " (http status=%d)"),
1386                                   handler->sline.code);
1387
1388           /* In case anything else arrives... discard it.  */
1389           handler->discard_body = TRUE;
1390
1391           return err;
1392         }
1393     }
1394
1395   /* ... and set up the header fields in HANDLER.  */
1396   handler->location = response_get_location(response,
1397                                             handler->session->session_url_str,
1398                                             handler->handler_pool,
1399                                             scratch_pool);
1400
1401   /* On the last request, we failed authentication. We succeeded this time,
1402      so let's save away these credentials.  */
1403   if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
1404     {
1405       SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
1406                                         handler->session->pool));
1407       handler->session->auth_attempts = 0;
1408       handler->session->auth_state = NULL;
1409     }
1410   handler->conn->last_status_code = handler->sline.code;
1411
1412   if (handler->sline.code >= 400)
1413     {
1414       /* 405 Method Not allowed.
1415          408 Request Timeout
1416          409 Conflict: can indicate a hook error.
1417          5xx (Internal) Server error. */
1418       serf_bucket_t *hdrs;
1419       const char *val;
1420
1421       hdrs = serf_bucket_response_get_headers(response);
1422       val = serf_bucket_headers_get(hdrs, "Content-Type");
1423       if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1424         {
1425           svn_ra_serf__server_error_t *server_err;
1426
1427           SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1428                                                    FALSE,
1429                                                    handler->handler_pool,
1430                                                    handler->handler_pool));
1431
1432           handler->server_error = server_err;
1433         }
1434       else
1435         {
1436           handler->discard_body = TRUE;
1437         }
1438     }
1439   else if (handler->sline.code <= 199)
1440     {
1441       handler->discard_body = TRUE;
1442     }
1443
1444   /* Stop processing the above, on every packet arrival.  */
1445   handler->reading_body = TRUE;
1446
1447  process_body:
1448
1449   /* A client cert file password was obtained and worked (any HTTP
1450      response means that the SSL connection was established.) */
1451   if (handler->conn->ssl_client_pw_auth_state)
1452     {
1453       SVN_ERR(svn_auth_save_credentials(handler->conn->ssl_client_pw_auth_state,
1454                                         handler->session->pool));
1455       handler->conn->ssl_client_pw_auth_state = NULL;
1456     }
1457   if (handler->conn->ssl_client_auth_state)
1458     {
1459       /* The cert file provider doesn't have any code to save creds so
1460          this is currently a no-op. */
1461       SVN_ERR(svn_auth_save_credentials(handler->conn->ssl_client_auth_state,
1462                                         handler->session->pool));
1463       handler->conn->ssl_client_auth_state = NULL;
1464     }
1465
1466   /* We've been instructed to ignore the body. Drain whatever is present.  */
1467   if (handler->discard_body)
1468     {
1469       *serf_status = drain_bucket(response);
1470
1471       return SVN_NO_ERROR;
1472     }
1473
1474   /* If we are supposed to parse the body as a server_error, then do
1475      that now.  */
1476   if (handler->server_error != NULL)
1477     {
1478       return svn_error_trace(
1479                 svn_ra_serf__handle_server_error(handler->server_error,
1480                                                  handler,
1481                                                  request, response,
1482                                                  serf_status,
1483                                                  scratch_pool));
1484     }
1485
1486   /* Pass the body along to the registered response handler.  */
1487   err = handler->response_handler(request, response,
1488                                   handler->response_baton,
1489                                   scratch_pool);
1490
1491   if (err
1492       && (!SERF_BUCKET_READ_ERROR(err->apr_err)
1493           || APR_STATUS_IS_ECONNRESET(err->apr_err)
1494           || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
1495     {
1496       /* These errors are special cased in serf
1497          ### We hope no handler returns these by accident. */
1498       *serf_status = err->apr_err;
1499       svn_error_clear(err);
1500       return SVN_NO_ERROR;
1501     }
1502
1503   return svn_error_trace(err);
1504 }
1505
1506
1507 /* Implements serf_response_handler_t for handle_response. Storing
1508    errors in handler->session->pending_error if appropriate. */
1509 static apr_status_t
1510 handle_response_cb(serf_request_t *request,
1511                    serf_bucket_t *response,
1512                    void *baton,
1513                    apr_pool_t *response_pool)
1514 {
1515   svn_ra_serf__handler_t *handler = baton;
1516   svn_error_t *err;
1517   apr_status_t inner_status;
1518   apr_status_t outer_status;
1519   apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */
1520
1521   err = svn_error_trace(handle_response(request, response,
1522                                         handler, &inner_status,
1523                                         scratch_pool));
1524
1525   /* Select the right status value to return.  */
1526   outer_status = save_error(handler->session, err);
1527   if (!outer_status)
1528     outer_status = inner_status;
1529
1530   /* Make sure the DONE flag is set properly and requests are cleaned up. */
1531   if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
1532     {
1533       svn_ra_serf__session_t *sess = handler->session;
1534       handler->done = TRUE;
1535       handler->scheduled = FALSE;
1536       outer_status = APR_EOF;
1537
1538       /* We use a cached handler->session here to allow handler to free the
1539          memory containing the handler */
1540       save_error(sess,
1541                  handler->done_delegate(request, handler->done_delegate_baton,
1542                                         scratch_pool));
1543     }
1544   else if (SERF_BUCKET_READ_ERROR(outer_status)
1545            && handler->session->pending_error)
1546     {
1547       handler->discard_body = TRUE; /* Discard further data */
1548       handler->done = TRUE; /* Mark as done */
1549       /* handler->scheduled is still TRUE, as we still expect data.
1550          If we would return an error outer-status the connection
1551          would have to be restarted. With scheduled still TRUE
1552          destroying the handler's pool will still reset the
1553          connection, avoiding the posibility of returning
1554          an error for this handler when a new request is
1555          scheduled. */
1556       outer_status = APR_EAGAIN; /* Exit context loop */
1557     }
1558
1559   return outer_status;
1560 }
1561
1562 /* Perform basic request setup, with special handling for HEAD requests,
1563    and finer-grained callbacks invoked (if non-NULL) to produce the request
1564    headers and body. */
1565 static svn_error_t *
1566 setup_request(serf_request_t *request,
1567               svn_ra_serf__handler_t *handler,
1568               serf_bucket_t **req_bkt,
1569               apr_pool_t *request_pool,
1570               apr_pool_t *scratch_pool)
1571 {
1572   serf_bucket_t *body_bkt;
1573   serf_bucket_t *headers_bkt;
1574   const char *accept_encoding;
1575
1576   if (handler->body_delegate)
1577     {
1578       serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
1579
1580       SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
1581                                      bkt_alloc, request_pool, scratch_pool));
1582     }
1583   else
1584     {
1585       body_bkt = NULL;
1586     }
1587
1588   if (handler->custom_accept_encoding)
1589     {
1590       accept_encoding = NULL;
1591     }
1592   else if (handler->session->using_compression != svn_tristate_false)
1593     {
1594       /* Accept gzip compression if enabled. */
1595       accept_encoding = "gzip";
1596     }
1597   else
1598     {
1599       accept_encoding = NULL;
1600     }
1601
1602   SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
1603                          handler->session, handler->method, handler->path,
1604                          body_bkt, handler->body_type, accept_encoding,
1605                          !handler->no_dav_headers, request_pool,
1606                          scratch_pool));
1607
1608   if (handler->header_delegate)
1609     {
1610       SVN_ERR(handler->header_delegate(headers_bkt,
1611                                        handler->header_delegate_baton,
1612                                        request_pool, scratch_pool));
1613     }
1614
1615   return SVN_NO_ERROR;
1616 }
1617
1618 /* Implements the serf_request_setup_t interface (which sets up both a
1619    request and its response handler callback). Handles errors for
1620    setup_request_cb */
1621 static apr_status_t
1622 setup_request_cb(serf_request_t *request,
1623               void *setup_baton,
1624               serf_bucket_t **req_bkt,
1625               serf_response_acceptor_t *acceptor,
1626               void **acceptor_baton,
1627               serf_response_handler_t *s_handler,
1628               void **s_handler_baton,
1629               apr_pool_t *request_pool)
1630 {
1631   svn_ra_serf__handler_t *handler = setup_baton;
1632   apr_pool_t *scratch_pool;
1633   svn_error_t *err;
1634
1635   /* Construct a scratch_pool? serf gives us a pool that will live for
1636      the duration of the request. But requests are retried in some cases */
1637   scratch_pool = svn_pool_create(request_pool);
1638
1639   if (strcmp(handler->method, "HEAD") == 0)
1640     *acceptor = accept_head;
1641   else
1642     *acceptor = accept_response;
1643   *acceptor_baton = handler;
1644
1645   *s_handler = handle_response_cb;
1646   *s_handler_baton = handler;
1647
1648   err = svn_error_trace(setup_request(request, handler, req_bkt,
1649                                       request_pool, scratch_pool));
1650
1651   svn_pool_destroy(scratch_pool);
1652   return save_error(handler->session, err);
1653 }
1654
1655 void
1656 svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
1657 {
1658   SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL
1659                            && !handler->scheduled);
1660
1661   /* In case HANDLER is re-queued, reset the various transient fields. */
1662   handler->done = FALSE;
1663   handler->server_error = NULL;
1664   handler->sline.version = 0;
1665   handler->location = NULL;
1666   handler->reading_body = FALSE;
1667   handler->discard_body = FALSE;
1668   handler->scheduled = TRUE;
1669
1670   /* Keeping track of the returned request object would be nice, but doesn't
1671      work the way we would expect in ra_serf..
1672
1673      Serf sometimes creates a new request for us (and destroys the old one)
1674      without telling, like when authentication failed (401/407 response.
1675
1676      We 'just' trust serf to do the right thing and expect it to tell us
1677      when the state of the request changes.
1678
1679      ### I fixed a request leak in serf in r2258 on auth failures.
1680    */
1681   (void) serf_connection_request_create(handler->conn->conn,
1682                                         setup_request_cb, handler);
1683 }
1684
1685
1686 svn_error_t *
1687 svn_ra_serf__discover_vcc(const char **vcc_url,
1688                           svn_ra_serf__session_t *session,
1689                           apr_pool_t *scratch_pool)
1690 {
1691   const char *path;
1692   const char *relative_path;
1693   const char *uuid;
1694
1695   /* If we've already got the information our caller seeks, just return it.  */
1696   if (session->vcc_url && session->repos_root_str)
1697     {
1698       *vcc_url = session->vcc_url;
1699       return SVN_NO_ERROR;
1700     }
1701
1702   path = session->session_url.path;
1703   *vcc_url = NULL;
1704   uuid = NULL;
1705
1706   do
1707     {
1708       apr_hash_t *props;
1709       svn_error_t *err;
1710
1711       err = svn_ra_serf__fetch_node_props(&props, session,
1712                                           path, SVN_INVALID_REVNUM,
1713                                           base_props,
1714                                           scratch_pool, scratch_pool);
1715       if (! err)
1716         {
1717           apr_hash_t *ns_props;
1718
1719           ns_props = apr_hash_get(props, "DAV:", 4);
1720           *vcc_url = svn_prop_get_value(ns_props,
1721                                         "version-controlled-configuration");
1722
1723           ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
1724           relative_path = svn_prop_get_value(ns_props,
1725                                              "baseline-relative-path");
1726           uuid = svn_prop_get_value(ns_props, "repository-uuid");
1727           break;
1728         }
1729       else
1730         {
1731           if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
1732               (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
1733             {
1734               return svn_error_trace(err);  /* found a _real_ error */
1735             }
1736           else
1737             {
1738               /* This happens when the file is missing in HEAD. */
1739               svn_error_clear(err);
1740
1741               /* Okay, strip off a component from PATH. */
1742               path = svn_urlpath__dirname(path, scratch_pool);
1743             }
1744         }
1745     }
1746   while ((path[0] != '\0')
1747          && (! (path[0] == '/' && path[1] == '\0')));
1748
1749   if (!*vcc_url)
1750     {
1751       return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1752                               _("The PROPFIND response did not include the "
1753                                 "requested version-controlled-configuration "
1754                                 "value"));
1755     }
1756
1757   /* Store our VCC in our cache. */
1758   if (!session->vcc_url)
1759     {
1760       session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
1761     }
1762
1763   /* Update our cached repository root URL. */
1764   if (!session->repos_root_str)
1765     {
1766       svn_stringbuf_t *url_buf;
1767
1768       url_buf = svn_stringbuf_create(path, scratch_pool);
1769
1770       svn_path_remove_components(url_buf,
1771                                  svn_path_component_count(relative_path));
1772
1773       /* Now recreate the root_url. */
1774       session->repos_root = session->session_url;
1775       session->repos_root.path =
1776         (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
1777       session->repos_root_str =
1778         svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
1779                                                   &session->repos_root, 0),
1780                                   session->pool);
1781     }
1782
1783   /* Store the repository UUID in the cache. */
1784   if (!session->uuid)
1785     {
1786       session->uuid = apr_pstrdup(session->pool, uuid);
1787     }
1788
1789   return SVN_NO_ERROR;
1790 }
1791
1792 svn_error_t *
1793 svn_ra_serf__get_relative_path(const char **rel_path,
1794                                const char *orig_path,
1795                                svn_ra_serf__session_t *session,
1796                                apr_pool_t *pool)
1797 {
1798   const char *decoded_root, *decoded_orig;
1799
1800   if (! session->repos_root.path)
1801     {
1802       const char *vcc_url;
1803
1804       /* This should only happen if we haven't detected HTTP v2
1805          support from the server.  */
1806       assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1807
1808       /* We don't actually care about the VCC_URL, but this API
1809          promises to populate the session's root-url cache, and that's
1810          what we really want. */
1811       SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
1812                                         pool));
1813     }
1814
1815   decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
1816   decoded_orig = svn_path_uri_decode(orig_path, pool);
1817   *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
1818   SVN_ERR_ASSERT(*rel_path != NULL);
1819   return SVN_NO_ERROR;
1820 }
1821
1822 svn_error_t *
1823 svn_ra_serf__report_resource(const char **report_target,
1824                              svn_ra_serf__session_t *session,
1825                              apr_pool_t *pool)
1826 {
1827   /* If we have HTTP v2 support, we want to report against the 'me'
1828      resource. */
1829   if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
1830     *report_target = apr_pstrdup(pool, session->me_resource);
1831
1832   /* Otherwise, we'll use the default VCC. */
1833   else
1834     SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool));
1835
1836   return SVN_NO_ERROR;
1837 }
1838
1839 svn_error_t *
1840 svn_ra_serf__error_on_status(serf_status_line sline,
1841                              const char *path,
1842                              const char *location)
1843 {
1844   switch(sline.code)
1845     {
1846       case 301:
1847       case 302:
1848       case 303:
1849       case 307:
1850       case 308:
1851         return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
1852                                  (sline.code == 301)
1853                                   ? _("Repository moved permanently to '%s'")
1854                                   : _("Repository moved temporarily to '%s'"),
1855                                  location);
1856       case 403:
1857         return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
1858                                  _("Access to '%s' forbidden"), path);
1859
1860       case 404:
1861         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1862                                  _("'%s' path not found"), path);
1863       case 405:
1864         return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1865                                  _("HTTP method is not allowed on '%s'"),
1866                                  path);
1867       case 409:
1868         return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1869                                  _("'%s' conflicts"), path);
1870       case 412:
1871         return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL,
1872                                  _("Precondition on '%s' failed"), path);
1873       case 423:
1874         return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
1875                                  _("'%s': no lock token available"), path);
1876
1877       case 411:
1878         return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1879                     _("DAV request failed: 411 Content length required. The "
1880                       "server or an intermediate proxy does not accept "
1881                       "chunked encoding. Try setting 'http-chunked-requests' "
1882                       "to 'auto' or 'no' in your client configuration."));
1883       case 500:
1884         return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1885                                  _("Unexpected server error %d '%s' on '%s'"),
1886                                  sline.code, sline.reason, path);
1887       case 501:
1888         return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1889                                  _("The requested feature is not supported by "
1890                                    "'%s'"), path);
1891     }
1892
1893   if (sline.code >= 300 || sline.code <= 199)
1894     return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1895                              _("Unexpected HTTP status %d '%s' on '%s'"),
1896                              sline.code, sline.reason, path);
1897
1898   return SVN_NO_ERROR;
1899 }
1900
1901 svn_error_t *
1902 svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler)
1903 {
1904   /* Is it a standard error status? */
1905   if (handler->sline.code != 405)
1906     SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
1907                                          handler->path,
1908                                          handler->location));
1909
1910   switch (handler->sline.code)
1911     {
1912       case 201:
1913         return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1914                                  _("Path '%s' unexpectedly created"),
1915                                  handler->path);
1916       case 204:
1917         return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1918                                  _("Path '%s' already exists"),
1919                                  handler->path);
1920
1921       case 405:
1922         return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1923                                  _("The HTTP method '%s' is not allowed"
1924                                    " on '%s'"),
1925                                  handler->method, handler->path);
1926       default:
1927         return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1928                                  _("Unexpected HTTP status %d '%s' on '%s' "
1929                                    "request to '%s'"),
1930                                  handler->sline.code, handler->sline.reason,
1931                                  handler->method, handler->path);
1932     }
1933 }
1934
1935 svn_error_t *
1936 svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
1937                                     svn_delta_shim_callbacks_t *callbacks)
1938 {
1939   svn_ra_serf__session_t *session = ra_session->priv;
1940
1941   session->shim_callbacks = callbacks;
1942   return SVN_NO_ERROR;
1943 }
1944
1945 /* Shared/standard done_delegate handler */
1946 static svn_error_t *
1947 response_done(serf_request_t *request,
1948               void *handler_baton,
1949               apr_pool_t *scratch_pool)
1950 {
1951   svn_ra_serf__handler_t *handler = handler_baton;
1952
1953   assert(handler->done);
1954
1955   if (handler->no_fail_on_http_failure_status)
1956     return SVN_NO_ERROR;
1957
1958   if (handler->server_error)
1959     return svn_ra_serf__server_error_create(handler, scratch_pool);
1960
1961   if (handler->sline.code >= 400 || handler->sline.code <= 199)
1962     {
1963       return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1964     }
1965
1966   if ((handler->sline.code >= 300 && handler->sline.code < 399)
1967       && !handler->no_fail_on_http_redirect_status)
1968     {
1969       return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1970     }
1971
1972   return SVN_NO_ERROR;
1973 }
1974
1975 /* Pool cleanup handler for request handlers.
1976
1977    If a serf context run stops for some outside error, like when the user
1978    cancels a request via ^C in the context loop, the handler is still
1979    registered in the serf context. With the pool cleanup there would be
1980    handlers registered in no freed memory.
1981
1982    This fallback kills the connection for this case, which will make serf
1983    unregister any outstanding requests on it. */
1984 static apr_status_t
1985 handler_cleanup(void *baton)
1986 {
1987   svn_ra_serf__handler_t *handler = baton;
1988   if (handler->scheduled)
1989     {
1990       svn_ra_serf__unschedule_handler(handler);
1991     }
1992
1993   return APR_SUCCESS;
1994 }
1995
1996 svn_ra_serf__handler_t *
1997 svn_ra_serf__create_handler(svn_ra_serf__session_t *session,
1998                             apr_pool_t *result_pool)
1999 {
2000   svn_ra_serf__handler_t *handler;
2001
2002   handler = apr_pcalloc(result_pool, sizeof(*handler));
2003   handler->handler_pool = result_pool;
2004
2005   apr_pool_cleanup_register(result_pool, handler, handler_cleanup,
2006                             apr_pool_cleanup_null);
2007
2008   handler->session = session;
2009   handler->conn = session->conns[0];
2010
2011   /* Setup the default done handler, to handle server errors */
2012   handler->done_delegate_baton = handler;
2013   handler->done_delegate = response_done;
2014
2015   return handler;
2016 }
2017
2018 svn_error_t *
2019 svn_ra_serf__uri_parse(apr_uri_t *uri,
2020                        const char *url_str,
2021                        apr_pool_t *result_pool)
2022 {
2023   apr_status_t status;
2024
2025   status = apr_uri_parse(result_pool, url_str, uri);
2026   if (status)
2027     {
2028       /* Do not use returned error status in error message because currently
2029          apr_uri_parse() returns APR_EGENERAL for all parsing errors. */
2030       return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2031                                _("Illegal URL '%s'"),
2032                                url_str);
2033     }
2034
2035   /* Depending the version of apr-util in use, for root paths uri.path
2036      will be NULL or "", where serf requires "/". */
2037   if (uri->path == NULL || uri->path[0] == '\0')
2038     {
2039       uri->path = apr_pstrdup(result_pool, "/");
2040     }
2041
2042   return SVN_NO_ERROR;
2043 }
2044
2045 void
2046 svn_ra_serf__setup_svndiff_accept_encoding(serf_bucket_t *headers,
2047                                            svn_ra_serf__session_t *session)
2048 {
2049   if (session->using_compression == svn_tristate_false)
2050     {
2051       /* Don't advertise support for compressed svndiff formats if
2052          compression is disabled. */
2053       serf_bucket_headers_setn(
2054         headers, "Accept-Encoding", "svndiff");
2055     }
2056   else if (session->using_compression == svn_tristate_unknown &&
2057            svn_ra_serf__is_low_latency_connection(session))
2058     {
2059       /* With http-compression=auto, advertise that we prefer svndiff2
2060          to svndiff1 with a low latency connection (assuming the underlying
2061          network has high bandwidth), as it is faster and in this case, we
2062          don't care about worse compression ratio. */
2063       serf_bucket_headers_setn(
2064         headers, "Accept-Encoding",
2065         "gzip,svndiff2;q=0.9,svndiff1;q=0.8,svndiff;q=0.7");
2066     }
2067   else
2068     {
2069       /* Otherwise, advertise that we prefer svndiff1 over svndiff2.
2070          svndiff2 is not a reasonable substitute for svndiff1 with default
2071          compression level, because, while it is faster, it also gives worse
2072          compression ratio.  While we can use svndiff2 in some cases (see
2073          above), we can't do this generally. */
2074       serf_bucket_headers_setn(
2075         headers, "Accept-Encoding",
2076         "gzip,svndiff1;q=0.9,svndiff2;q=0.8,svndiff;q=0.7");
2077     }
2078 }
2079
2080 svn_boolean_t
2081 svn_ra_serf__is_low_latency_connection(svn_ra_serf__session_t *session)
2082 {
2083   return session->conn_latency >= 0 &&
2084          session->conn_latency < apr_time_from_msec(5);
2085 }
2086
2087 apr_array_header_t *
2088 svn_ra_serf__get_dirent_props(apr_uint32_t dirent_fields,
2089                               svn_ra_serf__session_t *session,
2090                               apr_pool_t *result_pool)
2091 {
2092   svn_ra_serf__dav_props_t *prop;
2093   apr_array_header_t *props = apr_array_make
2094     (result_pool, 7, sizeof(svn_ra_serf__dav_props_t));
2095
2096   if (session->supports_deadprop_count != svn_tristate_false
2097       || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
2098     {
2099       if (dirent_fields & SVN_DIRENT_KIND)
2100         {
2101           prop = apr_array_push(props);
2102           prop->xmlns = "DAV:";
2103           prop->name = "resourcetype";
2104         }
2105
2106       if (dirent_fields & SVN_DIRENT_SIZE)
2107         {
2108           prop = apr_array_push(props);
2109           prop->xmlns = "DAV:";
2110           prop->name = "getcontentlength";
2111         }
2112
2113       if (dirent_fields & SVN_DIRENT_HAS_PROPS)
2114         {
2115           prop = apr_array_push(props);
2116           prop->xmlns = SVN_DAV_PROP_NS_DAV;
2117           prop->name = "deadprop-count";
2118         }
2119
2120       if (dirent_fields & SVN_DIRENT_CREATED_REV)
2121         {
2122           svn_ra_serf__dav_props_t *p = apr_array_push(props);
2123           p->xmlns = "DAV:";
2124           p->name = SVN_DAV__VERSION_NAME;
2125         }
2126
2127       if (dirent_fields & SVN_DIRENT_TIME)
2128         {
2129           prop = apr_array_push(props);
2130           prop->xmlns = "DAV:";
2131           prop->name = SVN_DAV__CREATIONDATE;
2132         }
2133
2134       if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
2135         {
2136           prop = apr_array_push(props);
2137           prop->xmlns = "DAV:";
2138           prop->name = "creator-displayname";
2139         }
2140     }
2141   else
2142     {
2143       /* We found an old subversion server that can't handle
2144          the deadprop-count property in the way we expect.
2145
2146          The neon behavior is to retrieve all properties in this case */
2147       prop = apr_array_push(props);
2148       prop->xmlns = "DAV:";
2149       prop->name = "allprop";
2150     }
2151
2152   return props;
2153 }
2154