1 /*
2 * util.c : serf utility routines for ra_serf
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24
25
26 #include <assert.h>
27
28 #define APR_WANT_STRFUNC
29 #include <apr.h>
30 #include <apr_want.h>
31
32 #include <serf.h>
33 #include <serf_bucket_types.h>
34
35 #include "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
ssl_convert_serf_failures(int failures)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
save_error(svn_ra_serf__session_t * session,svn_error_t * err)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 *
construct_realm(svn_ra_serf__session_t * session,apr_pool_t * pool)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 *
convert_organisation_to_str(apr_hash_t * org,apr_pool_t * pool)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
append_reason(svn_stringbuf_t * errmsg,const char * reason,int * reasons)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 *
ssl_server_cert(void * baton,int failures,const serf_ssl_certificate_t * cert,apr_pool_t * scratch_pool)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
ssl_server_cert_cb(void * baton,int failures,const serf_ssl_certificate_t * cert)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 *
load_authorities(svn_ra_serf__connection_t * conn,const char * authorities,apr_pool_t * pool)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
conn_negotiate_protocol(void * data,const char * protocol)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 *
conn_setup(apr_socket_t * sock,serf_bucket_t ** read_bkt,serf_bucket_t ** write_bkt,void * baton,apr_pool_t * pool)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
svn_ra_serf__conn_setup(apr_socket_t * sock,serf_bucket_t ** read_bkt,serf_bucket_t ** write_bkt,void * baton,apr_pool_t * pool)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 *
accept_response(serf_request_t * request,serf_bucket_t * stream,void * acceptor_baton,apr_pool_t * pool)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 *
accept_head(serf_request_t * request,serf_bucket_t * stream,void * acceptor_baton,apr_pool_t * pool)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 *
connection_closed(svn_ra_serf__connection_t * conn,apr_status_t why,apr_pool_t * pool)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
svn_ra_serf__conn_closed(serf_connection_t * conn,void * closed_baton,apr_status_t why,apr_pool_t * pool)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 *
handle_client_cert(void * data,const char ** cert_path,apr_pool_t * pool)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 */
svn_ra_serf__handle_client_cert(void * data,const char ** cert_path)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 *
handle_client_cert_pw(void * data,const char * cert_path,const char ** password,apr_pool_t * pool)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 */
svn_ra_serf__handle_client_cert_pw(void * data,const char * cert_path,const char ** password)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 *
setup_serf_req(serf_request_t * request,serf_bucket_t ** req_bkt,serf_bucket_t ** hdrs_bkt,svn_ra_serf__session_t * session,const char * method,const char * url,serf_bucket_t * body_bkt,const char * content_type,const char * accept_encoding,svn_boolean_t dav_headers,apr_pool_t * request_pool,apr_pool_t * scratch_pool)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 *
svn_ra_serf__context_run(svn_ra_serf__session_t * sess,apr_interval_time_t * waittime_left,apr_pool_t * scratch_pool)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 *
svn_ra_serf__context_run_wait(svn_boolean_t * done,svn_ra_serf__session_t * sess,apr_pool_t * scratch_pool)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
svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t * handler)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 *
svn_ra_serf__context_run_one(svn_ra_serf__handler_t * handler,apr_pool_t * scratch_pool)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
drain_bucket(serf_bucket_t * bucket)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 *
svn_ra_serf__handle_discard_body(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * pool)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
svn_ra_serf__response_discard_handler(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * pool)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 *
response_get_location(serf_bucket_t * response,const char * base_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
svn_ra_serf__expect_empty_body(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * scratch_pool)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
svn_ra_serf__credentials_callback(char ** username,char ** password,serf_request_t * request,void * baton,int code,const char * authn_type,const char * realm,apr_pool_t * pool)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 *
handle_response(serf_request_t * request,serf_bucket_t * response,svn_ra_serf__handler_t * handler,apr_status_t * serf_status,apr_pool_t * scratch_pool)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
handle_response_cb(serf_request_t * request,serf_bucket_t * response,void * baton,apr_pool_t * response_pool)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 *
setup_request(serf_request_t * request,svn_ra_serf__handler_t * handler,serf_bucket_t ** req_bkt,apr_pool_t * request_pool,apr_pool_t * scratch_pool)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
setup_request_cb(serf_request_t * request,void * setup_baton,serf_bucket_t ** req_bkt,serf_response_acceptor_t * acceptor,void ** acceptor_baton,serf_response_handler_t * s_handler,void ** s_handler_baton,apr_pool_t * request_pool)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
svn_ra_serf__request_create(svn_ra_serf__handler_t * handler)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 *
svn_ra_serf__discover_vcc(const char ** vcc_url,svn_ra_serf__session_t * session,apr_pool_t * scratch_pool)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 *
svn_ra_serf__get_relative_path(const char ** rel_path,const char * orig_path,svn_ra_serf__session_t * session,apr_pool_t * pool)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 *
svn_ra_serf__report_resource(const char ** report_target,svn_ra_serf__session_t * session,apr_pool_t * pool)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 *
svn_ra_serf__error_on_status(serf_status_line sline,const char * path,const char * location)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 *
svn_ra_serf__unexpected_status(svn_ra_serf__handler_t * handler)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 *
svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t * ra_session,svn_delta_shim_callbacks_t * callbacks)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 *
response_done(serf_request_t * request,void * handler_baton,apr_pool_t * scratch_pool)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
handler_cleanup(void * baton)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 *
svn_ra_serf__create_handler(svn_ra_serf__session_t * session,apr_pool_t * result_pool)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 *
svn_ra_serf__uri_parse(apr_uri_t * uri,const char * url_str,apr_pool_t * result_pool)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
svn_ra_serf__setup_svndiff_accept_encoding(serf_bucket_t * headers,svn_ra_serf__session_t * session)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
svn_ra_serf__is_low_latency_connection(svn_ra_serf__session_t * session)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 *
svn_ra_serf__get_dirent_props(apr_uint32_t dirent_fields,svn_ra_serf__session_t * session,apr_pool_t * result_pool)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
2155