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;
68 for (i = 0; i < sizeof(serf_failure_map) / (2 * sizeof(apr_uint32_t)); ++i)
70 if (failures & serf_failure_map[i][0])
72 svn_failures |= serf_failure_map[i][1];
73 failures &= ~serf_failure_map[i][0];
77 /* Map any remaining failure bits to our OTHER bit. */
80 svn_failures |= SVN_AUTH_SSL_OTHER;
88 save_error(svn_ra_serf__session_t *session,
91 if (err || session->pending_error)
93 session->pending_error = svn_error_compose_create(
94 session->pending_error,
96 return session->pending_error->apr_err;
103 /* Construct the realmstring, e.g. https://svn.collab.net:443. */
105 construct_realm(svn_ra_serf__session_t *session,
111 if (session->session_url.port_str)
113 port = session->session_url.port;
117 port = apr_uri_port_of_scheme(session->session_url.scheme);
120 realm = apr_psprintf(pool, "%s://%s:%d",
121 session->session_url.scheme,
122 session->session_url.hostname,
128 /* Convert a hash table containing the fields (as documented in X.509) of an
129 organisation to a string ORG, allocated in POOL. ORG is as returned by
130 serf_ssl_cert_issuer() and serf_ssl_cert_subject(). */
132 convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool)
134 const char *cn = svn_hash_gets(org, "CN");
135 const char *org_unit = svn_hash_gets(org, "OU");
136 const char *org_name = svn_hash_gets(org, "O");
137 const char *locality = svn_hash_gets(org, "L");
138 const char *state = svn_hash_gets(org, "ST");
139 const char *country = svn_hash_gets(org, "C");
140 const char *email = svn_hash_gets(org, "E");
141 svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool);
145 svn_stringbuf_appendcstr(buf, cn);
146 svn_stringbuf_appendcstr(buf, ", ");
151 svn_stringbuf_appendcstr(buf, org_unit);
152 svn_stringbuf_appendcstr(buf, ", ");
157 svn_stringbuf_appendcstr(buf, org_name);
158 svn_stringbuf_appendcstr(buf, ", ");
163 svn_stringbuf_appendcstr(buf, locality);
164 svn_stringbuf_appendcstr(buf, ", ");
169 svn_stringbuf_appendcstr(buf, state);
170 svn_stringbuf_appendcstr(buf, ", ");
175 svn_stringbuf_appendcstr(buf, country);
176 svn_stringbuf_appendcstr(buf, ", ");
179 /* Chop ', ' if any. */
180 svn_stringbuf_chop(buf, 2);
184 svn_stringbuf_appendcstr(buf, "(");
185 svn_stringbuf_appendcstr(buf, email);
186 svn_stringbuf_appendcstr(buf, ")");
192 static void append_reason(svn_stringbuf_t *errmsg, const char *reason, int *reasons)
195 svn_stringbuf_appendcstr(errmsg, _(": "));
197 svn_stringbuf_appendcstr(errmsg, _(", "));
198 svn_stringbuf_appendcstr(errmsg, reason);
202 /* This function is called on receiving a ssl certificate of a server when
203 opening a https connection. It allows Subversion to override the initial
204 validation done by serf.
205 Serf provides us the @a baton as provided in the call to
206 serf_ssl_server_cert_callback_set. The result of serf's initial validation
207 of the certificate @a CERT is returned as a bitmask in FAILURES. */
209 ssl_server_cert(void *baton, int failures,
210 const serf_ssl_certificate_t *cert,
211 apr_pool_t *scratch_pool)
213 svn_ra_serf__connection_t *conn = baton;
214 svn_auth_ssl_server_cert_info_t cert_info;
215 svn_auth_cred_ssl_server_trust_t *server_creds = NULL;
216 svn_auth_iterstate_t *state;
217 const char *realmstring;
218 apr_uint32_t svn_failures;
220 apr_hash_t *subject = NULL;
221 apr_hash_t *serf_cert = NULL;
224 svn_failures = (ssl_convert_serf_failures(failures)
225 | conn->server_cert_failures);
227 if (serf_ssl_cert_depth(cert) == 0)
229 /* If the depth is 0, the hostname must match the certificate.
231 ### This should really be handled by serf, which should pass an error
232 for this case, but that has backwards compatibility issues. */
233 apr_array_header_t *san;
234 svn_boolean_t found_matching_hostname = FALSE;
235 svn_string_t *actual_hostname =
236 svn_string_create(conn->session->session_url.hostname, scratch_pool);
238 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
240 san = svn_hash_gets(serf_cert, "subjectAltName");
241 /* Match server certificate CN with the hostname of the server iff
242 * we didn't find any subjectAltName fields and try to match them.
243 * Per RFC 2818 they are authoritative if present and CommonName
244 * should be ignored. NOTE: This isn't 100% correct since serf
245 * only loads the subjectAltName hash with dNSNames, technically
246 * we should ignore the CommonName if any subjectAltName entry
247 * exists even if it is one we don't support. */
248 if (san && san->nelts > 0)
251 for (i = 0; i < san->nelts; i++)
253 const char *s = APR_ARRAY_IDX(san, i, const char*);
254 svn_string_t *cert_hostname = svn_string_create(s, scratch_pool);
256 if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
258 found_matching_hostname = TRUE;
265 const char *hostname = NULL;
267 subject = serf_ssl_cert_subject(cert, scratch_pool);
270 hostname = svn_hash_gets(subject, "CN");
274 svn_string_t *cert_hostname = svn_string_create(hostname,
277 if (svn_cert__match_dns_identity(cert_hostname, actual_hostname))
279 found_matching_hostname = TRUE;
284 if (!found_matching_hostname)
285 svn_failures |= SVN_AUTH_SSL_CNMISMATCH;
291 /* Extract the info from the certificate */
293 subject = serf_ssl_cert_subject(cert, scratch_pool);
294 issuer = serf_ssl_cert_issuer(cert, scratch_pool);
296 serf_cert = serf_ssl_cert_certificate(cert, scratch_pool);
298 cert_info.hostname = svn_hash_gets(subject, "CN");
299 cert_info.fingerprint = svn_hash_gets(serf_cert, "sha1");
300 if (! cert_info.fingerprint)
301 cert_info.fingerprint = apr_pstrdup(scratch_pool, "<unknown>");
302 cert_info.valid_from = svn_hash_gets(serf_cert, "notBefore");
303 if (! cert_info.valid_from)
304 cert_info.valid_from = apr_pstrdup(scratch_pool, "[invalid date]");
305 cert_info.valid_until = svn_hash_gets(serf_cert, "notAfter");
306 if (! cert_info.valid_until)
307 cert_info.valid_until = apr_pstrdup(scratch_pool, "[invalid date]");
308 cert_info.issuer_dname = convert_organisation_to_str(issuer, scratch_pool);
309 cert_info.ascii_cert = serf_ssl_cert_export(cert, scratch_pool);
311 /* Handle any non-server certs. */
312 if (serf_ssl_cert_depth(cert) > 0)
316 svn_auth_set_parameter(conn->session->auth_baton,
317 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
320 svn_auth_set_parameter(conn->session->auth_baton,
321 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
324 realmstring = apr_psprintf(scratch_pool, "AUTHORITY:%s",
325 cert_info.fingerprint);
327 err = svn_auth_first_credentials(&creds, &state,
328 SVN_AUTH_CRED_SSL_SERVER_AUTHORITY,
330 conn->session->auth_baton,
333 svn_auth_set_parameter(conn->session->auth_baton,
334 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
336 svn_auth_set_parameter(conn->session->auth_baton,
337 SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL);
341 if (err->apr_err != SVN_ERR_AUTHN_NO_PROVIDER)
342 return svn_error_trace(err);
344 /* No provider registered that handles server authorities */
345 svn_error_clear(err);
351 server_creds = creds;
352 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
354 svn_failures &= ~server_creds->accepted_failures;
358 conn->server_cert_failures |= svn_failures;
363 svn_auth_set_parameter(conn->session->auth_baton,
364 SVN_AUTH_PARAM_SSL_SERVER_FAILURES,
367 svn_auth_set_parameter(conn->session->auth_baton,
368 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO,
371 realmstring = construct_realm(conn->session, conn->session->pool);
373 SVN_ERR(svn_auth_first_credentials(&creds, &state,
374 SVN_AUTH_CRED_SSL_SERVER_TRUST,
376 conn->session->auth_baton,
380 server_creds = creds;
381 svn_failures &= ~server_creds->accepted_failures;
382 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
385 while (svn_failures && creds)
387 SVN_ERR(svn_auth_next_credentials(&creds, state, scratch_pool));
391 server_creds = creds;
392 svn_failures &= ~server_creds->accepted_failures;
393 SVN_ERR(svn_auth_save_credentials(state, scratch_pool));
397 svn_auth_set_parameter(conn->session->auth_baton,
398 SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL);
400 /* Are there non accepted failures left? */
403 svn_stringbuf_t *errmsg;
406 errmsg = svn_stringbuf_create(
407 _("Server SSL certificate verification failed"),
411 if (svn_failures & SVN_AUTH_SSL_NOTYETVALID)
412 append_reason(errmsg, _("certificate is not yet valid"), &reasons);
414 if (svn_failures & SVN_AUTH_SSL_EXPIRED)
415 append_reason(errmsg, _("certificate has expired"), &reasons);
417 if (svn_failures & SVN_AUTH_SSL_CNMISMATCH)
418 append_reason(errmsg,
419 _("certificate issued for a different hostname"),
422 if (svn_failures & SVN_AUTH_SSL_UNKNOWNCA)
423 append_reason(errmsg, _("issuer is not trusted"), &reasons);
425 if (svn_failures & SVN_AUTH_SSL_OTHER)
426 append_reason(errmsg, _("and other reason(s)"), &reasons);
428 return svn_error_create(SVN_ERR_RA_SERF_SSL_CERT_UNTRUSTED, NULL,
435 /* Implements serf_ssl_need_server_cert_t for ssl_server_cert */
437 ssl_server_cert_cb(void *baton, int failures,
438 const serf_ssl_certificate_t *cert)
440 svn_ra_serf__connection_t *conn = baton;
441 svn_ra_serf__session_t *session = conn->session;
445 subpool = svn_pool_create(session->pool);
446 err = svn_error_trace(ssl_server_cert(baton, failures, cert, subpool));
447 svn_pool_destroy(subpool);
449 return save_error(session, err);
453 load_authorities(svn_ra_serf__connection_t *conn, const char *authorities,
456 apr_array_header_t *files = svn_cstring_split(authorities, ";",
457 TRUE /* chop_whitespace */,
461 for (i = 0; i < files->nelts; ++i)
463 const char *file = APR_ARRAY_IDX(files, i, const char *);
464 serf_ssl_certificate_t *ca_cert;
465 apr_status_t status = serf_ssl_load_cert_file(&ca_cert, file, pool);
467 if (status == APR_SUCCESS)
468 status = serf_ssl_trust_cert(conn->ssl_context, ca_cert);
470 if (status != APR_SUCCESS)
472 return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
473 _("Invalid config: unable to load certificate file '%s'"),
474 svn_dirent_local_style(file, pool));
482 conn_setup(apr_socket_t *sock,
483 serf_bucket_t **read_bkt,
484 serf_bucket_t **write_bkt,
488 svn_ra_serf__connection_t *conn = baton;
490 *read_bkt = serf_context_bucket_socket_create(conn->session->context,
491 sock, conn->bkt_alloc);
493 if (conn->session->using_ssl)
496 *read_bkt = serf_bucket_ssl_decrypt_create(*read_bkt, conn->ssl_context,
498 if (!conn->ssl_context)
500 conn->ssl_context = serf_bucket_ssl_encrypt_context_get(*read_bkt);
502 serf_ssl_set_hostname(conn->ssl_context,
503 conn->session->session_url.hostname);
505 serf_ssl_client_cert_provider_set(conn->ssl_context,
506 svn_ra_serf__handle_client_cert,
507 conn, conn->session->pool);
508 serf_ssl_client_cert_password_set(conn->ssl_context,
509 svn_ra_serf__handle_client_cert_pw,
510 conn, conn->session->pool);
511 serf_ssl_server_cert_callback_set(conn->ssl_context,
515 /* See if the user wants us to trust "default" openssl CAs. */
516 if (conn->session->trust_default_ca)
518 serf_ssl_use_default_certificates(conn->ssl_context);
520 /* Are there custom CAs to load? */
521 if (conn->session->ssl_authorities)
523 SVN_ERR(load_authorities(conn, conn->session->ssl_authorities,
524 conn->session->pool));
531 *write_bkt = serf_bucket_ssl_encrypt_create(*write_bkt,
540 /* svn_ra_serf__conn_setup is a callback for serf. This function
541 creates a read bucket and will wrap the write bucket if SSL
544 svn_ra_serf__conn_setup(apr_socket_t *sock,
545 serf_bucket_t **read_bkt,
546 serf_bucket_t **write_bkt,
550 svn_ra_serf__connection_t *conn = baton;
551 svn_ra_serf__session_t *session = conn->session;
554 err = svn_error_trace(conn_setup(sock,
559 return save_error(session, err);
563 /* Our default serf response acceptor. */
564 static serf_bucket_t *
565 accept_response(serf_request_t *request,
566 serf_bucket_t *stream,
567 void *acceptor_baton,
570 /* svn_ra_serf__handler_t *handler = acceptor_baton; */
572 serf_bucket_alloc_t *bkt_alloc;
574 bkt_alloc = serf_request_get_alloc(request);
575 c = serf_bucket_barrier_create(stream, bkt_alloc);
577 return serf_bucket_response_create(c, bkt_alloc);
581 /* Custom response acceptor for HEAD requests. */
582 static serf_bucket_t *
583 accept_head(serf_request_t *request,
584 serf_bucket_t *stream,
585 void *acceptor_baton,
588 /* svn_ra_serf__handler_t *handler = acceptor_baton; */
589 serf_bucket_t *response;
591 response = accept_response(request, stream, acceptor_baton, pool);
593 /* We know we shouldn't get a response body. */
594 serf_bucket_response_set_head(response);
600 connection_closed(svn_ra_serf__connection_t *conn,
606 return svn_ra_serf__wrap_err(why, NULL);
609 if (conn->session->using_ssl)
610 conn->ssl_context = NULL;
616 svn_ra_serf__conn_closed(serf_connection_t *conn,
621 svn_ra_serf__connection_t *ra_conn = closed_baton;
624 err = svn_error_trace(connection_closed(ra_conn, why, pool));
626 (void) save_error(ra_conn->session, err);
630 /* Implementation of svn_ra_serf__handle_client_cert */
632 handle_client_cert(void *data,
633 const char **cert_path,
636 svn_ra_serf__connection_t *conn = data;
637 svn_ra_serf__session_t *session = conn->session;
643 realm = construct_realm(session, session->pool);
645 if (!conn->ssl_client_auth_state)
647 SVN_ERR(svn_auth_first_credentials(&creds,
648 &conn->ssl_client_auth_state,
649 SVN_AUTH_CRED_SSL_CLIENT_CERT,
656 SVN_ERR(svn_auth_next_credentials(&creds,
657 conn->ssl_client_auth_state,
663 svn_auth_cred_ssl_client_cert_t *client_creds;
664 client_creds = creds;
665 *cert_path = client_creds->cert_file;
671 /* Implements serf_ssl_need_client_cert_t for handle_client_cert */
672 apr_status_t svn_ra_serf__handle_client_cert(void *data,
673 const char **cert_path)
675 svn_ra_serf__connection_t *conn = data;
676 svn_ra_serf__session_t *session = conn->session;
679 err = svn_error_trace(handle_client_cert(data, cert_path, session->pool));
681 return save_error(session, err);
684 /* Implementation for svn_ra_serf__handle_client_cert_pw */
686 handle_client_cert_pw(void *data,
687 const char *cert_path,
688 const char **password,
691 svn_ra_serf__connection_t *conn = data;
692 svn_ra_serf__session_t *session = conn->session;
697 if (!conn->ssl_client_pw_auth_state)
699 SVN_ERR(svn_auth_first_credentials(&creds,
700 &conn->ssl_client_pw_auth_state,
701 SVN_AUTH_CRED_SSL_CLIENT_CERT_PW,
708 SVN_ERR(svn_auth_next_credentials(&creds,
709 conn->ssl_client_pw_auth_state,
715 svn_auth_cred_ssl_client_cert_pw_t *pw_creds;
717 *password = pw_creds->password;
723 /* Implements serf_ssl_need_client_cert_pw_t for handle_client_cert_pw */
724 apr_status_t svn_ra_serf__handle_client_cert_pw(void *data,
725 const char *cert_path,
726 const char **password)
728 svn_ra_serf__connection_t *conn = data;
729 svn_ra_serf__session_t *session = conn->session;
732 err = svn_error_trace(handle_client_cert_pw(data,
737 return save_error(session, err);
742 * Given a REQUEST on connection CONN, construct a request bucket for it,
743 * returning the bucket in *REQ_BKT.
745 * If HDRS_BKT is not-NULL, it will be set to a headers_bucket that
746 * corresponds to the new request.
748 * The request will be METHOD at URL.
750 * If BODY_BKT is not-NULL, it will be sent as the request body.
752 * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header.
754 * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers
757 * REQUEST_POOL should live for the duration of the request. Serf will
758 * construct this and provide it to the request_setup callback, so we
759 * should just use that one.
762 setup_serf_req(serf_request_t *request,
763 serf_bucket_t **req_bkt,
764 serf_bucket_t **hdrs_bkt,
765 svn_ra_serf__session_t *session,
766 const char *method, const char *url,
767 serf_bucket_t *body_bkt, const char *content_type,
768 const char *accept_encoding,
769 svn_boolean_t dav_headers,
770 apr_pool_t *request_pool,
771 apr_pool_t *scratch_pool)
773 serf_bucket_alloc_t *allocator = serf_request_get_alloc(request);
776 svn_boolean_t set_CL = session->http10 || !session->using_chunked_requests;
778 if (set_CL && body_bkt != NULL)
780 /* Ugh. Use HTTP/1.0 to talk to the server because we don't know if
781 it speaks HTTP/1.1 (and thus, chunked requests), or because the
782 server actually responded as only supporting HTTP/1.0.
784 We'll take the existing body_bkt, spool it into a spillbuf, and
785 then wrap a bucket around that spillbuf. The spillbuf will give
786 us the Content-Length value. */
787 SVN_ERR(svn_ra_serf__copy_into_spillbuf(&buf, body_bkt,
790 /* Destroy original bucket since it content is already copied
792 serf_bucket_destroy(body_bkt);
794 body_bkt = svn_ra_serf__create_sb_bucket(buf, allocator,
799 /* Create a request bucket. Note that this sucker is kind enough to
800 add a "Host" header for us. */
801 *req_bkt = serf_request_bucket_request_create(request, method, url,
802 body_bkt, allocator);
804 /* Set the Content-Length value. This will also trigger an HTTP/1.0
805 request (rather than the default chunked request). */
808 if (body_bkt == NULL)
809 serf_bucket_request_set_CL(*req_bkt, 0);
811 serf_bucket_request_set_CL(*req_bkt, svn_spillbuf__get_size(buf));
814 *hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);
816 /* We use serf_bucket_headers_setn() because the USERAGENT has a
817 lifetime longer than this bucket. Thus, there is no need to copy
818 the header values. */
819 serf_bucket_headers_setn(*hdrs_bkt, "User-Agent", session->useragent);
823 serf_bucket_headers_setn(*hdrs_bkt, "Content-Type", content_type);
828 serf_bucket_headers_setn(*hdrs_bkt, "Connection", "keep-alive");
833 serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding);
836 /* These headers need to be sent with every request that might need
837 capability processing (e.g. during commit, reports, etc.), see
838 issue #3255 ("mod_dav_svn does not pass client capabilities to
839 start-commit hooks") for why.
841 Some request types like GET/HEAD/PROPFIND are unaware of capability
842 handling; and in some cases the responses can even be cached by
843 proxies, so we don't have to send these hearders there. */
846 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH);
847 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO);
848 serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS);
855 svn_ra_serf__context_run(svn_ra_serf__session_t *sess,
856 apr_interval_time_t *waittime_left,
857 apr_pool_t *scratch_pool)
861 assert(sess->pending_error == SVN_NO_ERROR);
863 if (sess->cancel_func)
864 SVN_ERR(sess->cancel_func(sess->cancel_baton));
866 status = serf_context_run(sess->context,
867 SVN_RA_SERF__CONTEXT_RUN_DURATION,
870 err = sess->pending_error;
871 sess->pending_error = SVN_NO_ERROR;
873 /* If the context duration timeout is up, we'll subtract that
874 duration from the total time alloted for such things. If
875 there's no time left, we fail with a message indicating that
876 the connection timed out. */
877 if (APR_STATUS_IS_TIMEUP(status))
883 if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION)
885 *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION;
890 svn_error_compose_create(
892 svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL,
893 _("Connection timed out")));
899 *waittime_left = sess->timeout;
905 /* ### This omits SVN_WARNING, and possibly relies on the fact that
906 ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */
907 if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST)
909 /* apr can't translate subversion errors to text */
910 SVN_ERR_W(svn_error_create(status, NULL, NULL),
911 _("Error running context"));
914 return svn_ra_serf__wrap_err(status, _("Error running context"));
921 svn_ra_serf__context_run_wait(svn_boolean_t *done,
922 svn_ra_serf__session_t *sess,
923 apr_pool_t *scratch_pool)
925 apr_pool_t *iterpool;
926 apr_interval_time_t waittime_left = sess->timeout;
928 assert(sess->pending_error == SVN_NO_ERROR);
930 iterpool = svn_pool_create(scratch_pool);
935 svn_pool_clear(iterpool);
937 SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
939 /* Debugging purposes only! */
940 for (i = 0; i < sess->num_conns; i++)
942 serf_debug__closed_conn(sess->conns[i]->bkt_alloc);
945 svn_pool_destroy(iterpool);
950 /* Ensure that a handler is no longer scheduled on the connection.
952 Eventually serf will have a reliable way to cancel existing requests,
953 but currently it doesn't even have a way to relyable identify a request
954 after rescheduling, for auth reasons.
956 So the only thing we can do today is reset the connection, which
957 will cancel all outstanding requests and prepare the connection
961 svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler)
963 serf_connection_reset(handler->conn->conn);
964 handler->scheduled = FALSE;
968 svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler,
969 apr_pool_t *scratch_pool)
973 /* Create a serf request based on HANDLER. */
974 svn_ra_serf__request_create(handler);
976 /* Wait until the response logic marks its DONE status. */
977 err = svn_ra_serf__context_run_wait(&handler->done, handler->session,
980 if (handler->scheduled)
982 /* We reset the connection (breaking pipelining, etc.), as
983 if we didn't the next data would still be handled by this handler,
984 which is done as far as our caller is concerned. */
985 svn_ra_serf__unschedule_handler(handler);
988 return svn_error_trace(err);
995 drain_bucket(serf_bucket_t *bucket)
997 /* Read whatever is in the bucket, and just drop it. */
1000 apr_status_t status;
1004 status = serf_bucket_read(bucket, SERF_READ_ALL_AVAIL, &data, &len);
1013 /* Implements svn_ra_serf__response_handler_t */
1015 svn_ra_serf__handle_discard_body(serf_request_t *request,
1016 serf_bucket_t *response,
1020 apr_status_t status;
1022 status = drain_bucket(response);
1024 return svn_ra_serf__wrap_err(status, NULL);
1026 return SVN_NO_ERROR;
1030 svn_ra_serf__response_discard_handler(serf_request_t *request,
1031 serf_bucket_t *response,
1035 return drain_bucket(response);
1039 /* Return the value of the RESPONSE's Location header if any, or NULL
1042 response_get_location(serf_bucket_t *response,
1043 const char *base_url,
1044 apr_pool_t *result_pool,
1045 apr_pool_t *scratch_pool)
1047 serf_bucket_t *headers;
1048 const char *location;
1050 headers = serf_bucket_response_get_headers(response);
1051 location = serf_bucket_headers_get(headers, "Location");
1052 if (location == NULL)
1055 /* The RFCs say we should have received a full url in LOCATION, but
1056 older apache versions and many custom web handlers just return a
1057 relative path here...
1059 And we can't trust anything because it is network data.
1061 if (*location == '/')
1064 apr_status_t status;
1066 status = apr_uri_parse(scratch_pool, base_url, &uri);
1068 if (status != APR_SUCCESS)
1071 /* Replace the path path with what we got */
1072 uri.path = (char*)svn_urlpath__canonicalize(location, scratch_pool);
1074 /* And make APR produce a proper full url for us */
1075 location = apr_uri_unparse(scratch_pool, &uri, 0);
1077 /* Fall through to ensure our canonicalization rules */
1079 else if (!svn_path_is_url(location))
1081 return NULL; /* Any other formats we should support? */
1084 return svn_uri_canonicalize(location, result_pool);
1088 /* Implements svn_ra_serf__response_handler_t */
1090 svn_ra_serf__expect_empty_body(serf_request_t *request,
1091 serf_bucket_t *response,
1093 apr_pool_t *scratch_pool)
1095 svn_ra_serf__handler_t *handler = baton;
1096 serf_bucket_t *hdrs;
1099 /* This function is just like handle_multistatus_only() except for the
1100 XML parsing callbacks. We want to look for the -readable element. */
1102 /* We should see this just once, in order to initialize SERVER_ERROR.
1103 At that point, the core error processing will take over. If we choose
1104 not to parse an error, then we'll never return here (because we
1105 change the response handler). */
1106 SVN_ERR_ASSERT(handler->server_error == NULL);
1108 hdrs = serf_bucket_response_get_headers(response);
1109 val = serf_bucket_headers_get(hdrs, "Content-Type");
1111 && (handler->sline.code < 200 || handler->sline.code >= 300)
1112 && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1114 svn_ra_serf__server_error_t *server_err;
1116 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1118 handler->handler_pool,
1119 handler->handler_pool));
1121 handler->server_error = server_err;
1125 /* The body was not text/xml, or we got a success code.
1126 Toss anything that arrives. */
1127 handler->discard_body = TRUE;
1130 /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it
1131 to call the response handler again. That will start up the XML parsing,
1132 or it will be dropped on the floor (per the decision above). */
1133 return SVN_NO_ERROR;
1138 svn_ra_serf__credentials_callback(char **username, char **password,
1139 serf_request_t *request, void *baton,
1140 int code, const char *authn_type,
1144 svn_ra_serf__handler_t *handler = baton;
1145 svn_ra_serf__session_t *session = handler->session;
1147 svn_auth_cred_simple_t *simple_creds;
1152 /* Use svn_auth_first_credentials if this is the first time we ask for
1153 credentials during this session OR if the last time we asked
1154 session->auth_state wasn't set (eg. if the credentials provider was
1155 cancelled by the user). */
1156 if (!session->auth_state)
1158 err = svn_auth_first_credentials(&creds,
1159 &session->auth_state,
1160 SVN_AUTH_CRED_SIMPLE,
1162 session->auth_baton,
1167 err = svn_auth_next_credentials(&creds,
1168 session->auth_state,
1174 (void) save_error(session, err);
1175 return err->apr_err;
1178 session->auth_attempts++;
1180 if (!creds || session->auth_attempts > 4)
1182 /* No more credentials. */
1183 (void) save_error(session,
1185 SVN_ERR_AUTHN_FAILED, NULL,
1186 _("No more credentials or we tried too many "
1187 "times.\nAuthentication failed")));
1188 return SVN_ERR_AUTHN_FAILED;
1191 simple_creds = creds;
1192 *username = apr_pstrdup(pool, simple_creds->username);
1193 *password = apr_pstrdup(pool, simple_creds->password);
1197 *username = apr_pstrdup(pool, session->proxy_username);
1198 *password = apr_pstrdup(pool, session->proxy_password);
1200 session->proxy_auth_attempts++;
1202 if (!session->proxy_username || session->proxy_auth_attempts > 4)
1204 /* No more credentials. */
1205 (void) save_error(session,
1207 SVN_ERR_AUTHN_FAILED, NULL,
1208 _("Proxy authentication failed")));
1209 return SVN_ERR_AUTHN_FAILED;
1213 handler->conn->last_status_code = code;
1218 /* Wait for HTTP response status and headers, and invoke HANDLER->
1219 response_handler() to carry out operation-specific processing.
1220 Afterwards, check for connection close.
1222 SERF_STATUS allows returning errors to serf without creating a
1223 subversion error object.
1225 static svn_error_t *
1226 handle_response(serf_request_t *request,
1227 serf_bucket_t *response,
1228 svn_ra_serf__handler_t *handler,
1229 apr_status_t *serf_status,
1230 apr_pool_t *scratch_pool)
1232 apr_status_t status;
1235 /* ### need to verify whether this already gets init'd on every
1236 ### successful exit. for an error-exit, it will (properly) be
1237 ### ignored by the caller. */
1238 *serf_status = APR_SUCCESS;
1242 /* Uh-oh. Our connection died. */
1243 handler->scheduled = FALSE;
1245 if (handler->response_error)
1247 /* Give a handler chance to prevent request requeue. */
1248 SVN_ERR(handler->response_error(request, response, 0,
1249 handler->response_error_baton));
1251 svn_ra_serf__request_create(handler);
1253 /* Response error callback is not configured. Requeue another request
1254 for this handler only if we didn't started to process body.
1255 Return error otherwise. */
1256 else if (!handler->reading_body)
1258 svn_ra_serf__request_create(handler);
1262 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1263 _("%s request on '%s' failed"),
1264 handler->method, handler->path);
1267 return SVN_NO_ERROR;
1270 /* If we're reading the body, then skip all this preparation. */
1271 if (handler->reading_body)
1274 /* Copy the Status-Line info into HANDLER, if we don't yet have it. */
1275 if (handler->sline.version == 0)
1277 serf_status_line sl;
1279 status = serf_bucket_response_status(response, &sl);
1280 if (status != APR_SUCCESS)
1282 /* The response line is not (yet) ready, or some other error. */
1283 *serf_status = status;
1284 return SVN_NO_ERROR; /* Handled by serf */
1287 /* If we got APR_SUCCESS, then we should have Status-Line info. */
1288 SVN_ERR_ASSERT(sl.version != 0);
1290 handler->sline = sl;
1291 handler->sline.reason = apr_pstrdup(handler->handler_pool, sl.reason);
1293 /* HTTP/1.1? (or later) */
1294 if (sl.version != SERF_HTTP_10)
1295 handler->session->http10 = FALSE;
1298 /* Keep reading from the network until we've read all the headers. */
1299 status = serf_bucket_response_wait_for_headers(response);
1302 /* The typical "error" will be APR_EAGAIN, meaning that more input
1303 from the network is required to complete the reading of the
1305 if (!APR_STATUS_IS_EOF(status))
1307 /* Either the headers are not (yet) complete, or there really
1309 *serf_status = status;
1310 return SVN_NO_ERROR;
1313 /* wait_for_headers() will return EOF if there is no body in this
1314 response, or if we completely read the body. The latter is not
1315 true since we would have set READING_BODY to get the body read,
1316 and we would not be back to this code block.
1318 It can also return EOF if we truly hit EOF while (say) processing
1319 the headers. aka Badness. */
1321 /* Cases where a lack of a response body (via EOF) is okay:
1323 * - 204/304 response
1325 * Otherwise, if we get an EOF here, something went really wrong: either
1326 * the server closed on us early or we're reading too much. Either way,
1329 if (strcmp(handler->method, "HEAD") != 0
1330 && handler->sline.code != 204
1331 && handler->sline.code != 304)
1333 err = svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
1334 svn_ra_serf__wrap_err(status, NULL),
1335 _("Premature EOF seen from server"
1336 " (http status=%d)"),
1337 handler->sline.code);
1339 /* In case anything else arrives... discard it. */
1340 handler->discard_body = TRUE;
1346 /* ... and set up the header fields in HANDLER. */
1347 handler->location = response_get_location(response,
1348 handler->session->session_url_str,
1349 handler->handler_pool,
1352 /* On the last request, we failed authentication. We succeeded this time,
1353 so let's save away these credentials. */
1354 if (handler->conn->last_status_code == 401 && handler->sline.code < 400)
1356 SVN_ERR(svn_auth_save_credentials(handler->session->auth_state,
1357 handler->session->pool));
1358 handler->session->auth_attempts = 0;
1359 handler->session->auth_state = NULL;
1361 handler->conn->last_status_code = handler->sline.code;
1363 if (handler->sline.code >= 400)
1365 /* 405 Method Not allowed.
1367 409 Conflict: can indicate a hook error.
1368 5xx (Internal) Server error. */
1369 serf_bucket_t *hdrs;
1372 hdrs = serf_bucket_response_get_headers(response);
1373 val = serf_bucket_headers_get(hdrs, "Content-Type");
1374 if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0)
1376 svn_ra_serf__server_error_t *server_err;
1378 SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler,
1380 handler->handler_pool,
1381 handler->handler_pool));
1383 handler->server_error = server_err;
1387 handler->discard_body = TRUE;
1390 else if (handler->sline.code <= 199)
1392 handler->discard_body = TRUE;
1395 /* Stop processing the above, on every packet arrival. */
1396 handler->reading_body = TRUE;
1400 /* We've been instructed to ignore the body. Drain whatever is present. */
1401 if (handler->discard_body)
1403 *serf_status = drain_bucket(response);
1405 return SVN_NO_ERROR;
1408 /* If we are supposed to parse the body as a server_error, then do
1410 if (handler->server_error != NULL)
1412 return svn_error_trace(
1413 svn_ra_serf__handle_server_error(handler->server_error,
1420 /* Pass the body along to the registered response handler. */
1421 err = handler->response_handler(request, response,
1422 handler->response_baton,
1426 && (!SERF_BUCKET_READ_ERROR(err->apr_err)
1427 || APR_STATUS_IS_ECONNRESET(err->apr_err)
1428 || APR_STATUS_IS_ECONNABORTED(err->apr_err)))
1430 /* These errors are special cased in serf
1431 ### We hope no handler returns these by accident. */
1432 *serf_status = err->apr_err;
1433 svn_error_clear(err);
1434 return SVN_NO_ERROR;
1437 return svn_error_trace(err);
1441 /* Implements serf_response_handler_t for handle_response. Storing
1442 errors in handler->session->pending_error if appropriate. */
1444 handle_response_cb(serf_request_t *request,
1445 serf_bucket_t *response,
1447 apr_pool_t *response_pool)
1449 svn_ra_serf__handler_t *handler = baton;
1451 apr_status_t inner_status;
1452 apr_status_t outer_status;
1453 apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */
1455 err = svn_error_trace(handle_response(request, response,
1456 handler, &inner_status,
1459 /* Select the right status value to return. */
1460 outer_status = save_error(handler->session, err);
1462 outer_status = inner_status;
1464 /* Make sure the DONE flag is set properly and requests are cleaned up. */
1465 if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status))
1467 svn_ra_serf__session_t *sess = handler->session;
1468 handler->done = TRUE;
1469 handler->scheduled = FALSE;
1470 outer_status = APR_EOF;
1472 /* We use a cached handler->session here to allow handler to free the
1473 memory containing the handler */
1475 handler->done_delegate(request, handler->done_delegate_baton,
1478 else if (SERF_BUCKET_READ_ERROR(outer_status)
1479 && handler->session->pending_error)
1481 handler->discard_body = TRUE; /* Discard further data */
1482 handler->done = TRUE; /* Mark as done */
1483 /* handler->scheduled is still TRUE, as we still expect data.
1484 If we would return an error outer-status the connection
1485 would have to be restarted. With scheduled still TRUE
1486 destroying the handler's pool will still reset the
1487 connection, avoiding the posibility of returning
1488 an error for this handler when a new request is
1490 outer_status = APR_EAGAIN; /* Exit context loop */
1493 return outer_status;
1496 /* Perform basic request setup, with special handling for HEAD requests,
1497 and finer-grained callbacks invoked (if non-NULL) to produce the request
1498 headers and body. */
1499 static svn_error_t *
1500 setup_request(serf_request_t *request,
1501 svn_ra_serf__handler_t *handler,
1502 serf_bucket_t **req_bkt,
1503 apr_pool_t *request_pool,
1504 apr_pool_t *scratch_pool)
1506 serf_bucket_t *body_bkt;
1507 serf_bucket_t *headers_bkt;
1508 const char *accept_encoding;
1510 if (handler->body_delegate)
1512 serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request);
1514 SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton,
1515 bkt_alloc, request_pool, scratch_pool));
1522 if (handler->custom_accept_encoding)
1524 accept_encoding = NULL;
1526 else if (handler->session->using_compression)
1528 /* Accept gzip compression if enabled. */
1529 accept_encoding = "gzip";
1533 accept_encoding = NULL;
1536 SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt,
1537 handler->session, handler->method, handler->path,
1538 body_bkt, handler->body_type, accept_encoding,
1539 !handler->no_dav_headers, request_pool,
1542 if (handler->header_delegate)
1544 SVN_ERR(handler->header_delegate(headers_bkt,
1545 handler->header_delegate_baton,
1546 request_pool, scratch_pool));
1549 return SVN_NO_ERROR;
1552 /* Implements the serf_request_setup_t interface (which sets up both a
1553 request and its response handler callback). Handles errors for
1556 setup_request_cb(serf_request_t *request,
1558 serf_bucket_t **req_bkt,
1559 serf_response_acceptor_t *acceptor,
1560 void **acceptor_baton,
1561 serf_response_handler_t *s_handler,
1562 void **s_handler_baton,
1563 apr_pool_t *request_pool)
1565 svn_ra_serf__handler_t *handler = setup_baton;
1566 apr_pool_t *scratch_pool;
1569 /* Construct a scratch_pool? serf gives us a pool that will live for
1570 the duration of the request. But requests are retried in some cases */
1571 scratch_pool = svn_pool_create(request_pool);
1573 if (strcmp(handler->method, "HEAD") == 0)
1574 *acceptor = accept_head;
1576 *acceptor = accept_response;
1577 *acceptor_baton = handler;
1579 *s_handler = handle_response_cb;
1580 *s_handler_baton = handler;
1582 err = svn_error_trace(setup_request(request, handler, req_bkt,
1583 request_pool, scratch_pool));
1585 svn_pool_destroy(scratch_pool);
1586 return save_error(handler->session, err);
1590 svn_ra_serf__request_create(svn_ra_serf__handler_t *handler)
1592 SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL
1593 && !handler->scheduled);
1595 /* In case HANDLER is re-queued, reset the various transient fields. */
1596 handler->done = FALSE;
1597 handler->server_error = NULL;
1598 handler->sline.version = 0;
1599 handler->location = NULL;
1600 handler->reading_body = FALSE;
1601 handler->discard_body = FALSE;
1602 handler->scheduled = TRUE;
1604 /* Keeping track of the returned request object would be nice, but doesn't
1605 work the way we would expect in ra_serf..
1607 Serf sometimes creates a new request for us (and destroys the old one)
1608 without telling, like when authentication failed (401/407 response.
1610 We 'just' trust serf to do the right thing and expect it to tell us
1611 when the state of the request changes.
1613 ### I fixed a request leak in serf in r2258 on auth failures.
1615 (void) serf_connection_request_create(handler->conn->conn,
1616 setup_request_cb, handler);
1621 svn_ra_serf__discover_vcc(const char **vcc_url,
1622 svn_ra_serf__session_t *session,
1623 apr_pool_t *scratch_pool)
1626 const char *relative_path;
1629 /* If we've already got the information our caller seeks, just return it. */
1630 if (session->vcc_url && session->repos_root_str)
1632 *vcc_url = session->vcc_url;
1633 return SVN_NO_ERROR;
1636 path = session->session_url.path;
1645 err = svn_ra_serf__fetch_node_props(&props, session,
1646 path, SVN_INVALID_REVNUM,
1648 scratch_pool, scratch_pool);
1651 apr_hash_t *ns_props;
1653 ns_props = apr_hash_get(props, "DAV:", 4);
1654 *vcc_url = svn_prop_get_value(ns_props,
1655 "version-controlled-configuration");
1657 ns_props = svn_hash_gets(props, SVN_DAV_PROP_NS_DAV);
1658 relative_path = svn_prop_get_value(ns_props,
1659 "baseline-relative-path");
1660 uuid = svn_prop_get_value(ns_props, "repository-uuid");
1665 if ((err->apr_err != SVN_ERR_FS_NOT_FOUND) &&
1666 (err->apr_err != SVN_ERR_RA_DAV_FORBIDDEN))
1668 return svn_error_trace(err); /* found a _real_ error */
1672 /* This happens when the file is missing in HEAD. */
1673 svn_error_clear(err);
1675 /* Okay, strip off a component from PATH. */
1676 path = svn_urlpath__dirname(path, scratch_pool);
1680 while ((path[0] != '\0')
1681 && (! (path[0] == '/' && path[1] == '\0')));
1685 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1686 _("The PROPFIND response did not include the "
1687 "requested version-controlled-configuration "
1691 /* Store our VCC in our cache. */
1692 if (!session->vcc_url)
1694 session->vcc_url = apr_pstrdup(session->pool, *vcc_url);
1697 /* Update our cached repository root URL. */
1698 if (!session->repos_root_str)
1700 svn_stringbuf_t *url_buf;
1702 url_buf = svn_stringbuf_create(path, scratch_pool);
1704 svn_path_remove_components(url_buf,
1705 svn_path_component_count(relative_path));
1707 /* Now recreate the root_url. */
1708 session->repos_root = session->session_url;
1709 session->repos_root.path =
1710 (char *)svn_fspath__canonicalize(url_buf->data, session->pool);
1711 session->repos_root_str =
1712 svn_urlpath__canonicalize(apr_uri_unparse(session->pool,
1713 &session->repos_root, 0),
1717 /* Store the repository UUID in the cache. */
1720 session->uuid = apr_pstrdup(session->pool, uuid);
1723 return SVN_NO_ERROR;
1727 svn_ra_serf__get_relative_path(const char **rel_path,
1728 const char *orig_path,
1729 svn_ra_serf__session_t *session,
1732 const char *decoded_root, *decoded_orig;
1734 if (! session->repos_root.path)
1736 const char *vcc_url;
1738 /* This should only happen if we haven't detected HTTP v2
1739 support from the server. */
1740 assert(! SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session));
1742 /* We don't actually care about the VCC_URL, but this API
1743 promises to populate the session's root-url cache, and that's
1744 what we really want. */
1745 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session,
1749 decoded_root = svn_path_uri_decode(session->repos_root.path, pool);
1750 decoded_orig = svn_path_uri_decode(orig_path, pool);
1751 *rel_path = svn_urlpath__skip_ancestor(decoded_root, decoded_orig);
1752 SVN_ERR_ASSERT(*rel_path != NULL);
1753 return SVN_NO_ERROR;
1757 svn_ra_serf__report_resource(const char **report_target,
1758 svn_ra_serf__session_t *session,
1761 /* If we have HTTP v2 support, we want to report against the 'me'
1763 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
1764 *report_target = apr_pstrdup(pool, session->me_resource);
1766 /* Otherwise, we'll use the default VCC. */
1768 SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool));
1770 return SVN_NO_ERROR;
1774 svn_ra_serf__error_on_status(serf_status_line sline,
1776 const char *location)
1785 return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL,
1787 ? _("Repository moved permanently to '%s'")
1788 : _("Repository moved temporarily to '%s'"),
1791 return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL,
1792 _("Access to '%s' forbidden"), path);
1795 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1796 _("'%s' path not found"), path);
1798 return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1799 _("HTTP method is not allowed on '%s'"),
1802 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
1803 _("'%s' conflicts"), path);
1805 return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL,
1806 _("Precondition on '%s' failed"), path);
1808 return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL,
1809 _("'%s': no lock token available"), path);
1812 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1813 _("DAV request failed: 411 Content length required. The "
1814 "server or an intermediate proxy does not accept "
1815 "chunked encoding. Try setting 'http-chunked-requests' "
1816 "to 'auto' or 'no' in your client configuration."));
1818 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1819 _("Unexpected server error %d '%s' on '%s'"),
1820 sline.code, sline.reason, path);
1822 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1823 _("The requested feature is not supported by "
1827 if (sline.code >= 300 || sline.code <= 199)
1828 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1829 _("Unexpected HTTP status %d '%s' on '%s'"),
1830 sline.code, sline.reason, path);
1832 return SVN_NO_ERROR;
1836 svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler)
1838 /* Is it a standard error status? */
1839 if (handler->sline.code != 405)
1840 SVN_ERR(svn_ra_serf__error_on_status(handler->sline,
1842 handler->location));
1844 switch (handler->sline.code)
1847 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1848 _("Path '%s' unexpectedly created"),
1851 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1852 _("Path '%s' already exists"),
1856 return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL,
1857 _("The HTTP method '%s' is not allowed"
1859 handler->method, handler->path);
1861 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
1862 _("Unexpected HTTP status %d '%s' on '%s' "
1864 handler->sline.code, handler->sline.reason,
1865 handler->method, handler->path);
1870 svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session,
1871 svn_delta_shim_callbacks_t *callbacks)
1873 svn_ra_serf__session_t *session = ra_session->priv;
1875 session->shim_callbacks = callbacks;
1876 return SVN_NO_ERROR;
1879 /* Shared/standard done_delegate handler */
1880 static svn_error_t *
1881 response_done(serf_request_t *request,
1882 void *handler_baton,
1883 apr_pool_t *scratch_pool)
1885 svn_ra_serf__handler_t *handler = handler_baton;
1887 assert(handler->done);
1889 if (handler->no_fail_on_http_failure_status)
1890 return SVN_NO_ERROR;
1892 if (handler->server_error)
1893 return svn_ra_serf__server_error_create(handler, scratch_pool);
1895 if (handler->sline.code >= 400 || handler->sline.code <= 199)
1897 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1900 if ((handler->sline.code >= 300 && handler->sline.code < 399)
1901 && !handler->no_fail_on_http_redirect_status)
1903 return svn_error_trace(svn_ra_serf__unexpected_status(handler));
1906 return SVN_NO_ERROR;
1909 /* Pool cleanup handler for request handlers.
1911 If a serf context run stops for some outside error, like when the user
1912 cancels a request via ^C in the context loop, the handler is still
1913 registered in the serf context. With the pool cleanup there would be
1914 handlers registered in no freed memory.
1916 This fallback kills the connection for this case, which will make serf
1917 unregister any outstanding requests on it. */
1919 handler_cleanup(void *baton)
1921 svn_ra_serf__handler_t *handler = baton;
1922 if (handler->scheduled)
1924 svn_ra_serf__unschedule_handler(handler);
1930 svn_ra_serf__handler_t *
1931 svn_ra_serf__create_handler(svn_ra_serf__session_t *session,
1932 apr_pool_t *result_pool)
1934 svn_ra_serf__handler_t *handler;
1936 handler = apr_pcalloc(result_pool, sizeof(*handler));
1937 handler->handler_pool = result_pool;
1939 apr_pool_cleanup_register(result_pool, handler, handler_cleanup,
1940 apr_pool_cleanup_null);
1942 handler->session = session;
1943 handler->conn = session->conns[0];
1945 /* Setup the default done handler, to handle server errors */
1946 handler->done_delegate_baton = handler;
1947 handler->done_delegate = response_done;
1953 svn_ra_serf__uri_parse(apr_uri_t *uri,
1954 const char *url_str,
1955 apr_pool_t *result_pool)
1957 apr_status_t status;
1959 status = apr_uri_parse(result_pool, url_str, uri);
1962 /* Do not use returned error status in error message because currently
1963 apr_uri_parse() returns APR_EGENERAL for all parsing errors. */
1964 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
1965 _("Illegal URL '%s'"),
1969 /* Depending the version of apr-util in use, for root paths uri.path
1970 will be NULL or "", where serf requires "/". */
1971 if (uri->path == NULL || uri->path[0] == '\0')
1973 uri->path = apr_pstrdup(result_pool, "/");
1976 return SVN_NO_ERROR;