2 * util.c : serf utility routines for ra_serf
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
28 #define APR_WANT_STRFUNC
33 #include <serf_bucket_types.h>
36 #include "svn_dirent_uri.h"
38 #include "svn_private_config.h"
39 #include "svn_string.h"
40 #include "svn_props.h"
41 #include "svn_dirent_uri.h"
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"
51 static const apr_uint32_t serf_failure_map[][2] =
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 }
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. */
63 ssl_convert_serf_failures(int failures)
65 apr_uint32_t svn_failures = 0;
69 i < sizeof(serf_failure_map) / (sizeof(serf_failure_map[0]));
72 if (failures & serf_failure_map[i][0])
74 svn_failures |= serf_failure_map[i][1];
75 failures &= ~serf_failure_map[i][0];
79 /* Map any remaining failure bits to our OTHER bit. */
82 svn_failures |= SVN_AUTH_SSL_OTHER;
90 save_error(svn_ra_serf__session_t *session,
93 if (err || session->pending_error)
95 session->pending_error = svn_error_compose_create(
96 session->pending_error,
98 return session->pending_error->apr_err;
105 /* Construct the realmstring, e.g. https://svn.collab.net:443. */
107 construct_realm(svn_ra_serf__session_t *session,
113 if (session->session_url.port_str)
115 port = session->session_url.port;
119 port = apr_uri_port_of_scheme(session->session_url.scheme);
122 realm = apr_psprintf(pool, "%s://%s:%d",
123 session->session_url.scheme,
124 session->session_url.hostname,
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(). */
134 convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
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);
147 svn_stringbuf_appendcstr(buf, cn);
148 svn_stringbuf_appendcstr(buf, ", ");
153 svn_stringbuf_appendcstr(buf, org_unit);
154 svn_stringbuf_appendcstr(buf, ", ");
159 svn_stringbuf_appendcstr(buf, org_name);
160 svn_stringbuf_appendcstr(buf, ", ");
165 svn_stringbuf_appendcstr(buf, locality);
166 svn_stringbuf_appendcstr(buf, ", ");
171 svn_stringbuf_appendcstr(buf, state);
172 svn_stringbuf_appendcstr(buf, ", ");
177 svn_stringbuf_appendcstr(buf, country);
178 svn_stringbuf_appendcstr(buf, ", ");
181 /* Chop ', ' if any. */
182 svn_stringbuf_chop(buf, 2);
186 svn_stringbuf_appendcstr(buf, "(");
187 svn_stringbuf_appendcstr(buf, email);
188 svn_stringbuf_appendcstr(buf, ")");
194 static void append_reason(svn_stringbuf_t *errmsg, const char *reason, int *reasons)
197 svn_stringbuf_appendcstr(errmsg, _(": "));
199 svn_stringbuf_appendcstr(errmsg, _(", "));
200 svn_stringbuf_appendcstr(errmsg, reason);
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. */
211 ssl_server_cert(void *baton, int failures,
212 const serf_ssl_certificate_t *cert,
213 apr_pool_t *scratch_pool)
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;
222 apr_hash_t *subject = NULL;
223 apr_hash_t *serf_cert = NULL;
226 svn_failures = (ssl_convert_serf_failures(failures)
227 | conn->server_cert_failures);
229 if (serf_ssl_cert_depth(cert) == 0)
231 /* If the depth is 0, the hostname must match the certificate.
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);
240 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
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)
253 for (i = 0; i < san->nelts; i++)
255 const char *s = APR_ARRAY_IDX(san, i, const char*);
256 svn_string_t *cert_hostname = svn_string_create(s, scratch_pool);
258 if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
260 found_matching_hostname = TRUE;
267 const char *hostname = NULL;
269 subject = serf_ssl_cert_subject(cert, scratch_pool);
272 hostname = svn_hash_gets(subject, "CN");
276 svn_string_t *cert_hostname = svn_string_create(hostname,
279 if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
281 found_matching_hostname = TRUE;
286 if (!found_matching_hostname)
287 svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
293 /* Extract the info from the certificate */
295 subject = serf_ssl_cert_subject(cert, scratch_pool);
296 issuer = serf_ssl_cert_issuer(cert, scratch_pool);
298 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
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);
313 /* Handle any non-server certs. */
314 if (serf_ssl_cert_depth(cert) > 0)
318 svn_auth_set_parameter(conn->session->auth_baton,
319 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
322 svn_auth_set_parameter(conn->session->auth_baton,
323 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
326 realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s",
327 cert_info.fingerprint);
329 err = svn_auth_first_credentials(&creds, &state,
330 SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
332 conn->session->auth_baton,
335 svn_auth_set_parameter(conn->session->auth_baton,
336 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
338 svn_auth_set_parameter(conn->session->auth_baton,
339 SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
343 if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER)
344 return svn_error_trace(err);
346 /* No provider registered that handles server authorities */
347 svn_error_clear(err);
353 server_creds = creds;
354 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
356 svn_failures &= ~server_creds->accepted_failures;
360 conn->server_cert_failures |= svn_failures;
365 svn_auth_set_parameter(conn->session->auth_baton,
366 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
369 svn_auth_set_parameter(conn->session->auth_baton,
370 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
373 realmstring = construct_realm(conn->session, conn->session->pool);
375 SVN_ERR(svn_auth_first_credentials(&creds, &state,
376 SVN_AUTH_CRED_SSL_SERVER_TRUST,
378 conn->session->auth_baton,
382 server_creds = creds;
383 svn_failures &= ~server_creds->accepted_failures;
384 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
387 while (svn_failures && creds)
389 SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool));
393 server_creds = creds;
394 svn_failures &= ~server_creds->accepted_failures;
395 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
399 svn_auth_set_parameter(conn->session->auth_baton,
400 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
402 /* Are there non accepted failures left? */
405 svn_stringbuf_t *errmsg;
408 errmsg = svn_stringbuf_create(
409 _("Server SSL certificate verification failed"),
413 if (svn_failures & SVN_AUTH_SSL_NOTYETVALID)
414 append_reason(errmsg, _("certificate is not yet valid"), &reasons);
416 if (svn_failures & SVN_AUTH_SSL_EXPIRED)
417 append_reason(errmsg, _("certificate has expired"), &reasons);
419 if (svn_failures & SVN_AUTH_SSL_CNMISMATCH)
420 append_reason(errmsg,
421 _("certificate issued for a different hostname"),
424 if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA)
425 append_reason(errmsg, _("issuer is not trusted"), &reasons);
427 if (svn_failures & SVN_AUTH_SSL_OTHER)
428 append_reason(errmsg, _("and other reason(s)"), &reasons);
430 return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL,
437 /* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
439 ssl_server_cert_cb(void *baton, int failures,
440 const serf_ssl_certificate_t *cert)
442 svn_ra_serf__connection_t *conn = baton;
443 svn_ra_serf__session_t *session = conn->session;
447 subpool = svn_pool_create(session->pool);
448 err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
449 svn_pool_destroy(subpool);
451 return save_error(session, err);
455 load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
458 apr_array_header_t *files = svn_cstring_split(authorities, ";",
459 TRUE /* chop_whitespace */,
463 for (i = 0; i < files->nelts; ++i)
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);
469 if (status == APR_SUCCESS)
470 status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
472 if (status != APR_SUCCESS)
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));
483 #if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2)
484 /* Implements serf_ssl_protocol_result_cb_t */
486 conn_negotiate_protocol(void *data,
487 const char *protocol)
489 svn_ra_serf__connection_t *conn = data;
491 if (!strcmp(protocol, "h2"))
493 serf_connection_set_framing_type(
495 SERF_CONNECTION_FRAMING_TYPE_HTTP2);
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;
505 /* protocol should be "" or "http/1.1" */
506 serf_connection_set_framing_type(
508 SERF_CONNECTION_FRAMING_TYPE_HTTP1);
516 conn_setup(apr_socket_t *sock,
517 serf_bucket_t **read_bkt,
518 serf_bucket_t **write_bkt,
522 svn_ra_serf__connection_t *conn = baton;
524 *read_bkt = serf_context_bucket_socket_create(conn->session->context,
525 sock, conn->bkt_alloc);
527 if (conn->session->using_ssl)
530 *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
532 if (!conn->ssl_context)
534 conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
536 serf_ssl_set_hostname(conn->ssl_context,
537 conn->session->session_url.hostname);
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,
549 /* See if the user wants us to trust "default" openssl CAs. */
550 if (conn->session->trust_default_ca)
552 serf_ssl_use_default_certificates(conn->ssl_context);
554 /* Are there custom CAs to load? */
555 if (conn->session->ssl_authorities)
557 SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
558 conn->session->pool));
560 #if SERF_VERSION_AT_LEAST(1, 4, 0) && defined(SVN__SERF_TEST_HTTP2)
562 serf_ssl_negotiate_protocol(conn->ssl_context, "h2,http/1.1",
563 conn_negotiate_protocol, conn))
565 serf_connection_set_framing_type(
567 SERF_CONNECTION_FRAMING_TYPE_NONE);
575 *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
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
588 svn_ra_serf__conn_setup(apr_socket_t *sock,
589 serf_bucket_t **read_bkt,
590 serf_bucket_t **write_bkt,
594 svn_ra_serf__connection_t *conn = baton;
595 svn_ra_serf__session_t *session = conn->session;
598 err = svn_error_trace(conn_setup(sock,
603 return save_error(session, err);
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,
614 /* svn_ra_serf__handler_t *handler = acceptor_baton; */
616 serf_bucket_alloc_t *bkt_alloc;
618 bkt_alloc = serf_request_get_alloc(request);
619 c = serf_bucket_barrier_create(stream, bkt_alloc);
621 return serf_bucket_response_create(c, bkt_alloc);
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,
632 /* svn_ra_serf__handler_t *handler = acceptor_baton; */
633 serf_bucket_t *response;
635 response = accept_response(request, stream, acceptor_baton, pool);
637 /* We know we shouldn't get a response body. */
638 serf_bucket_response_set_head(response);
644 connection_closed(svn_ra_serf__connection_t *conn,
650 return svn_ra_serf__wrap_err(why, NULL);
653 if (conn->session->using_ssl)
654 conn->ssl_context = NULL;
660 svn_ra_serf__conn_closed(serf_connection_t *conn,
665 svn_ra_serf__connection_t *ra_conn = closed_baton;
668 err = svn_error_trace(connection_closed(ra_conn, why, pool));
670 (void) save_error(ra_conn->session, err);
674 /* Implementation of svn_ra_serf__handle_client_cert */
676 handle_client_cert(void *data,
677 const char **cert_path,
680 svn_ra_serf__connection_t *conn = data;
681 svn_ra_serf__session_t *session = conn->session;
687 realm = construct_realm(session, session->pool);
689 if (!conn->ssl_client_auth_state)
691 SVN_ERR(svn_auth_first_credentials(&creds,
692 &conn->ssl_client_auth_state,
693 SVN_AUTH_CRED_SSL_CLIENT_CERT,
700 SVN_ERR(svn_auth_next_credentials(&creds,
701 conn->ssl_client_auth_state,
707 svn_auth_cred_ssl_client_cert_t *client_creds;
708 client_creds = creds;
709 *cert_path = client_creds->cert_file;
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)
719 svn_ra_serf__connection_t *conn = data;
720 svn_ra_serf__session_t *session = conn->session;
723 err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
725 return save_error(session, err);
728 /* Implementation for svn_ra_serf__handle_client_cert_pw */
730 handle_client_cert_pw(void *data,
731 const char *cert_path,
732 const char **password,
735 svn_ra_serf__connection_t *conn = data;
736 svn_ra_serf__session_t *session = conn->session;
741 if (!conn->ssl_client_pw_auth_state)
743 SVN_ERR(svn_auth_first_credentials(&creds,
744 &conn->ssl_client_pw_auth_state,
745 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
752 SVN_ERR(svn_auth_next_credentials(&creds,
753 conn->ssl_client_pw_auth_state,
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;
764 *password = pw_creds->password;
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)
775 svn_ra_serf__connection_t *conn = data;
776 svn_ra_serf__session_t *session = conn->session;
779 err = svn_error_trace(handle_client_cert_pw(data,
784 return save_error(session, err);
789 * Given a REQUEST on connection CONN, construct a request bucket for it,
790 * returning the bucket in *REQ_BKT.
792 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
793 * corresponds to the new request.
795 * The request will be METHOD at URL.
797 * If BODY_BKT is not-NULL, it will be sent as the request body.
799 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
801 * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers
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.
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)
820 serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
823 svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
825 if (set_CL && body_bkt != NULL)
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.
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,
837 /* Destroy original bucket since it content is already copied
839 serf_bucket_destroy(body_bkt);
841 body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
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);
851 /* Set the Content-Length value. This will also trigger an HTTP/1.0
852 request (rather than the default chunked request). */
855 if (body_bkt == NULL)
856 serf_bucket_request_set_CL(*req_bkt, 0);
858 serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
861 *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
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);
870 serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
875 serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
880 serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
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.
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. */
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);
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)
908 assert(sess->pending_error == SVN_NO_ERROR);
910 if (sess->cancel_func)
911 SVN_ERR(sess->cancel_func(sess->cancel_baton));
913 status = serf_context_run(sess->context,
914 SVN_RA_SERF__CONTEXT_RUN_DURATION,
917 err = sess->pending_error;
918 sess->pending_error = SVN_NO_ERROR;
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))
930 if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
932 *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
937 svn_error_compose_create(
939 svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
940 _("Connection timed out")));
946 *waittime_left = sess->timeout;
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)
956 /* apr can't translate subversion errors to text */
957 SVN_ERR_W(svn_error_create(status, NULL, NULL),
958 _("Error running context"));
961 return svn_ra_serf__wrap_err(status, _("Error running context"));
968 svn_ra_serf__context_run_wait(svn_boolean_t *done,
969 svn_ra_serf__session_t *sess,
970 apr_pool_t *scratch_pool)
972 apr_pool_t *iterpool;
973 apr_interval_time_t waittime_left = sess->timeout;
975 assert(sess->pending_error == SVN_NO_ERROR);
977 iterpool = svn_pool_create(scratch_pool);
982 svn_pool_clear(iterpool);
984 SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
986 /* Debugging purposes only! */
987 for (i = 0; i < sess->num_conns; i++)
989 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
992 svn_pool_destroy(iterpool);
997 /* Ensure that a handler is no longer scheduled on the connection.
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.
1003 So the only thing we can do today is reset the connection, which
1004 will cancel all outstanding requests and prepare the connection
1008 svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler)
1010 serf_connection_reset(handler->conn->conn);
1011 handler->scheduled = FALSE;
1015 svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
1016 apr_pool_t *scratch_pool)
1020 /* Create a serf request based on HANDLER. */
1021 svn_ra_serf__request_create(handler);
1023 /* Wait until the response logic marks its DONE status. */
1024 err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
1027 if (handler->scheduled)
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);
1035 return svn_error_trace(err);
1042 drain_bucket(serf_bucket_t *bucket)
1044 /* Read whatever is in the bucket, and just drop it. */
1047 apr_status_t status;
1051 status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
1060 /* Implements svn_ra_serf__response_handler_t */
1062 svn_ra_serf__handle_discard_body(serf_request_t *request,
1063 serf_bucket_t *response,
1067 apr_status_t status;
1069 status = drain_bucket(response);
1071 return svn_ra_serf__wrap_err(status, NULL);
1073 return SVN_NO_ERROR;
1077 svn_ra_serf__response_discard_handler(serf_request_t *request,
1078 serf_bucket_t *response,
1082 return drain_bucket(response);
1086 /* Return the value of the RESPONSE's Location header if any, or NULL
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)
1094 serf_bucket_t *headers;
1095 const char *location;
1097 headers = serf_bucket_response_get_headers(response);
1098 location = serf_bucket_headers_get(headers, "Location");
1099 if (location == NULL)
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...
1106 And we can't trust anything because it is network data.
1108 if (*location == '/')
1111 apr_status_t status;
1113 status = apr_uri_parse(scratch_pool, base_url, &uri);
1115 if (status != APR_SUCCESS)
1118 /* Replace the path path with what we got */
1119 uri.path = apr_pstrdup(scratch_pool, location);
1121 /* And make APR produce a proper full url for us */
1122 return apr_uri_unparse(result_pool, &uri, 0);
1124 else if (!svn_path_is_url(location))
1126 return NULL; /* Any other formats we should support? */
1129 return apr_pstrdup(result_pool, location);
1133 /* Implements svn_ra_serf__response_handler_t */
1135 svn_ra_serf__expect_empty_body(serf_request_t *request,
1136 serf_bucket_t *response,
1138 apr_pool_t *scratch_pool)
1140 svn_ra_serf__handler_t *handler = baton;
1141 serf_bucket_t *hdrs;
1144 /* This function is just like handle_multistatus_only() except for the
1145 XML parsing callbacks. We want to look for the -readable element. */
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);
1153 hdrs = serf_bucket_response_get_headers(response);
1154 val = serf_bucket_headers_get(hdrs, "Content-Type");
1156 && (handler->sline.code < 200 || handler->sline.code >= 300)
1157 && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1159 svn_ra_serf__server_error_t *server_err;
1161 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1163 handler->handler_pool,
1164 handler->handler_pool));
1166 handler->server_error = server_err;
1170 /* The body was not text/xml, or we got a success code.
1171 Toss anything that arrives. */
1172 handler->discard_body = TRUE;
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;
1183 svn_ra_serf__credentials_callback(char **username, char **password,
1184 serf_request_t *request, void *baton,
1185 int code, const char *authn_type,
1189 svn_ra_serf__handler_t *handler = baton;
1190 svn_ra_serf__session_t *session = handler->session;
1192 svn_auth_cred_simple_t *simple_creds;
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)
1203 err = svn_auth_first_credentials(&creds,
1204 &session->auth_state,
1205 SVN_AUTH_CRED_SIMPLE,
1207 session->auth_baton,
1212 err = svn_auth_next_credentials(&creds,
1213 session->auth_state,
1219 (void) save_error(session, err);
1220 return err->apr_err;
1223 session->auth_attempts++;
1225 if (!creds || session->auth_attempts > 4)
1227 /* No more credentials. */
1228 (void) save_error(session,
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;
1236 simple_creds = creds;
1237 *username = apr_pstrdup(pool, simple_creds->username);
1238 *password = apr_pstrdup(pool, simple_creds->password);
1242 *username = apr_pstrdup(pool, session->proxy_username);
1243 *password = apr_pstrdup(pool, session->proxy_password);
1245 session->proxy_auth_attempts++;
1247 if (!session->proxy_username || session->proxy_auth_attempts > 4)
1249 /* No more credentials. */
1250 (void) save_error(session,
1252 SVN_ERR_AUTHN_FAILED, NULL,
1253 _("Proxy authentication failed")));
1254 return SVN_ERR_AUTHN_FAILED;
1258 handler->conn->last_status_code = code;
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.
1267 SERF_STATUS allows returning errors to serf without creating a
1268 subversion error object.
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)
1277 apr_status_t status;
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;
1287 /* Uh-oh. Our connection died. */
1288 handler->scheduled = FALSE;
1290 if (handler->response_error)
1292 /* Give a handler chance to prevent request requeue. */
1293 SVN_ERR(handler->response_error(request, response, 0,
1294 handler->response_error_baton));
1296 svn_ra_serf__request_create(handler);
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)
1303 svn_ra_serf__request_create(handler);
1307 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1308 _("%s request on '%s' failed"),
1309 handler->method, handler->path);
1312 return SVN_NO_ERROR;
1315 /* If we're reading the body, then skip all this preparation. */
1316 if (handler->reading_body)
1319 /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
1320 if (handler->sline.version == 0)
1322 serf_status_line sl;
1324 status = serf_bucket_response_status(response, &sl);
1325 if (status != APR_SUCCESS)
1327 /* The response line is not (yet) ready, or some other error. */
1328 *serf_status = status;
1329 return SVN_NO_ERROR; /* Handled by serf */
1332 /* If we got APR_SUCCESS, then we should have Status-Line info. */
1333 SVN_ERR_ASSERT(sl.version != 0);
1335 handler->sline = sl;
1336 handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
1338 /* HTTP/1.1? (or later) */
1339 if (sl.version != SERF_HTTP_10)
1340 handler->session->http10 = FALSE;
1342 if (sl.version >= SERF_HTTP_VERSION(2, 0)) {
1343 handler->session->http20 = TRUE;
1347 /* Keep reading from the network until we've read all the headers. */
1348 status = serf_bucket_response_wait_for_headers(response);
1351 /* The typical "error" will be APR_EAGAIN, meaning that more input
1352 from the network is required to complete the reading of the
1354 if (!APR_STATUS_IS_EOF(status))
1356 /* Either the headers are not (yet) complete, or there really
1358 *serf_status = status;
1359 return SVN_NO_ERROR;
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.
1367 It can also return EOF if we truly hit EOF while (say) processing
1368 the headers. aka Badness. */
1370 /* Cases where a lack of a response body (via EOF) is okay:
1372 * - 204/304 response
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,
1378 if (strcmp(handler->method, "HEAD") != 0
1379 && handler->sline.code != 204
1380 && handler->sline.code != 304)
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);
1388 /* In case anything else arrives... discard it. */
1389 handler->discard_body = TRUE;
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,
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)
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;
1410 handler->conn->last_status_code = handler->sline.code;
1412 if (handler->sline.code >= 400)
1414 /* 405 Method Not allowed.
1416 409 Conflict: can indicate a hook error.
1417 5xx (Internal) Server error. */
1418 serf_bucket_t *hdrs;
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)
1425 svn_ra_serf__server_error_t *server_err;
1427 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1429 handler->handler_pool,
1430 handler->handler_pool));
1432 handler->server_error = server_err;
1436 handler->discard_body = TRUE;
1439 else if (handler->sline.code <= 199)
1441 handler->discard_body = TRUE;
1444 /* Stop processing the above, on every packet arrival. */
1445 handler->reading_body = TRUE;
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)
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;
1457 if (handler->conn->ssl_client_auth_state)
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;
1466 /* We've been instructed to ignore the body. Drain whatever is present. */
1467 if (handler->discard_body)
1469 *serf_status = drain_bucket(response);
1471 return SVN_NO_ERROR;
1474 /* If we are supposed to parse the body as a server_error, then do
1476 if (handler->server_error != NULL)
1478 return svn_error_trace(
1479 svn_ra_serf__handle_server_error(handler->server_error,
1486 /* Pass the body along to the registered response handler. */
1487 err = handler->response_handler(request, response,
1488 handler->response_baton,
1492 && (!SERF_BUCKET_READ_ERROR(err->apr_err)
1493 || APR_STATUS_IS_ECONNRESET(err->apr_err)
1494 || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
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;
1503 return svn_error_trace(err);
1507 /* Implements serf_response_handler_t for handle_response. Storing
1508 errors in handler->session->pending_error if appropriate. */
1510 handle_response_cb(serf_request_t *request,
1511 serf_bucket_t *response,
1513 apr_pool_t *response_pool)
1515 svn_ra_serf__handler_t *handler = baton;
1517 apr_status_t inner_status;
1518 apr_status_t outer_status;
1519 apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */
1521 err = svn_error_trace(handle_response(request, response,
1522 handler, &inner_status,
1525 /* Select the right status value to return. */
1526 outer_status = save_error(handler->session, err);
1528 outer_status = inner_status;
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))
1533 svn_ra_serf__session_t *sess = handler->session;
1534 handler->done = TRUE;
1535 handler->scheduled = FALSE;
1536 outer_status = APR_EOF;
1538 /* We use a cached handler->session here to allow handler to free the
1539 memory containing the handler */
1541 handler->done_delegate(request, handler->done_delegate_baton,
1544 else if (SERF_BUCKET_READ_ERROR(outer_status)
1545 && handler->session->pending_error)
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
1556 outer_status = APR_EAGAIN; /* Exit context loop */
1559 return outer_status;
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)
1572 serf_bucket_t *body_bkt;
1573 serf_bucket_t *headers_bkt;
1574 const char *accept_encoding;
1576 if (handler->body_delegate)
1578 serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
1580 SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
1581 bkt_alloc, request_pool, scratch_pool));
1588 if (handler->custom_accept_encoding)
1590 accept_encoding = NULL;
1592 else if (handler->session->using_compression != svn_tristate_false)
1594 /* Accept gzip compression if enabled. */
1595 accept_encoding = "gzip";
1599 accept_encoding = NULL;
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,
1608 if (handler->header_delegate)
1610 SVN_ERR(handler->header_delegate(headers_bkt,
1611 handler->header_delegate_baton,
1612 request_pool, scratch_pool));
1615 return SVN_NO_ERROR;
1618 /* Implements the serf_request_setup_t interface (which sets up both a
1619 request and its response handler callback). Handles errors for
1622 setup_request_cb(serf_request_t *request,
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)
1631 svn_ra_serf__handler_t *handler = setup_baton;
1632 apr_pool_t *scratch_pool;
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);
1639 if (strcmp(handler->method, "HEAD") == 0)
1640 *acceptor = accept_head;
1642 *acceptor = accept_response;
1643 *acceptor_baton = handler;
1645 *s_handler = handle_response_cb;
1646 *s_handler_baton = handler;
1648 err = svn_error_trace(setup_request(request, handler, req_bkt,
1649 request_pool, scratch_pool));
1651 svn_pool_destroy(scratch_pool);
1652 return save_error(handler->session, err);
1656 svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
1658 SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL
1659 && !handler->scheduled);
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;
1670 /* Keeping track of the returned request object would be nice, but doesn't
1671 work the way we would expect in ra_serf..
1673 Serf sometimes creates a new request for us (and destroys the old one)
1674 without telling, like when authentication failed (401/407 response.
1676 We 'just' trust serf to do the right thing and expect it to tell us
1677 when the state of the request changes.
1679 ### I fixed a request leak in serf in r2258 on auth failures.
1681 (void) serf_connection_request_create(handler->conn->conn,
1682 setup_request_cb, handler);
1687 svn_ra_serf__discover_vcc(const char **vcc_url,
1688 svn_ra_serf__session_t *session,
1689 apr_pool_t *scratch_pool)
1692 const char *relative_path;
1695 /* If we've already got the information our caller seeks, just return it. */
1696 if (session->vcc_url && session->repos_root_str)
1698 *vcc_url = session->vcc_url;
1699 return SVN_NO_ERROR;
1702 path = session->session_url.path;
1711 err = svn_ra_serf__fetch_node_props(&props, session,
1712 path, SVN_INVALID_REVNUM,
1714 scratch_pool, scratch_pool);
1717 apr_hash_t *ns_props;
1719 ns_props = apr_hash_get(props, "DAV:", 4);
1720 *vcc_url = svn_prop_get_value(ns_props,
1721 "version-controlled-configuration");
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");
1731 if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
1732 (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
1734 return svn_error_trace(err); /* found a _real_ error */
1738 /* This happens when the file is missing in HEAD. */
1739 svn_error_clear(err);
1741 /* Okay, strip off a component from PATH. */
1742 path = svn_urlpath__dirname(path, scratch_pool);
1746 while ((path[0] != '\0')
1747 && (! (path[0] == '/' && path[1] == '\0')));
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 "
1757 /* Store our VCC in our cache. */
1758 if (!session->vcc_url)
1760 session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
1763 /* Update our cached repository root URL. */
1764 if (!session->repos_root_str)
1766 svn_stringbuf_t *url_buf;
1768 url_buf = svn_stringbuf_create(path, scratch_pool);
1770 svn_path_remove_components(url_buf,
1771 svn_path_component_count(relative_path));
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),
1783 /* Store the repository UUID in the cache. */
1786 session->uuid = apr_pstrdup(session->pool, uuid);
1789 return SVN_NO_ERROR;
1793 svn_ra_serf__get_relative_path(const char **rel_path,
1794 const char *orig_path,
1795 svn_ra_serf__session_t *session,
1798 const char *decoded_root, *decoded_orig;
1800 if (! session->repos_root.path)
1802 const char *vcc_url;
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));
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,
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;
1823 svn_ra_serf__report_resource(const char **report_target,
1824 svn_ra_serf__session_t *session,
1827 /* If we have HTTP v2 support, we want to report against the 'me'
1829 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
1830 *report_target = apr_pstrdup(pool, session->me_resource);
1832 /* Otherwise, we'll use the default VCC. */
1834 SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool));
1836 return SVN_NO_ERROR;
1840 svn_ra_serf__error_on_status(serf_status_line sline,
1842 const char *location)
1851 return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
1853 ? _("Repository moved permanently to '%s'")
1854 : _("Repository moved temporarily to '%s'"),
1857 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
1858 _("Access to '%s' forbidden"), path);
1861 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1862 _("'%s' path not found"), path);
1864 return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1865 _("HTTP method is not allowed on '%s'"),
1868 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1869 _("'%s' conflicts"), path);
1871 return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL,
1872 _("Precondition on '%s' failed"), path);
1874 return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
1875 _("'%s': no lock token available"), path);
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."));
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);
1888 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1889 _("The requested feature is not supported by "
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);
1898 return SVN_NO_ERROR;
1902 svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler)
1904 /* Is it a standard error status? */
1905 if (handler->sline.code != 405)
1906 SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
1908 handler->location));
1910 switch (handler->sline.code)
1913 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1914 _("Path '%s' unexpectedly created"),
1917 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1918 _("Path '%s' already exists"),
1922 return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1923 _("The HTTP method '%s' is not allowed"
1925 handler->method, handler->path);
1927 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1928 _("Unexpected HTTP status %d '%s' on '%s' "
1930 handler->sline.code, handler->sline.reason,
1931 handler->method, handler->path);
1936 svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
1937 svn_delta_shim_callbacks_t *callbacks)
1939 svn_ra_serf__session_t *session = ra_session->priv;
1941 session->shim_callbacks = callbacks;
1942 return SVN_NO_ERROR;
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)
1951 svn_ra_serf__handler_t *handler = handler_baton;
1953 assert(handler->done);
1955 if (handler->no_fail_on_http_failure_status)
1956 return SVN_NO_ERROR;
1958 if (handler->server_error)
1959 return svn_ra_serf__server_error_create(handler, scratch_pool);
1961 if (handler->sline.code >= 400 || handler->sline.code <= 199)
1963 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1966 if ((handler->sline.code >= 300 && handler->sline.code < 399)
1967 && !handler->no_fail_on_http_redirect_status)
1969 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1972 return SVN_NO_ERROR;
1975 /* Pool cleanup handler for request handlers.
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.
1982 This fallback kills the connection for this case, which will make serf
1983 unregister any outstanding requests on it. */
1985 handler_cleanup(void *baton)
1987 svn_ra_serf__handler_t *handler = baton;
1988 if (handler->scheduled)
1990 svn_ra_serf__unschedule_handler(handler);
1996 svn_ra_serf__handler_t *
1997 svn_ra_serf__create_handler(svn_ra_serf__session_t *session,
1998 apr_pool_t *result_pool)
2000 svn_ra_serf__handler_t *handler;
2002 handler = apr_pcalloc(result_pool, sizeof(*handler));
2003 handler->handler_pool = result_pool;
2005 apr_pool_cleanup_register(result_pool, handler, handler_cleanup,
2006 apr_pool_cleanup_null);
2008 handler->session = session;
2009 handler->conn = session->conns[0];
2011 /* Setup the default done handler, to handle server errors */
2012 handler->done_delegate_baton = handler;
2013 handler->done_delegate = response_done;
2019 svn_ra_serf__uri_parse(apr_uri_t *uri,
2020 const char *url_str,
2021 apr_pool_t *result_pool)
2023 apr_status_t status;
2025 status = apr_uri_parse(result_pool, url_str, uri);
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'"),
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')
2039 uri->path = apr_pstrdup(result_pool, "/");
2042 return SVN_NO_ERROR;
2046 svn_ra_serf__setup_svndiff_accept_encoding(serf_bucket_t *headers,
2047 svn_ra_serf__session_t *session)
2049 if (session->using_compression == svn_tristate_false)
2051 /* Don't advertise support for compressed svndiff formats if
2052 compression is disabled. */
2053 serf_bucket_headers_setn(
2054 headers, "Accept-Encoding", "svndiff");
2056 else if (session->using_compression == svn_tristate_unknown &&
2057 svn_ra_serf__is_low_latency_connection(session))
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");
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");
2081 svn_ra_serf__is_low_latency_connection(svn_ra_serf__session_t *session)
2083 return session->conn_latency >= 0 &&
2084 session->conn_latency < apr_time_from_msec(5);
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)
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));
2096 if (session->supports_deadprop_count != svn_tristate_false
2097 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
2099 if (dirent_fields & SVN_DIRENT_KIND)
2101 prop = apr_array_push(props);
2102 prop->xmlns = "DAV:";
2103 prop->name = "resourcetype";
2106 if (dirent_fields & SVN_DIRENT_SIZE)
2108 prop = apr_array_push(props);
2109 prop->xmlns = "DAV:";
2110 prop->name = "getcontentlength";
2113 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
2115 prop = apr_array_push(props);
2116 prop->xmlns = SVN_DAV_PROP_NS_DAV;
2117 prop->name = "deadprop-count";
2120 if (dirent_fields & SVN_DIRENT_CREATED_REV)
2122 svn_ra_serf__dav_props_t *p = apr_array_push(props);
2124 p->name = SVN_DAV__VERSION_NAME;
2127 if (dirent_fields & SVN_DIRENT_TIME)
2129 prop = apr_array_push(props);
2130 prop->xmlns = "DAV:";
2131 prop->name = SVN_DAV__CREATIONDATE;
2134 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
2136 prop = apr_array_push(props);
2137 prop->xmlns = "DAV:";
2138 prop->name = "creator-displayname";
2143 /* We found an old subversion server that can't handle
2144 the deadprop-count property in the way we expect.
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";