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 svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
761 *password = pw_creds->password;
767 /* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
768 apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
769 const char *cert_path,
770 const char **password)
772 svn_ra_serf__connection_t *conn = data;
773 svn_ra_serf__session_t *session = conn->session;
776 err = svn_error_trace(handle_client_cert_pw(data,
781 return save_error(session, err);
786 * Given a REQUEST on connection CONN, construct a request bucket for it,
787 * returning the bucket in *REQ_BKT.
789 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
790 * corresponds to the new request.
792 * The request will be METHOD at URL.
794 * If BODY_BKT is not-NULL, it will be sent as the request body.
796 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
798 * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers
801 * REQUEST_POOL should live for the duration of the request. Serf will
802 * construct this and provide it to the request_setup callback, so we
803 * should just use that one.
806 setup_serf_req(serf_request_t *request,
807 serf_bucket_t **req_bkt,
808 serf_bucket_t **hdrs_bkt,
809 svn_ra_serf__session_t *session,
810 const char *method, const char *url,
811 serf_bucket_t *body_bkt, const char *content_type,
812 const char *accept_encoding,
813 svn_boolean_t dav_headers,
814 apr_pool_t *request_pool,
815 apr_pool_t *scratch_pool)
817 serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
820 svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
822 if (set_CL && body_bkt != NULL)
824 /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
825 it speaks HTTP/1.1 (and thus, chunked requests), or because the
826 server actually responded as only supporting HTTP/1.0.
828 We'll take the existing body_bkt, spool it into a spillbuf, and
829 then wrap a bucket around that spillbuf. The spillbuf will give
830 us the Content-Length value. */
831 SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
834 /* Destroy original bucket since it content is already copied
836 serf_bucket_destroy(body_bkt);
838 body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
843 /* Create a request bucket. Note that this sucker is kind enough to
844 add a "Host" header for us. */
845 *req_bkt = serf_request_bucket_request_create(request, method, url,
846 body_bkt, allocator);
848 /* Set the Content-Length value. This will also trigger an HTTP/1.0
849 request (rather than the default chunked request). */
852 if (body_bkt == NULL)
853 serf_bucket_request_set_CL(*req_bkt, 0);
855 serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
858 *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
860 /* We use serf_bucket_headers_setn() because the USERAGENT has a
861 lifetime longer than this bucket. Thus, there is no need to copy
862 the header values. */
863 serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
867 serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
872 serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
877 serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
880 /* These headers need to be sent with every request that might need
881 capability processing (e.g. during commit, reports, etc.), see
882 issue #3255 ("mod_dav_svn does not pass client capabilities to
883 start-commit hooks") for why.
885 Some request types like GET/HEAD/PROPFIND are unaware of capability
886 handling; and in some cases the responses can even be cached by
887 proxies, so we don't have to send these hearders there. */
890 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
891 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
892 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
899 svn_ra_serf__context_run(svn_ra_serf__session_t *sess,
900 apr_interval_time_t *waittime_left,
901 apr_pool_t *scratch_pool)
905 assert(sess->pending_error == SVN_NO_ERROR);
907 if (sess->cancel_func)
908 SVN_ERR(sess->cancel_func(sess->cancel_baton));
910 status = serf_context_run(sess->context,
911 SVN_RA_SERF__CONTEXT_RUN_DURATION,
914 err = sess->pending_error;
915 sess->pending_error = SVN_NO_ERROR;
917 /* If the context duration timeout is up, we'll subtract that
918 duration from the total time alloted for such things. If
919 there's no time left, we fail with a message indicating that
920 the connection timed out. */
921 if (APR_STATUS_IS_TIMEUP(status))
927 if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
929 *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
934 svn_error_compose_create(
936 svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
937 _("Connection timed out")));
943 *waittime_left = sess->timeout;
949 /* ### This omits SVN_WARNING, and possibly relies on the fact that
950 ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */
951 if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
953 /* apr can't translate subversion errors to text */
954 SVN_ERR_W(svn_error_create(status, NULL, NULL),
955 _("Error running context"));
958 return svn_ra_serf__wrap_err(status, _("Error running context"));
965 svn_ra_serf__context_run_wait(svn_boolean_t *done,
966 svn_ra_serf__session_t *sess,
967 apr_pool_t *scratch_pool)
969 apr_pool_t *iterpool;
970 apr_interval_time_t waittime_left = sess->timeout;
972 assert(sess->pending_error == SVN_NO_ERROR);
974 iterpool = svn_pool_create(scratch_pool);
979 svn_pool_clear(iterpool);
981 SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
983 /* Debugging purposes only! */
984 for (i = 0; i < sess->num_conns; i++)
986 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
989 svn_pool_destroy(iterpool);
994 /* Ensure that a handler is no longer scheduled on the connection.
996 Eventually serf will have a reliable way to cancel existing requests,
997 but currently it doesn't even have a way to relyable identify a request
998 after rescheduling, for auth reasons.
1000 So the only thing we can do today is reset the connection, which
1001 will cancel all outstanding requests and prepare the connection
1005 svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler)
1007 serf_connection_reset(handler->conn->conn);
1008 handler->scheduled = FALSE;
1012 svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
1013 apr_pool_t *scratch_pool)
1017 /* Create a serf request based on HANDLER. */
1018 svn_ra_serf__request_create(handler);
1020 /* Wait until the response logic marks its DONE status. */
1021 err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
1024 if (handler->scheduled)
1026 /* We reset the connection (breaking pipelining, etc.), as
1027 if we didn't the next data would still be handled by this handler,
1028 which is done as far as our caller is concerned. */
1029 svn_ra_serf__unschedule_handler(handler);
1032 return svn_error_trace(err);
1039 drain_bucket(serf_bucket_t *bucket)
1041 /* Read whatever is in the bucket, and just drop it. */
1044 apr_status_t status;
1048 status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
1057 /* Implements svn_ra_serf__response_handler_t */
1059 svn_ra_serf__handle_discard_body(serf_request_t *request,
1060 serf_bucket_t *response,
1064 apr_status_t status;
1066 status = drain_bucket(response);
1068 return svn_ra_serf__wrap_err(status, NULL);
1070 return SVN_NO_ERROR;
1074 svn_ra_serf__response_discard_handler(serf_request_t *request,
1075 serf_bucket_t *response,
1079 return drain_bucket(response);
1083 /* Return the value of the RESPONSE's Location header if any, or NULL
1086 response_get_location(serf_bucket_t *response,
1087 const char *base_url,
1088 apr_pool_t *result_pool,
1089 apr_pool_t *scratch_pool)
1091 serf_bucket_t *headers;
1092 const char *location;
1094 headers = serf_bucket_response_get_headers(response);
1095 location = serf_bucket_headers_get(headers, "Location");
1096 if (location == NULL)
1099 /* The RFCs say we should have received a full url in LOCATION, but
1100 older apache versions and many custom web handlers just return a
1101 relative path here...
1103 And we can't trust anything because it is network data.
1105 if (*location == '/')
1108 apr_status_t status;
1110 status = apr_uri_parse(scratch_pool, base_url, &uri);
1112 if (status != APR_SUCCESS)
1115 /* Replace the path path with what we got */
1116 uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
1118 /* And make APR produce a proper full url for us */
1119 location = apr_uri_unparse(scratch_pool, &uri, 0);
1121 /* Fall through to ensure our canonicalization rules */
1123 else if (!svn_path_is_url(location))
1125 return NULL; /* Any other formats we should support? */
1128 return svn_uri_canonicalize(location, result_pool);
1132 /* Implements svn_ra_serf__response_handler_t */
1134 svn_ra_serf__expect_empty_body(serf_request_t *request,
1135 serf_bucket_t *response,
1137 apr_pool_t *scratch_pool)
1139 svn_ra_serf__handler_t *handler = baton;
1140 serf_bucket_t *hdrs;
1143 /* This function is just like handle_multistatus_only() except for the
1144 XML parsing callbacks. We want to look for the -readable element. */
1146 /* We should see this just once, in order to initialize SERVER_ERROR.
1147 At that point, the core error processing will take over. If we choose
1148 not to parse an error, then we'll never return here (because we
1149 change the response handler). */
1150 SVN_ERR_ASSERT(handler->server_error == NULL);
1152 hdrs = serf_bucket_response_get_headers(response);
1153 val = serf_bucket_headers_get(hdrs, "Content-Type");
1155 && (handler->sline.code < 200 || handler->sline.code >= 300)
1156 && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1158 svn_ra_serf__server_error_t *server_err;
1160 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1162 handler->handler_pool,
1163 handler->handler_pool));
1165 handler->server_error = server_err;
1169 /* The body was not text/xml, or we got a success code.
1170 Toss anything that arrives. */
1171 handler->discard_body = TRUE;
1174 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1175 to call the response handler again. That will start up the XML parsing,
1176 or it will be dropped on the floor (per the decision above). */
1177 return SVN_NO_ERROR;
1182 svn_ra_serf__credentials_callback(char **username, char **password,
1183 serf_request_t *request, void *baton,
1184 int code, const char *authn_type,
1188 svn_ra_serf__handler_t *handler = baton;
1189 svn_ra_serf__session_t *session = handler->session;
1191 svn_auth_cred_simple_t *simple_creds;
1196 /* Use svn_auth_first_credentials if this is the first time we ask for
1197 credentials during this session OR if the last time we asked
1198 session->auth_state wasn't set (eg. if the credentials provider was
1199 cancelled by the user). */
1200 if (!session->auth_state)
1202 err = svn_auth_first_credentials(&creds,
1203 &session->auth_state,
1204 SVN_AUTH_CRED_SIMPLE,
1206 session->auth_baton,
1211 err = svn_auth_next_credentials(&creds,
1212 session->auth_state,
1218 (void) save_error(session, err);
1219 return err->apr_err;
1222 session->auth_attempts++;
1224 if (!creds || session->auth_attempts > 4)
1226 /* No more credentials. */
1227 (void) save_error(session,
1229 SVN_ERR_AUTHN_FAILED, NULL,
1230 _("No more credentials or we tried too many "
1231 "times.\nAuthentication failed")));
1232 return SVN_ERR_AUTHN_FAILED;
1235 simple_creds = creds;
1236 *username = apr_pstrdup(pool, simple_creds->username);
1237 *password = apr_pstrdup(pool, simple_creds->password);
1241 *username = apr_pstrdup(pool, session->proxy_username);
1242 *password = apr_pstrdup(pool, session->proxy_password);
1244 session->proxy_auth_attempts++;
1246 if (!session->proxy_username || session->proxy_auth_attempts > 4)
1248 /* No more credentials. */
1249 (void) save_error(session,
1251 SVN_ERR_AUTHN_FAILED, NULL,
1252 _("Proxy authentication failed")));
1253 return SVN_ERR_AUTHN_FAILED;
1257 handler->conn->last_status_code = code;
1262 /* Wait for HTTP response status and headers, and invoke HANDLER->
1263 response_handler() to carry out operation-specific processing.
1264 Afterwards, check for connection close.
1266 SERF_STATUS allows returning errors to serf without creating a
1267 subversion error object.
1269 static svn_error_t *
1270 handle_response(serf_request_t *request,
1271 serf_bucket_t *response,
1272 svn_ra_serf__handler_t *handler,
1273 apr_status_t *serf_status,
1274 apr_pool_t *scratch_pool)
1276 apr_status_t status;
1279 /* ### need to verify whether this already gets init'd on every
1280 ### successful exit. for an error-exit, it will (properly) be
1281 ### ignored by the caller. */
1282 *serf_status = APR_SUCCESS;
1286 /* Uh-oh. Our connection died. */
1287 handler->scheduled = FALSE;
1289 if (handler->response_error)
1291 /* Give a handler chance to prevent request requeue. */
1292 SVN_ERR(handler->response_error(request, response, 0,
1293 handler->response_error_baton));
1295 svn_ra_serf__request_create(handler);
1297 /* Response error callback is not configured. Requeue another request
1298 for this handler only if we didn't started to process body.
1299 Return error otherwise. */
1300 else if (!handler->reading_body)
1302 svn_ra_serf__request_create(handler);
1306 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1307 _("%s request on '%s' failed"),
1308 handler->method, handler->path);
1311 return SVN_NO_ERROR;
1314 /* If we're reading the body, then skip all this preparation. */
1315 if (handler->reading_body)
1318 /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
1319 if (handler->sline.version == 0)
1321 serf_status_line sl;
1323 status = serf_bucket_response_status(response, &sl);
1324 if (status != APR_SUCCESS)
1326 /* The response line is not (yet) ready, or some other error. */
1327 *serf_status = status;
1328 return SVN_NO_ERROR; /* Handled by serf */
1331 /* If we got APR_SUCCESS, then we should have Status-Line info. */
1332 SVN_ERR_ASSERT(sl.version != 0);
1334 handler->sline = sl;
1335 handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
1337 /* HTTP/1.1? (or later) */
1338 if (sl.version != SERF_HTTP_10)
1339 handler->session->http10 = FALSE;
1341 if (sl.version >= SERF_HTTP_VERSION(2, 0)) {
1342 handler->session->http20 = TRUE;
1346 /* Keep reading from the network until we've read all the headers. */
1347 status = serf_bucket_response_wait_for_headers(response);
1350 /* The typical "error" will be APR_EAGAIN, meaning that more input
1351 from the network is required to complete the reading of the
1353 if (!APR_STATUS_IS_EOF(status))
1355 /* Either the headers are not (yet) complete, or there really
1357 *serf_status = status;
1358 return SVN_NO_ERROR;
1361 /* wait_for_headers() will return EOF if there is no body in this
1362 response, or if we completely read the body. The latter is not
1363 true since we would have set READING_BODY to get the body read,
1364 and we would not be back to this code block.
1366 It can also return EOF if we truly hit EOF while (say) processing
1367 the headers. aka Badness. */
1369 /* Cases where a lack of a response body (via EOF) is okay:
1371 * - 204/304 response
1373 * Otherwise, if we get an EOF here, something went really wrong: either
1374 * the server closed on us early or we're reading too much. Either way,
1377 if (strcmp(handler->method, "HEAD") != 0
1378 && handler->sline.code != 204
1379 && handler->sline.code != 304)
1381 err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
1382 svn_ra_serf__wrap_err(status, NULL),
1383 _("Premature EOF seen from server"
1384 " (http status=%d)"),
1385 handler->sline.code);
1387 /* In case anything else arrives... discard it. */
1388 handler->discard_body = TRUE;
1394 /* ... and set up the header fields in HANDLER. */
1395 handler->location = response_get_location(response,
1396 handler->session->session_url_str,
1397 handler->handler_pool,
1400 /* On the last request, we failed authentication. We succeeded this time,
1401 so let's save away these credentials. */
1402 if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
1404 SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
1405 handler->session->pool));
1406 handler->session->auth_attempts = 0;
1407 handler->session->auth_state = NULL;
1409 handler->conn->last_status_code = handler->sline.code;
1411 if (handler->sline.code >= 400)
1413 /* 405 Method Not allowed.
1415 409 Conflict: can indicate a hook error.
1416 5xx (Internal) Server error. */
1417 serf_bucket_t *hdrs;
1420 hdrs = serf_bucket_response_get_headers(response);
1421 val = serf_bucket_headers_get(hdrs, "Content-Type");
1422 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1424 svn_ra_serf__server_error_t *server_err;
1426 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1428 handler->handler_pool,
1429 handler->handler_pool));
1431 handler->server_error = server_err;
1435 handler->discard_body = TRUE;
1438 else if (handler->sline.code <= 199)
1440 handler->discard_body = TRUE;
1443 /* Stop processing the above, on every packet arrival. */
1444 handler->reading_body = TRUE;
1448 /* We've been instructed to ignore the body. Drain whatever is present. */
1449 if (handler->discard_body)
1451 *serf_status = drain_bucket(response);
1453 return SVN_NO_ERROR;
1456 /* If we are supposed to parse the body as a server_error, then do
1458 if (handler->server_error != NULL)
1460 return svn_error_trace(
1461 svn_ra_serf__handle_server_error(handler->server_error,
1468 /* Pass the body along to the registered response handler. */
1469 err = handler->response_handler(request, response,
1470 handler->response_baton,
1474 && (!SERF_BUCKET_READ_ERROR(err->apr_err)
1475 || APR_STATUS_IS_ECONNRESET(err->apr_err)
1476 || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
1478 /* These errors are special cased in serf
1479 ### We hope no handler returns these by accident. */
1480 *serf_status = err->apr_err;
1481 svn_error_clear(err);
1482 return SVN_NO_ERROR;
1485 return svn_error_trace(err);
1489 /* Implements serf_response_handler_t for handle_response. Storing
1490 errors in handler->session->pending_error if appropriate. */
1492 handle_response_cb(serf_request_t *request,
1493 serf_bucket_t *response,
1495 apr_pool_t *response_pool)
1497 svn_ra_serf__handler_t *handler = baton;
1499 apr_status_t inner_status;
1500 apr_status_t outer_status;
1501 apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */
1503 err = svn_error_trace(handle_response(request, response,
1504 handler, &inner_status,
1507 /* Select the right status value to return. */
1508 outer_status = save_error(handler->session, err);
1510 outer_status = inner_status;
1512 /* Make sure the DONE flag is set properly and requests are cleaned up. */
1513 if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
1515 svn_ra_serf__session_t *sess = handler->session;
1516 handler->done = TRUE;
1517 handler->scheduled = FALSE;
1518 outer_status = APR_EOF;
1520 /* We use a cached handler->session here to allow handler to free the
1521 memory containing the handler */
1523 handler->done_delegate(request, handler->done_delegate_baton,
1526 else if (SERF_BUCKET_READ_ERROR(outer_status)
1527 && handler->session->pending_error)
1529 handler->discard_body = TRUE; /* Discard further data */
1530 handler->done = TRUE; /* Mark as done */
1531 /* handler->scheduled is still TRUE, as we still expect data.
1532 If we would return an error outer-status the connection
1533 would have to be restarted. With scheduled still TRUE
1534 destroying the handler's pool will still reset the
1535 connection, avoiding the posibility of returning
1536 an error for this handler when a new request is
1538 outer_status = APR_EAGAIN; /* Exit context loop */
1541 return outer_status;
1544 /* Perform basic request setup, with special handling for HEAD requests,
1545 and finer-grained callbacks invoked (if non-NULL) to produce the request
1546 headers and body. */
1547 static svn_error_t *
1548 setup_request(serf_request_t *request,
1549 svn_ra_serf__handler_t *handler,
1550 serf_bucket_t **req_bkt,
1551 apr_pool_t *request_pool,
1552 apr_pool_t *scratch_pool)
1554 serf_bucket_t *body_bkt;
1555 serf_bucket_t *headers_bkt;
1556 const char *accept_encoding;
1558 if (handler->body_delegate)
1560 serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
1562 SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
1563 bkt_alloc, request_pool, scratch_pool));
1570 if (handler->custom_accept_encoding)
1572 accept_encoding = NULL;
1574 else if (handler->session->using_compression != svn_tristate_false)
1576 /* Accept gzip compression if enabled. */
1577 accept_encoding = "gzip";
1581 accept_encoding = NULL;
1584 SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
1585 handler->session, handler->method, handler->path,
1586 body_bkt, handler->body_type, accept_encoding,
1587 !handler->no_dav_headers, request_pool,
1590 if (handler->header_delegate)
1592 SVN_ERR(handler->header_delegate(headers_bkt,
1593 handler->header_delegate_baton,
1594 request_pool, scratch_pool));
1597 return SVN_NO_ERROR;
1600 /* Implements the serf_request_setup_t interface (which sets up both a
1601 request and its response handler callback). Handles errors for
1604 setup_request_cb(serf_request_t *request,
1606 serf_bucket_t **req_bkt,
1607 serf_response_acceptor_t *acceptor,
1608 void **acceptor_baton,
1609 serf_response_handler_t *s_handler,
1610 void **s_handler_baton,
1611 apr_pool_t *request_pool)
1613 svn_ra_serf__handler_t *handler = setup_baton;
1614 apr_pool_t *scratch_pool;
1617 /* Construct a scratch_pool? serf gives us a pool that will live for
1618 the duration of the request. But requests are retried in some cases */
1619 scratch_pool = svn_pool_create(request_pool);
1621 if (strcmp(handler->method, "HEAD") == 0)
1622 *acceptor = accept_head;
1624 *acceptor = accept_response;
1625 *acceptor_baton = handler;
1627 *s_handler = handle_response_cb;
1628 *s_handler_baton = handler;
1630 err = svn_error_trace(setup_request(request, handler, req_bkt,
1631 request_pool, scratch_pool));
1633 svn_pool_destroy(scratch_pool);
1634 return save_error(handler->session, err);
1638 svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
1640 SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL
1641 && !handler->scheduled);
1643 /* In case HANDLER is re-queued, reset the various transient fields. */
1644 handler->done = FALSE;
1645 handler->server_error = NULL;
1646 handler->sline.version = 0;
1647 handler->location = NULL;
1648 handler->reading_body = FALSE;
1649 handler->discard_body = FALSE;
1650 handler->scheduled = TRUE;
1652 /* Keeping track of the returned request object would be nice, but doesn't
1653 work the way we would expect in ra_serf..
1655 Serf sometimes creates a new request for us (and destroys the old one)
1656 without telling, like when authentication failed (401/407 response.
1658 We 'just' trust serf to do the right thing and expect it to tell us
1659 when the state of the request changes.
1661 ### I fixed a request leak in serf in r2258 on auth failures.
1663 (void) serf_connection_request_create(handler->conn->conn,
1664 setup_request_cb, handler);
1669 svn_ra_serf__discover_vcc(const char **vcc_url,
1670 svn_ra_serf__session_t *session,
1671 apr_pool_t *scratch_pool)
1674 const char *relative_path;
1677 /* If we've already got the information our caller seeks, just return it. */
1678 if (session->vcc_url && session->repos_root_str)
1680 *vcc_url = session->vcc_url;
1681 return SVN_NO_ERROR;
1684 path = session->session_url.path;
1693 err = svn_ra_serf__fetch_node_props(&props, session,
1694 path, SVN_INVALID_REVNUM,
1696 scratch_pool, scratch_pool);
1699 apr_hash_t *ns_props;
1701 ns_props = apr_hash_get(props, "DAV:", 4);
1702 *vcc_url = svn_prop_get_value(ns_props,
1703 "version-controlled-configuration");
1705 ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
1706 relative_path = svn_prop_get_value(ns_props,
1707 "baseline-relative-path");
1708 uuid = svn_prop_get_value(ns_props, "repository-uuid");
1713 if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
1714 (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
1716 return svn_error_trace(err); /* found a _real_ error */
1720 /* This happens when the file is missing in HEAD. */
1721 svn_error_clear(err);
1723 /* Okay, strip off a component from PATH. */
1724 path = svn_urlpath__dirname(path, scratch_pool);
1728 while ((path[0] != '\0')
1729 && (! (path[0] == '/' && path[1] == '\0')));
1733 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1734 _("The PROPFIND response did not include the "
1735 "requested version-controlled-configuration "
1739 /* Store our VCC in our cache. */
1740 if (!session->vcc_url)
1742 session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
1745 /* Update our cached repository root URL. */
1746 if (!session->repos_root_str)
1748 svn_stringbuf_t *url_buf;
1750 url_buf = svn_stringbuf_create(path, scratch_pool);
1752 svn_path_remove_components(url_buf,
1753 svn_path_component_count(relative_path));
1755 /* Now recreate the root_url. */
1756 session->repos_root = session->session_url;
1757 session->repos_root.path =
1758 (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
1759 session->repos_root_str =
1760 svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
1761 &session->repos_root, 0),
1765 /* Store the repository UUID in the cache. */
1768 session->uuid = apr_pstrdup(session->pool, uuid);
1771 return SVN_NO_ERROR;
1775 svn_ra_serf__get_relative_path(const char **rel_path,
1776 const char *orig_path,
1777 svn_ra_serf__session_t *session,
1780 const char *decoded_root, *decoded_orig;
1782 if (! session->repos_root.path)
1784 const char *vcc_url;
1786 /* This should only happen if we haven't detected HTTP v2
1787 support from the server. */
1788 assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1790 /* We don't actually care about the VCC_URL, but this API
1791 promises to populate the session's root-url cache, and that's
1792 what we really want. */
1793 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
1797 decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
1798 decoded_orig = svn_path_uri_decode(orig_path, pool);
1799 *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
1800 SVN_ERR_ASSERT(*rel_path != NULL);
1801 return SVN_NO_ERROR;
1805 svn_ra_serf__report_resource(const char **report_target,
1806 svn_ra_serf__session_t *session,
1809 /* If we have HTTP v2 support, we want to report against the 'me'
1811 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
1812 *report_target = apr_pstrdup(pool, session->me_resource);
1814 /* Otherwise, we'll use the default VCC. */
1816 SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool));
1818 return SVN_NO_ERROR;
1822 svn_ra_serf__error_on_status(serf_status_line sline,
1824 const char *location)
1833 return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
1835 ? _("Repository moved permanently to '%s'")
1836 : _("Repository moved temporarily to '%s'"),
1839 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
1840 _("Access to '%s' forbidden"), path);
1843 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1844 _("'%s' path not found"), path);
1846 return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1847 _("HTTP method is not allowed on '%s'"),
1850 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1851 _("'%s' conflicts"), path);
1853 return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL,
1854 _("Precondition on '%s' failed"), path);
1856 return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
1857 _("'%s': no lock token available"), path);
1860 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1861 _("DAV request failed: 411 Content length required. The "
1862 "server or an intermediate proxy does not accept "
1863 "chunked encoding. Try setting 'http-chunked-requests' "
1864 "to 'auto' or 'no' in your client configuration."));
1866 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1867 _("Unexpected server error %d '%s' on '%s'"),
1868 sline.code, sline.reason, path);
1870 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1871 _("The requested feature is not supported by "
1875 if (sline.code >= 300 || sline.code <= 199)
1876 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1877 _("Unexpected HTTP status %d '%s' on '%s'"),
1878 sline.code, sline.reason, path);
1880 return SVN_NO_ERROR;
1884 svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler)
1886 /* Is it a standard error status? */
1887 if (handler->sline.code != 405)
1888 SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
1890 handler->location));
1892 switch (handler->sline.code)
1895 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1896 _("Path '%s' unexpectedly created"),
1899 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1900 _("Path '%s' already exists"),
1904 return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1905 _("The HTTP method '%s' is not allowed"
1907 handler->method, handler->path);
1909 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1910 _("Unexpected HTTP status %d '%s' on '%s' "
1912 handler->sline.code, handler->sline.reason,
1913 handler->method, handler->path);
1918 svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
1919 svn_delta_shim_callbacks_t *callbacks)
1921 svn_ra_serf__session_t *session = ra_session->priv;
1923 session->shim_callbacks = callbacks;
1924 return SVN_NO_ERROR;
1927 /* Shared/standard done_delegate handler */
1928 static svn_error_t *
1929 response_done(serf_request_t *request,
1930 void *handler_baton,
1931 apr_pool_t *scratch_pool)
1933 svn_ra_serf__handler_t *handler = handler_baton;
1935 assert(handler->done);
1937 if (handler->no_fail_on_http_failure_status)
1938 return SVN_NO_ERROR;
1940 if (handler->server_error)
1941 return svn_ra_serf__server_error_create(handler, scratch_pool);
1943 if (handler->sline.code >= 400 || handler->sline.code <= 199)
1945 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1948 if ((handler->sline.code >= 300 && handler->sline.code < 399)
1949 && !handler->no_fail_on_http_redirect_status)
1951 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1954 return SVN_NO_ERROR;
1957 /* Pool cleanup handler for request handlers.
1959 If a serf context run stops for some outside error, like when the user
1960 cancels a request via ^C in the context loop, the handler is still
1961 registered in the serf context. With the pool cleanup there would be
1962 handlers registered in no freed memory.
1964 This fallback kills the connection for this case, which will make serf
1965 unregister any outstanding requests on it. */
1967 handler_cleanup(void *baton)
1969 svn_ra_serf__handler_t *handler = baton;
1970 if (handler->scheduled)
1972 svn_ra_serf__unschedule_handler(handler);
1978 svn_ra_serf__handler_t *
1979 svn_ra_serf__create_handler(svn_ra_serf__session_t *session,
1980 apr_pool_t *result_pool)
1982 svn_ra_serf__handler_t *handler;
1984 handler = apr_pcalloc(result_pool, sizeof(*handler));
1985 handler->handler_pool = result_pool;
1987 apr_pool_cleanup_register(result_pool, handler, handler_cleanup,
1988 apr_pool_cleanup_null);
1990 handler->session = session;
1991 handler->conn = session->conns[0];
1993 /* Setup the default done handler, to handle server errors */
1994 handler->done_delegate_baton = handler;
1995 handler->done_delegate = response_done;
2001 svn_ra_serf__uri_parse(apr_uri_t *uri,
2002 const char *url_str,
2003 apr_pool_t *result_pool)
2005 apr_status_t status;
2007 status = apr_uri_parse(result_pool, url_str, uri);
2010 /* Do not use returned error status in error message because currently
2011 apr_uri_parse() returns APR_EGENERAL for all parsing errors. */
2012 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
2013 _("Illegal URL '%s'"),
2017 /* Depending the version of apr-util in use, for root paths uri.path
2018 will be NULL or "", where serf requires "/". */
2019 if (uri->path == NULL || uri->path[0] == '\0')
2021 uri->path = apr_pstrdup(result_pool, "/");
2024 return SVN_NO_ERROR;
2028 svn_ra_serf__setup_svndiff_accept_encoding(serf_bucket_t *headers,
2029 svn_ra_serf__session_t *session)
2031 if (session->using_compression == svn_tristate_false)
2033 /* Don't advertise support for compressed svndiff formats if
2034 compression is disabled. */
2035 serf_bucket_headers_setn(
2036 headers, "Accept-Encoding", "svndiff");
2038 else if (session->using_compression == svn_tristate_unknown &&
2039 svn_ra_serf__is_low_latency_connection(session))
2041 /* With http-compression=auto, advertise that we prefer svndiff2
2042 to svndiff1 with a low latency connection (assuming the underlying
2043 network has high bandwidth), as it is faster and in this case, we
2044 don't care about worse compression ratio. */
2045 serf_bucket_headers_setn(
2046 headers, "Accept-Encoding",
2047 "gzip,svndiff2;q=0.9,svndiff1;q=0.8,svndiff;q=0.7");
2051 /* Otherwise, advertise that we prefer svndiff1 over svndiff2.
2052 svndiff2 is not a reasonable substitute for svndiff1 with default
2053 compression level, because, while it is faster, it also gives worse
2054 compression ratio. While we can use svndiff2 in some cases (see
2055 above), we can't do this generally. */
2056 serf_bucket_headers_setn(
2057 headers, "Accept-Encoding",
2058 "gzip,svndiff1;q=0.9,svndiff2;q=0.8,svndiff;q=0.7");
2063 svn_ra_serf__is_low_latency_connection(svn_ra_serf__session_t *session)
2065 return session->conn_latency >= 0 &&
2066 session->conn_latency < apr_time_from_msec(5);
2069 apr_array_header_t *
2070 svn_ra_serf__get_dirent_props(apr_uint32_t dirent_fields,
2071 svn_ra_serf__session_t *session,
2072 apr_pool_t *result_pool)
2074 svn_ra_serf__dav_props_t *prop;
2075 apr_array_header_t *props = apr_array_make
2076 (result_pool, 7, sizeof(svn_ra_serf__dav_props_t));
2078 if (session->supports_deadprop_count != svn_tristate_false
2079 || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
2081 if (dirent_fields & SVN_DIRENT_KIND)
2083 prop = apr_array_push(props);
2084 prop->xmlns = "DAV:";
2085 prop->name = "resourcetype";
2088 if (dirent_fields & SVN_DIRENT_SIZE)
2090 prop = apr_array_push(props);
2091 prop->xmlns = "DAV:";
2092 prop->name = "getcontentlength";
2095 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
2097 prop = apr_array_push(props);
2098 prop->xmlns = SVN_DAV_PROP_NS_DAV;
2099 prop->name = "deadprop-count";
2102 if (dirent_fields & SVN_DIRENT_CREATED_REV)
2104 svn_ra_serf__dav_props_t *p = apr_array_push(props);
2106 p->name = SVN_DAV__VERSION_NAME;
2109 if (dirent_fields & SVN_DIRENT_TIME)
2111 prop = apr_array_push(props);
2112 prop->xmlns = "DAV:";
2113 prop->name = SVN_DAV__CREATIONDATE;
2116 if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
2118 prop = apr_array_push(props);
2119 prop->xmlns = "DAV:";
2120 prop->name = "creator-displayname";
2125 /* We found an old subversion server that can't handle
2126 the deadprop-count property in the way we expect.
2128 The neon behavior is to retrieve all properties in this case */
2129 prop = apr_array_push(props);
2130 prop->xmlns = "DAV:";
2131 prop->name = "allprop";