diff options
Diffstat (limited to 'subversion/libsvn_ra_serf/util.c')
-rw-r--r-- | subversion/libsvn_ra_serf/util.c | 1549 |
1 files changed, 353 insertions, 1196 deletions
diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c index 8f6c1bb..5490dde 100644 --- a/subversion/libsvn_ra_serf/util.c +++ b/subversion/libsvn_ra_serf/util.c @@ -32,83 +32,22 @@ #include <serf.h> #include <serf_bucket_types.h> -#include <expat.h> - #include "svn_hash.h" #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_private_config.h" #include "svn_string.h" -#include "svn_xml.h" #include "svn_props.h" #include "svn_dirent_uri.h" #include "../libsvn_ra/ra_loader.h" #include "private/svn_dep_compat.h" #include "private/svn_fspath.h" -#include "private/svn_subr_private.h" #include "private/svn_auth_private.h" #include "private/svn_cert.h" #include "ra_serf.h" - -/* Fix for older expat 1.95.x's that do not define - * XML_STATUS_OK/XML_STATUS_ERROR - */ -#ifndef XML_STATUS_OK -#define XML_STATUS_OK 1 -#define XML_STATUS_ERROR 0 -#endif - -#ifndef XML_VERSION_AT_LEAST -#define XML_VERSION_AT_LEAST(major,minor,patch) \ -(((major) < XML_MAJOR_VERSION) \ - || ((major) == XML_MAJOR_VERSION && (minor) < XML_MINOR_VERSION) \ - || ((major) == XML_MAJOR_VERSION && (minor) == XML_MINOR_VERSION && \ - (patch) <= XML_MICRO_VERSION)) -#endif /* APR_VERSION_AT_LEAST */ - -#if XML_VERSION_AT_LEAST(1, 95, 8) -#define EXPAT_HAS_STOPPARSER -#endif - -/* Read/write chunks of this size into the spillbuf. */ -#define PARSE_CHUNK_SIZE 8000 - -/* We will store one megabyte in memory, before switching to store content - into a temporary file. */ -#define SPILL_SIZE 1000000 - - -/* This structure records pending data for the parser in memory blocks, - and possibly into a temporary file if "too much" content arrives. */ -struct svn_ra_serf__pending_t { - /* The spillbuf where we record the pending data. */ - svn_spillbuf_t *buf; - - /* This flag is set when the network has reached EOF. The PENDING - processing can then properly detect when parsing has completed. */ - svn_boolean_t network_eof; -}; - -#define HAS_PENDING_DATA(p) ((p) != NULL && (p)->buf != NULL \ - && svn_spillbuf__get_size((p)->buf) != 0) - - -struct expat_ctx_t { - svn_ra_serf__xml_context_t *xmlctx; - XML_Parser parser; - svn_ra_serf__handler_t *handler; - - svn_error_t *inner_error; - - /* Do not use this pool for allocation. It is merely recorded for running - the cleanup handler. */ - apr_pool_t *cleanup_pool; -}; - - static const apr_uint32_t serf_failure_map[][2] = { { SERF_SSL_CERT_NOTYETVALID, SVN_AUTH_SSL_NOTYETVALID }, @@ -192,6 +131,7 @@ construct_realm(svn_ra_serf__session_t *session, static char * convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool) { + const char *cn = svn_hash_gets(org, "CN"); const char *org_unit = svn_hash_gets(org, "OU"); const char *org_name = svn_hash_gets(org, "O"); const char *locality = svn_hash_gets(org, "L"); @@ -200,6 +140,12 @@ convert_organisation_to_str(apr_hash_t *org, apr_pool_t *pool) const char *email = svn_hash_gets(org, "E"); svn_stringbuf_t *buf = svn_stringbuf_create_empty(pool); + if (cn) + { + svn_stringbuf_appendcstr(buf, cn); + svn_stringbuf_appendcstr(buf, ", "); + } + if (org_unit) { svn_stringbuf_appendcstr(buf, org_unit); @@ -285,7 +231,6 @@ ssl_server_cert(void *baton, int failures, ### This should really be handled by serf, which should pass an error for this case, but that has backwards compatibility issues. */ apr_array_header_t *san; - svn_boolean_t found_san_entry = FALSE; svn_boolean_t found_matching_hostname = FALSE; svn_string_t *actual_hostname = svn_string_create(conn->session->session_url.hostname, scratch_pool); @@ -293,11 +238,16 @@ ssl_server_cert(void *baton, int failures, serf_cert = serf_ssl_cert_certificate(cert, scratch_pool); san = svn_hash_gets(serf_cert, "subjectAltName"); - /* Try to find matching server name via subjectAltName first... */ - if (san) + /* Match server certificate CN with the hostname of the server iff + * we didn't find any subjectAltName fields and try to match them. + * Per RFC 2818 they are authoritative if present and CommonName + * should be ignored. NOTE: This isn't 100% correct since serf + * only loads the subjectAltName hash with dNSNames, technically + * we should ignore the CommonName if any subjectAltName entry + * exists even if it is one we don't support. */ + if (san && san->nelts > 0) { int i; - found_san_entry = san->nelts > 0; for (i = 0; i < san->nelts; i++) { const char *s = APR_ARRAY_IDX(san, i, const char*); @@ -310,12 +260,7 @@ ssl_server_cert(void *baton, int failures, } } } - - /* Match server certificate CN with the hostname of the server iff - * we didn't find any subjectAltName fields and try to match them. - * Per RFC 2818 they are authoritative if present and CommonName - * should be ignored. */ - if (!found_matching_hostname && !found_san_entry) + else { const char *hostname = NULL; @@ -368,11 +313,11 @@ ssl_server_cert(void *baton, int failures, { svn_error_t *err; - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, &cert_info); - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_FAILURES, &svn_failures); @@ -382,13 +327,13 @@ ssl_server_cert(void *baton, int failures, err = svn_auth_first_credentials(&creds, &state, SVN_AUTH_CRED_SSL_SERVER_AUTHORITY, realmstring, - conn->session->wc_callbacks->auth_baton, + conn->session->auth_baton, scratch_pool); - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL); - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_FAILURES, NULL); if (err) @@ -415,11 +360,11 @@ ssl_server_cert(void *baton, int failures, return APR_SUCCESS; } - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_FAILURES, &svn_failures); - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, &cert_info); @@ -428,7 +373,7 @@ ssl_server_cert(void *baton, int failures, SVN_ERR(svn_auth_first_credentials(&creds, &state, SVN_AUTH_CRED_SSL_SERVER_TRUST, realmstring, - conn->session->wc_callbacks->auth_baton, + conn->session->auth_baton, scratch_pool)); if (creds) { @@ -449,7 +394,7 @@ ssl_server_cert(void *baton, int failures, } } - svn_auth_set_parameter(conn->session->wc_callbacks->auth_baton, + svn_auth_set_parameter(conn->session->auth_baton, SVN_AUTH_PARAM_SSL_SERVER_CERT_INFO, NULL); /* Are there non accepted failures left? */ @@ -622,6 +567,7 @@ accept_response(serf_request_t *request, void *acceptor_baton, apr_pool_t *pool) { + /* svn_ra_serf__handler_t *handler = acceptor_baton; */ serf_bucket_t *c; serf_bucket_alloc_t *bkt_alloc; @@ -639,6 +585,7 @@ accept_head(serf_request_t *request, void *acceptor_baton, apr_pool_t *pool) { + /* svn_ra_serf__handler_t *handler = acceptor_baton; */ serf_bucket_t *response; response = accept_response(request, stream, acceptor_baton, pool); @@ -656,7 +603,7 @@ connection_closed(svn_ra_serf__connection_t *conn, { if (why) { - return svn_error_wrap_apr(why, NULL); + return svn_ra_serf__wrap_err(why, NULL); } if (conn->session->using_ssl) @@ -701,7 +648,7 @@ handle_client_cert(void *data, &conn->ssl_client_auth_state, SVN_AUTH_CRED_SSL_CLIENT_CERT, realm, - session->wc_callbacks->auth_baton, + session->auth_baton, pool)); } else @@ -753,7 +700,7 @@ handle_client_cert_pw(void *data, &conn->ssl_client_pw_auth_state, SVN_AUTH_CRED_SSL_CLIENT_CERT_PW, cert_path, - session->wc_callbacks->auth_baton, + session->auth_baton, pool)); } else @@ -804,6 +751,9 @@ apr_status_t svn_ra_serf__handle_client_cert_pw(void *data, * * If CONTENT_TYPE is not-NULL, it will be sent as the Content-Type header. * + * If DAV_HEADERS is non-zero, it will add standard DAV capabilites headers + * to request. + * * REQUEST_POOL should live for the duration of the request. Serf will * construct this and provide it to the request_setup callback, so we * should just use that one. @@ -816,6 +766,7 @@ setup_serf_req(serf_request_t *request, const char *method, const char *url, serf_bucket_t *body_bkt, const char *content_type, const char *accept_encoding, + svn_boolean_t dav_headers, apr_pool_t *request_pool, apr_pool_t *scratch_pool) { @@ -882,12 +833,86 @@ setup_serf_req(serf_request_t *request, serf_bucket_headers_setn(*hdrs_bkt, "Accept-Encoding", accept_encoding); } - /* These headers need to be sent with every request; see issue #3255 - ("mod_dav_svn does not pass client capabilities to start-commit - hooks") for why. */ - serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH); - serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO); - serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS); + /* These headers need to be sent with every request that might need + capability processing (e.g. during commit, reports, etc.), see + issue #3255 ("mod_dav_svn does not pass client capabilities to + start-commit hooks") for why. + + Some request types like GET/HEAD/PROPFIND are unaware of capability + handling; and in some cases the responses can even be cached by + proxies, so we don't have to send these hearders there. */ + if (dav_headers) + { + serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_DEPTH); + serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_MERGEINFO); + serf_bucket_headers_setn(*hdrs_bkt, "DAV", SVN_DAV_NS_DAV_SVN_LOG_REVPROPS); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_ra_serf__context_run(svn_ra_serf__session_t *sess, + apr_interval_time_t *waittime_left, + apr_pool_t *scratch_pool) +{ + apr_status_t status; + svn_error_t *err; + assert(sess->pending_error == SVN_NO_ERROR); + + if (sess->cancel_func) + SVN_ERR(sess->cancel_func(sess->cancel_baton)); + + status = serf_context_run(sess->context, + SVN_RA_SERF__CONTEXT_RUN_DURATION, + scratch_pool); + + err = sess->pending_error; + sess->pending_error = SVN_NO_ERROR; + + /* If the context duration timeout is up, we'll subtract that + duration from the total time alloted for such things. If + there's no time left, we fail with a message indicating that + the connection timed out. */ + if (APR_STATUS_IS_TIMEUP(status)) + { + status = 0; + + if (sess->timeout) + { + if (*waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) + { + *waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; + } + else + { + return + svn_error_compose_create( + err, + svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, + _("Connection timed out"))); + } + } + } + else + { + *waittime_left = sess->timeout; + } + + SVN_ERR(err); + if (status) + { + /* ### This omits SVN_WARNING, and possibly relies on the fact that + ### MAX(SERF_ERROR_*) < SVN_ERR_BAD_CATEGORY_START? */ + if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST) + { + /* apr can't translate subversion errors to text */ + SVN_ERR_W(svn_error_create(status, NULL, NULL), + _("Error running context")); + } + + return svn_ra_serf__wrap_err(status, _("Error running context")); + } return SVN_NO_ERROR; } @@ -905,63 +930,11 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done, iterpool = svn_pool_create(scratch_pool); while (!*done) { - apr_status_t status; - svn_error_t *err; int i; svn_pool_clear(iterpool); - if (sess->cancel_func) - SVN_ERR((*sess->cancel_func)(sess->cancel_baton)); - - status = serf_context_run(sess->context, - SVN_RA_SERF__CONTEXT_RUN_DURATION, - iterpool); - - err = sess->pending_error; - sess->pending_error = SVN_NO_ERROR; - - /* If the context duration timeout is up, we'll subtract that - duration from the total time alloted for such things. If - there's no time left, we fail with a message indicating that - the connection timed out. */ - if (APR_STATUS_IS_TIMEUP(status)) - { - status = 0; - - if (sess->timeout) - { - if (waittime_left > SVN_RA_SERF__CONTEXT_RUN_DURATION) - { - waittime_left -= SVN_RA_SERF__CONTEXT_RUN_DURATION; - } - else - { - return - svn_error_compose_create( - err, - svn_error_create(SVN_ERR_RA_DAV_CONN_TIMEOUT, NULL, - _("Connection timed out"))); - } - } - } - else - { - waittime_left = sess->timeout; - } - - SVN_ERR(err); - if (status) - { - if (status >= SVN_ERR_BAD_CATEGORY_START && status < SVN_ERR_LAST) - { - /* apr can't translate subversion errors to text */ - SVN_ERR_W(svn_error_create(status, NULL, NULL), - _("Error running context")); - } - - return svn_ra_serf__wrap_err(status, _("Error running context")); - } + SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool)); /* Debugging purposes only! */ for (i = 0; i < sess->num_conns; i++) @@ -974,6 +947,22 @@ svn_ra_serf__context_run_wait(svn_boolean_t *done, return SVN_NO_ERROR; } +/* Ensure that a handler is no longer scheduled on the connection. + + Eventually serf will have a reliable way to cancel existing requests, + but currently it doesn't even have a way to relyable identify a request + after rescheduling, for auth reasons. + + So the only thing we can do today is reset the connection, which + will cancel all outstanding requests and prepare the connection + for re-use. +*/ +static void +svn_ra_serf__unschedule_handler(svn_ra_serf__handler_t *handler) +{ + serf_connection_reset(handler->conn->conn); + handler->scheduled = FALSE; +} svn_error_t * svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler, @@ -988,130 +977,18 @@ svn_ra_serf__context_run_one(svn_ra_serf__handler_t *handler, err = svn_ra_serf__context_run_wait(&handler->done, handler->session, scratch_pool); - /* A callback invocation has been canceled. In this simple case of - context_run_one, we can keep the ra-session operational by resetting - the connection. - - If we don't do this, the next context run will notice that the connection - is still in the error state and will just return SVN_ERR_CEASE_INVOCATION - (=the last error for the connection) again */ - if (err && err->apr_err == SVN_ERR_CEASE_INVOCATION) - { - apr_status_t status = serf_connection_reset(handler->conn->conn); - - if (status) - err = svn_error_compose_create(err, - svn_ra_serf__wrap_err(status, NULL)); - } - - if (handler->server_error) + if (handler->scheduled) { - err = svn_error_compose_create(err, handler->server_error->error); - handler->server_error = NULL; + /* We reset the connection (breaking pipelining, etc.), as + if we didn't the next data would still be handled by this handler, + which is done as far as our caller is concerned. */ + svn_ra_serf__unschedule_handler(handler); } return svn_error_trace(err); } -/* - * Expat callback invoked on a start element tag for an error response. - */ -static svn_error_t * -start_error(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - const char **attrs, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (!ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "error") == 0) - { - ctx->in_error = TRUE; - } - else if (ctx->in_error && strcmp(name.name, "human-readable") == 0) - { - const char *err_code; - - err_code = svn_xml_get_attr_value("errcode", attrs); - if (err_code) - { - apr_int64_t val; - - SVN_ERR(svn_cstring_atoi64(&val, err_code)); - ctx->error->apr_err = (apr_status_t)val; - } - - /* If there's no error code provided, or if the provided code is - 0 (which can happen sometimes depending on how the error is - constructed on the server-side), just pick a generic error - code to run with. */ - if (! ctx->error->apr_err) - { - ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; - } - - /* Start collecting cdata. */ - svn_stringbuf_setempty(ctx->cdata); - ctx->collect_cdata = TRUE; - } - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on an end element tag for a PROPFIND response. - */ -static svn_error_t * -end_error(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "error") == 0) - { - ctx->in_error = FALSE; - } - if (ctx->in_error && strcmp(name.name, "human-readable") == 0) - { - /* On the server dav_error_response_tag() will add a leading - and trailing newline if DEBUG_CR is defined in mod_dav.h, - so remove any such characters here. */ - svn_stringbuf_strip_whitespace(ctx->cdata); - - ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data, - ctx->cdata->len); - ctx->collect_cdata = FALSE; - } - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on CDATA elements in an error response. - * - * This callback can be called multiple times. - */ -static svn_error_t * -cdata_error(svn_ra_serf__xml_parser_t *parser, - const char *data, - apr_size_t len, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (ctx->collect_cdata) - { - svn_stringbuf_appendbytes(ctx->cdata, data, len); - } - - return SVN_NO_ERROR; -} static apr_status_t @@ -1131,28 +1008,7 @@ drain_bucket(serf_bucket_t *bucket) } -static svn_ra_serf__server_error_t * -begin_error_parsing(svn_ra_serf__xml_start_element_t start, - svn_ra_serf__xml_end_element_t end, - svn_ra_serf__xml_cdata_chunk_handler_t cdata, - apr_pool_t *result_pool) -{ - svn_ra_serf__server_error_t *server_err; - - server_err = apr_pcalloc(result_pool, sizeof(*server_err)); - server_err->error = svn_error_create(APR_SUCCESS, NULL, NULL); - server_err->contains_precondition_error = FALSE; - server_err->cdata = svn_stringbuf_create_empty(server_err->error->pool); - server_err->collect_cdata = FALSE; - server_err->parser.pool = server_err->error->pool; - server_err->parser.user_data = server_err; - server_err->parser.start = start; - server_err->parser.end = end; - server_err->parser.cdata = cdata; - server_err->parser.ignore_errors = TRUE; - - return server_err; -} + /* Implements svn_ra_serf__response_handler_t */ svn_error_t * @@ -1241,7 +1097,7 @@ svn_ra_serf__expect_empty_body(serf_request_t *request, const char *val; /* This function is just like handle_multistatus_only() except for the - XML parsing callbacks. We want to look for the human-readable element. */ + XML parsing callbacks. We want to look for the -readable element. */ /* We should see this just once, in order to initialize SERVER_ERROR. At that point, the core error processing will take over. If we choose @@ -1251,21 +1107,22 @@ svn_ra_serf__expect_empty_body(serf_request_t *request, hdrs = serf_bucket_response_get_headers(response); val = serf_bucket_headers_get(hdrs, "Content-Type"); - if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) + if (val + && (handler->sline.code < 200 || handler->sline.code >= 300) + && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) { svn_ra_serf__server_error_t *server_err; - server_err = begin_error_parsing(start_error, end_error, cdata_error, - handler->handler_pool); - - /* Get the parser to set our DONE flag. */ - server_err->parser.done = &handler->done; + SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler, + FALSE, + handler->handler_pool, + handler->handler_pool)); handler->server_error = server_err; } else { - /* The body was not text/xml, so we don't know what to do with it. + /* The body was not text/xml, or we got a success code. Toss anything that arrives. */ handler->discard_body = TRUE; } @@ -1277,631 +1134,6 @@ svn_ra_serf__expect_empty_body(serf_request_t *request, } -/* Given a string like "HTTP/1.1 500 (status)" in BUF, parse out the numeric - status code into *STATUS_CODE_OUT. Ignores leading whitespace. */ -static svn_error_t * -parse_dav_status(int *status_code_out, svn_stringbuf_t *buf, - apr_pool_t *scratch_pool) -{ - svn_error_t *err; - const char *token; - char *tok_status; - svn_stringbuf_t *temp_buf = svn_stringbuf_dup(buf, scratch_pool); - - svn_stringbuf_strip_whitespace(temp_buf); - token = apr_strtok(temp_buf->data, " \t\r\n", &tok_status); - if (token) - token = apr_strtok(NULL, " \t\r\n", &tok_status); - if (!token) - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("Malformed DAV:status CDATA '%s'"), - buf->data); - err = svn_cstring_atoi(status_code_out, token); - if (err) - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, err, - _("Malformed DAV:status CDATA '%s'"), - buf->data); - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on a start element tag for a 207 response. - */ -static svn_error_t * -start_207(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - const char **attrs, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (!ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "multistatus") == 0) - { - ctx->in_error = TRUE; - } - else if (ctx->in_error && strcmp(name.name, "responsedescription") == 0) - { - /* Start collecting cdata. */ - svn_stringbuf_setempty(ctx->cdata); - ctx->collect_cdata = TRUE; - } - else if (ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "status") == 0) - { - /* Start collecting cdata. */ - svn_stringbuf_setempty(ctx->cdata); - ctx->collect_cdata = TRUE; - } - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on an end element tag for a 207 response. - */ -static svn_error_t * -end_207(svn_ra_serf__xml_parser_t *parser, - svn_ra_serf__dav_props_t name, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "multistatus") == 0) - { - ctx->in_error = FALSE; - } - if (ctx->in_error && strcmp(name.name, "responsedescription") == 0) - { - /* Remove leading newline added by DEBUG_CR on server */ - svn_stringbuf_strip_whitespace(ctx->cdata); - - ctx->collect_cdata = FALSE; - ctx->error->message = apr_pstrmemdup(ctx->error->pool, ctx->cdata->data, - ctx->cdata->len); - if (ctx->contains_precondition_error) - ctx->error->apr_err = SVN_ERR_FS_PROP_BASEVALUE_MISMATCH; - else - ctx->error->apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; - } - else if (ctx->in_error && - strcmp(name.namespace, "DAV:") == 0 && - strcmp(name.name, "status") == 0) - { - int status_code; - - ctx->collect_cdata = FALSE; - - SVN_ERR(parse_dav_status(&status_code, ctx->cdata, parser->pool)); - if (status_code == 412) - ctx->contains_precondition_error = TRUE; - } - - return SVN_NO_ERROR; -} - -/* - * Expat callback invoked on CDATA elements in a 207 response. - * - * This callback can be called multiple times. - */ -static svn_error_t * -cdata_207(svn_ra_serf__xml_parser_t *parser, - const char *data, - apr_size_t len, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t *ctx = parser->user_data; - - if (ctx->collect_cdata) - { - svn_stringbuf_appendbytes(ctx->cdata, data, len); - } - - return SVN_NO_ERROR; -} - -/* Implements svn_ra_serf__response_handler_t */ -svn_error_t * -svn_ra_serf__handle_multistatus_only(serf_request_t *request, - serf_bucket_t *response, - void *baton, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__handler_t *handler = baton; - - /* This function is just like expect_empty_body() except for the - XML parsing callbacks. We are looking for very limited pieces of - the multistatus response. */ - - /* We should see this just once, in order to initialize SERVER_ERROR. - At that point, the core error processing will take over. If we choose - not to parse an error, then we'll never return here (because we - change the response handler). */ - SVN_ERR_ASSERT(handler->server_error == NULL); - - { - serf_bucket_t *hdrs; - const char *val; - - hdrs = serf_bucket_response_get_headers(response); - val = serf_bucket_headers_get(hdrs, "Content-Type"); - if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) - { - svn_ra_serf__server_error_t *server_err; - - server_err = begin_error_parsing(start_207, end_207, cdata_207, - handler->handler_pool); - - /* Get the parser to set our DONE flag. */ - server_err->parser.done = &handler->done; - - handler->server_error = server_err; - } - else - { - /* The body was not text/xml, so we don't know what to do with it. - Toss anything that arrives. */ - handler->discard_body = TRUE; - } - } - - /* Returning SVN_NO_ERROR will return APR_SUCCESS to serf, which tells it - to call the response handler again. That will start up the XML parsing, - or it will be dropped on the floor (per the decision above). */ - return SVN_NO_ERROR; -} - - -/* Conforms to Expat's XML_StartElementHandler */ -static void -start_xml(void *userData, const char *raw_name, const char **attrs) -{ - svn_ra_serf__xml_parser_t *parser = userData; - svn_ra_serf__dav_props_t name; - apr_pool_t *scratch_pool; - svn_error_t *err; - - if (parser->error) - return; - - if (!parser->state) - svn_ra_serf__xml_push_state(parser, 0); - - /* ### get a real scratch_pool */ - scratch_pool = parser->state->pool; - - svn_ra_serf__define_ns(&parser->state->ns_list, attrs, parser->state->pool); - - svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name); - - err = parser->start(parser, name, attrs, scratch_pool); - if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) - err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - - parser->error = err; -} - - -/* Conforms to Expat's XML_EndElementHandler */ -static void -end_xml(void *userData, const char *raw_name) -{ - svn_ra_serf__xml_parser_t *parser = userData; - svn_ra_serf__dav_props_t name; - svn_error_t *err; - apr_pool_t *scratch_pool; - - if (parser->error) - return; - - /* ### get a real scratch_pool */ - scratch_pool = parser->state->pool; - - svn_ra_serf__expand_ns(&name, parser->state->ns_list, raw_name); - - err = parser->end(parser, name, scratch_pool); - if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) - err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - - parser->error = err; -} - - -/* Conforms to Expat's XML_CharacterDataHandler */ -static void -cdata_xml(void *userData, const char *data, int len) -{ - svn_ra_serf__xml_parser_t *parser = userData; - svn_error_t *err; - apr_pool_t *scratch_pool; - - if (parser->error) - return; - - if (!parser->state) - svn_ra_serf__xml_push_state(parser, 0); - - /* ### get a real scratch_pool */ - scratch_pool = parser->state->pool; - - err = parser->cdata(parser, data, len, scratch_pool); - if (err && !SERF_BUCKET_READ_ERROR(err->apr_err)) - err = svn_error_create(SVN_ERR_RA_SERF_WRAPPED_ERROR, err, NULL); - - parser->error = err; -} - -/* Flip the requisite bits in CTX to indicate that processing of the - response is complete, adding the current "done item" to the list of - completed items. */ -static void -add_done_item(svn_ra_serf__xml_parser_t *ctx) -{ - /* Make sure we don't add to DONE_LIST twice. */ - if (!*ctx->done) - { - *ctx->done = TRUE; - if (ctx->done_list) - { - ctx->done_item->data = ctx->user_data; - ctx->done_item->next = *ctx->done_list; - *ctx->done_list = ctx->done_item; - } - } -} - - -static svn_error_t * -write_to_pending(svn_ra_serf__xml_parser_t *ctx, - const char *data, - apr_size_t len, - apr_pool_t *scratch_pool) -{ - if (ctx->pending == NULL) - { - ctx->pending = apr_pcalloc(ctx->pool, sizeof(*ctx->pending)); - ctx->pending->buf = svn_spillbuf__create(PARSE_CHUNK_SIZE, - SPILL_SIZE, - ctx->pool); - } - - /* Copy the data into one or more chunks in the spill buffer. */ - return svn_error_trace(svn_spillbuf__write(ctx->pending->buf, - data, len, - scratch_pool)); -} - - -static svn_error_t * -inject_to_parser(svn_ra_serf__xml_parser_t *ctx, - const char *data, - apr_size_t len, - const serf_status_line *sl) -{ - int xml_status; - - xml_status = XML_Parse(ctx->xmlp, data, (int) len, 0); - - if (! ctx->ignore_errors) - { - SVN_ERR(ctx->error); - - if (xml_status != XML_STATUS_OK) - { - if (sl == NULL) - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("XML parsing failed")); - - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("XML parsing failed: (%d %s)"), - sl->code, sl->reason); - } - } - - return SVN_NO_ERROR; -} - -/* Apr pool cleanup handler to release an XML_Parser in success and error - conditions */ -static apr_status_t -xml_parser_cleanup(void *baton) -{ - XML_Parser *xmlp = baton; - - if (*xmlp) - { - (void) XML_ParserFree(*xmlp); - *xmlp = NULL; - } - - return APR_SUCCESS; -} - -/* Limit the amount of pending content to parse at once to < 100KB per - iteration. This number is chosen somewhat arbitrarely. Making it lower - will have a drastical negative impact on performance, whereas increasing it - increases the risk for connection timeouts. - */ -#define PENDING_TO_PARSE PARSE_CHUNK_SIZE * 5 - -svn_error_t * -svn_ra_serf__process_pending(svn_ra_serf__xml_parser_t *parser, - svn_boolean_t *network_eof, - apr_pool_t *scratch_pool) -{ - svn_boolean_t pending_empty = FALSE; - apr_size_t cur_read = 0; - - /* Fast path exit: already paused, nothing to do, or already done. */ - if (parser->paused || parser->pending == NULL || *parser->done) - { - *network_eof = parser->pending ? parser->pending->network_eof : FALSE; - return SVN_NO_ERROR; - } - - /* Parsing the pending conten in the spillbuf will result in many disc i/o - operations. This can be so slow that we don't run the network event - processing loop often enough, resulting in timed out connections. - - So we limit the amounts of bytes parsed per iteration. - */ - while (cur_read < PENDING_TO_PARSE) - { - const char *data; - apr_size_t len; - - /* Get a block of content, stopping the loop when we run out. */ - SVN_ERR(svn_spillbuf__read(&data, &len, parser->pending->buf, - scratch_pool)); - if (data) - { - /* Inject the content into the XML parser. */ - SVN_ERR(inject_to_parser(parser, data, len, NULL)); - - /* If the XML parsing callbacks paused us, then we're done for now. */ - if (parser->paused) - break; - - cur_read += len; - } - else - { - /* The buffer is empty. */ - pending_empty = TRUE; - break; - } - } - - /* If the PENDING structures are empty *and* we consumed all content from - the network, then we're completely done with the parsing. */ - if (pending_empty && - parser->pending->network_eof) - { - int xml_status; - SVN_ERR_ASSERT(parser->xmlp != NULL); - - /* Tell the parser that no more content will be parsed. */ - xml_status = XML_Parse(parser->xmlp, NULL, 0, 1); - - apr_pool_cleanup_run(parser->pool, &parser->xmlp, xml_parser_cleanup); - parser->xmlp = NULL; - - if (! parser->ignore_errors) - { - SVN_ERR(parser->error); - - if (xml_status != XML_STATUS_OK) - { - return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL, - _("XML parsing failed")); - } - } - - add_done_item(parser); - } - - *network_eof = parser->pending ? parser->pending->network_eof : FALSE; - - return SVN_NO_ERROR; -} -#undef PENDING_TO_PARSE - - -/* ### this is still broken conceptually. just shifting incrementally... */ -static svn_error_t * -handle_server_error(serf_request_t *request, - serf_bucket_t *response, - apr_pool_t *scratch_pool) -{ - svn_ra_serf__server_error_t server_err = { 0 }; - serf_bucket_t *hdrs; - const char *val; - apr_status_t err; - - hdrs = serf_bucket_response_get_headers(response); - val = serf_bucket_headers_get(hdrs, "Content-Type"); - if (val && strncasecmp(val, "text/xml", sizeof("text/xml") - 1) == 0) - { - /* ### we should figure out how to reuse begin_error_parsing */ - - server_err.error = svn_error_create(APR_SUCCESS, NULL, NULL); - server_err.contains_precondition_error = FALSE; - server_err.cdata = svn_stringbuf_create_empty(scratch_pool); - server_err.collect_cdata = FALSE; - server_err.parser.pool = server_err.error->pool; - server_err.parser.user_data = &server_err; - server_err.parser.start = start_error; - server_err.parser.end = end_error; - server_err.parser.cdata = cdata_error; - server_err.parser.done = &server_err.done; - server_err.parser.ignore_errors = TRUE; - - /* We don't care about any errors except for SERVER_ERR.ERROR */ - svn_error_clear(svn_ra_serf__handle_xml_parser(request, - response, - &server_err.parser, - scratch_pool)); - - /* ### checking DONE is silly. the above only parses whatever has - ### been received at the network interface. totally wrong. but - ### it is what we have for now (maintaining historical code), - ### until we fully migrate. */ - if (server_err.done && server_err.error->apr_err == APR_SUCCESS) - { - svn_error_clear(server_err.error); - server_err.error = SVN_NO_ERROR; - } - - return svn_error_trace(server_err.error); - } - - /* The only error that we will return is from the XML response body. - Otherwise, ignore the entire body but allow SUCCESS/EOF/EAGAIN to - surface. */ - err = drain_bucket(response); - if (err && !SERF_BUCKET_READ_ERROR(err)) - return svn_ra_serf__wrap_err(err, NULL); - - return SVN_NO_ERROR; -} - - -/* Implements svn_ra_serf__response_handler_t */ -svn_error_t * -svn_ra_serf__handle_xml_parser(serf_request_t *request, - serf_bucket_t *response, - void *baton, - apr_pool_t *pool) -{ - serf_status_line sl; - apr_status_t status; - svn_ra_serf__xml_parser_t *ctx = baton; - svn_error_t *err; - - /* ### get the HANDLER rather than fetching this. */ - status = serf_bucket_response_status(response, &sl); - if (SERF_BUCKET_READ_ERROR(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } - - /* Woo-hoo. Nothing here to see. */ - if (sl.code == 404 && !ctx->ignore_errors) - { - err = handle_server_error(request, response, pool); - - if (err && APR_STATUS_IS_EOF(err->apr_err)) - add_done_item(ctx); - - return svn_error_trace(err); - } - - if (!ctx->xmlp) - { - ctx->xmlp = XML_ParserCreate(NULL); - apr_pool_cleanup_register(ctx->pool, &ctx->xmlp, xml_parser_cleanup, - apr_pool_cleanup_null); - XML_SetUserData(ctx->xmlp, ctx); - XML_SetElementHandler(ctx->xmlp, start_xml, end_xml); - if (ctx->cdata) - { - XML_SetCharacterDataHandler(ctx->xmlp, cdata_xml); - } - } - - while (1) - { - const char *data; - apr_size_t len; - - status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); - if (SERF_BUCKET_READ_ERROR(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } - - /* Note: once the callbacks invoked by inject_to_parser() sets the - PAUSED flag, then it will not be cleared. write_to_pending() will - only save the content. Logic outside of serf_context_run() will - clear that flag, as appropriate, along with processing the - content that we have placed into the PENDING buffer. - - We want to save arriving content into the PENDING structures if - the parser has been paused, or we already have data in there (so - the arriving data is appended, rather than injected out of order) */ - if (ctx->paused || HAS_PENDING_DATA(ctx->pending)) - { - err = write_to_pending(ctx, data, len, pool); - } - else - { - err = inject_to_parser(ctx, data, len, &sl); - if (err) - { - /* Should have no errors if IGNORE_ERRORS is set. */ - SVN_ERR_ASSERT(!ctx->ignore_errors); - } - } - if (err) - { - SVN_ERR_ASSERT(ctx->xmlp != NULL); - - apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup); - add_done_item(ctx); - return svn_error_trace(err); - } - - if (APR_STATUS_IS_EAGAIN(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } - - if (APR_STATUS_IS_EOF(status)) - { - if (ctx->pending != NULL) - ctx->pending->network_eof = TRUE; - - /* We just hit the end of the network content. If we have nothing - in the PENDING structures, then we're completely done. */ - if (!HAS_PENDING_DATA(ctx->pending)) - { - int xml_status; - SVN_ERR_ASSERT(ctx->xmlp != NULL); - - xml_status = XML_Parse(ctx->xmlp, NULL, 0, 1); - - apr_pool_cleanup_run(ctx->pool, &ctx->xmlp, xml_parser_cleanup); - - if (! ctx->ignore_errors) - { - SVN_ERR(ctx->error); - - if (xml_status != XML_STATUS_OK) - { - return svn_error_create( - SVN_ERR_XML_MALFORMED, NULL, - _("The XML response contains invalid XML")); - } - } - - add_done_item(ctx); - } - - return svn_ra_serf__wrap_err(status, NULL); - } - - /* feed me! */ - } - /* not reached */ -} - - apr_status_t svn_ra_serf__credentials_callback(char **username, char **password, serf_request_t *request, void *baton, @@ -1927,7 +1159,7 @@ svn_ra_serf__credentials_callback(char **username, char **password, &session->auth_state, SVN_AUTH_CRED_SIMPLE, realm, - session->wc_callbacks->auth_baton, + session->auth_baton, session->pool); } else @@ -2008,6 +1240,8 @@ handle_response(serf_request_t *request, if (!response) { /* Uh-oh. Our connection died. */ + handler->scheduled = FALSE; + if (handler->response_error) { /* Give a handler chance to prevent request requeue. */ @@ -2126,10 +1360,7 @@ handle_response(serf_request_t *request, } handler->conn->last_status_code = handler->sline.code; - if (handler->sline.code == 405 - || handler->sline.code == 408 - || handler->sline.code == 409 - || handler->sline.code >= 500) + if (handler->sline.code >= 400) { /* 405 Method Not allowed. 408 Request Timeout @@ -2144,35 +1375,22 @@ handle_response(serf_request_t *request, { svn_ra_serf__server_error_t *server_err; - server_err = begin_error_parsing(start_error, end_error, cdata_error, - handler->handler_pool); - /* Get the parser to set our DONE flag. */ - server_err->parser.done = &handler->done; + SVN_ERR(svn_ra_serf__setup_error_parsing(&server_err, handler, + FALSE, + handler->handler_pool, + handler->handler_pool)); handler->server_error = server_err; } else { handler->discard_body = TRUE; - - if (!handler->session->pending_error) - { - apr_status_t apr_err = SVN_ERR_RA_DAV_REQUEST_FAILED; - - /* 405 == Method Not Allowed (Occurs when trying to lock a working - copy path which no longer exists at HEAD in the repository. */ - if (handler->sline.code == 405 - && strcmp(handler->method, "LOCK") == 0) - apr_err = SVN_ERR_FS_OUT_OF_DATE; - - handler->session->pending_error = - svn_error_createf(apr_err, NULL, - _("%s request on '%s' failed: %d %s"), - handler->method, handler->path, - handler->sline.code, handler->sline.reason); - } } } + else if (handler->sline.code <= 199) + { + handler->discard_body = TRUE; + } /* Stop processing the above, on every packet arrival. */ handler->reading_body = TRUE; @@ -2184,13 +1402,6 @@ handle_response(serf_request_t *request, { *serf_status = drain_bucket(response); - /* If the handler hasn't set done (which it shouldn't have) and - we now have the EOF, go ahead and set it so that we can stop - our context loops. - */ - if (!handler->done && APR_STATUS_IS_EOF(*serf_status)) - handler->done = TRUE; - return SVN_NO_ERROR; } @@ -2198,50 +1409,12 @@ handle_response(serf_request_t *request, that now. */ if (handler->server_error != NULL) { - err = svn_ra_serf__handle_xml_parser(request, response, - &handler->server_error->parser, - scratch_pool); - - /* If we do not receive an error or it is a non-transient error, return - immediately. - - APR_EOF will be returned when parsing is complete. - - APR_EAGAIN & WAIT_CONN may be intermittently returned as we proceed through - parsing and the network has no more data right now. If we receive that, - clear the error and return - allowing serf to wait for more data. - */ - if (!err || SERF_BUCKET_READ_ERROR(err->apr_err)) - return svn_error_trace(err); - - if (!APR_STATUS_IS_EOF(err->apr_err)) - { - *serf_status = err->apr_err; - svn_error_clear(err); - return SVN_NO_ERROR; - } - - /* Clear the EOF. We don't need it. */ - svn_error_clear(err); - - /* If the parsing is done, and we did not extract an error, then - simply toss everything, and anything else that might arrive. - The higher-level code will need to investigate HANDLER->SLINE, - as we have no further information for them. */ - if (handler->done - && handler->server_error->error->apr_err == APR_SUCCESS) - { - svn_error_clear(handler->server_error->error); - - /* Stop parsing for a server error. */ - handler->server_error = NULL; - - /* If anything arrives after this, then just discard it. */ - handler->discard_body = TRUE; - } - - *serf_status = APR_EOF; - return SVN_NO_ERROR; + return svn_error_trace( + svn_ra_serf__handle_server_error(handler->server_error, + handler, + request, response, + serf_status, + scratch_pool)); } /* Pass the body along to the registered response handler. */ @@ -2271,12 +1444,13 @@ static apr_status_t handle_response_cb(serf_request_t *request, serf_bucket_t *response, void *baton, - apr_pool_t *scratch_pool) + apr_pool_t *response_pool) { svn_ra_serf__handler_t *handler = baton; svn_error_t *err; apr_status_t inner_status; apr_status_t outer_status; + apr_pool_t *scratch_pool = response_pool; /* Scratch pool needed? */ err = svn_error_trace(handle_response(request, response, handler, &inner_status, @@ -2287,9 +1461,34 @@ handle_response_cb(serf_request_t *request, if (!outer_status) outer_status = inner_status; - /* Make sure the DONE flag is set properly. */ + /* Make sure the DONE flag is set properly and requests are cleaned up. */ if (APR_STATUS_IS_EOF(outer_status) || APR_STATUS_IS_EOF(inner_status)) - handler->done = TRUE; + { + svn_ra_serf__session_t *sess = handler->session; + handler->done = TRUE; + handler->scheduled = FALSE; + outer_status = APR_EOF; + + /* We use a cached handler->session here to allow handler to free the + memory containing the handler */ + save_error(sess, + handler->done_delegate(request, handler->done_delegate_baton, + scratch_pool)); + } + else if (SERF_BUCKET_READ_ERROR(outer_status) + && handler->session->pending_error) + { + handler->discard_body = TRUE; /* Discard further data */ + handler->done = TRUE; /* Mark as done */ + /* handler->scheduled is still TRUE, as we still expect data. + If we would return an error outer-status the connection + would have to be restarted. With scheduled still TRUE + destroying the handler's pool will still reset the + connection, avoiding the posibility of returning + an error for this handler when a new request is + scheduled. */ + outer_status = APR_EAGAIN; /* Exit context loop */ + } return outer_status; } @@ -2312,9 +1511,8 @@ setup_request(serf_request_t *request, { serf_bucket_alloc_t *bkt_alloc = serf_request_get_alloc(request); - /* ### should pass the scratch_pool */ SVN_ERR(handler->body_delegate(&body_bkt, handler->body_delegate_baton, - bkt_alloc, request_pool)); + bkt_alloc, request_pool, scratch_pool)); } else { @@ -2338,17 +1536,17 @@ setup_request(serf_request_t *request, SVN_ERR(setup_serf_req(request, req_bkt, &headers_bkt, handler->session, handler->method, handler->path, body_bkt, handler->body_type, accept_encoding, - request_pool, scratch_pool)); + !handler->no_dav_headers, request_pool, + scratch_pool)); if (handler->header_delegate) { - /* ### should pass the scratch_pool */ SVN_ERR(handler->header_delegate(headers_bkt, handler->header_delegate_baton, - request_pool)); + request_pool, scratch_pool)); } - return APR_SUCCESS; + return SVN_NO_ERROR; } /* Implements the serf_request_setup_t interface (which sets up both a @@ -2362,51 +1560,58 @@ setup_request_cb(serf_request_t *request, void **acceptor_baton, serf_response_handler_t *s_handler, void **s_handler_baton, - apr_pool_t *pool) + apr_pool_t *request_pool) { svn_ra_serf__handler_t *handler = setup_baton; + apr_pool_t *scratch_pool; svn_error_t *err; - /* ### construct a scratch_pool? serf gives us a pool that will live for - ### the duration of the request. */ - apr_pool_t *scratch_pool = pool; + /* Construct a scratch_pool? serf gives us a pool that will live for + the duration of the request. But requests are retried in some cases */ + scratch_pool = svn_pool_create(request_pool); if (strcmp(handler->method, "HEAD") == 0) *acceptor = accept_head; else *acceptor = accept_response; - *acceptor_baton = handler->session; + *acceptor_baton = handler; *s_handler = handle_response_cb; *s_handler_baton = handler; err = svn_error_trace(setup_request(request, handler, req_bkt, - pool /* request_pool */, scratch_pool)); + request_pool, scratch_pool)); + svn_pool_destroy(scratch_pool); return save_error(handler->session, err); } void svn_ra_serf__request_create(svn_ra_serf__handler_t *handler) { - SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL); - - /* In case HANDLER is re-queued, reset the various transient fields. + SVN_ERR_ASSERT_NO_RETURN(handler->handler_pool != NULL + && !handler->scheduled); - ### prior to recent changes, HANDLER was constant. maybe we should - ### break out these processing fields, apart from the request - ### definition. */ + /* In case HANDLER is re-queued, reset the various transient fields. */ handler->done = FALSE; handler->server_error = NULL; handler->sline.version = 0; handler->location = NULL; handler->reading_body = FALSE; handler->discard_body = FALSE; + handler->scheduled = TRUE; + + /* Keeping track of the returned request object would be nice, but doesn't + work the way we would expect in ra_serf.. + + Serf sometimes creates a new request for us (and destroys the old one) + without telling, like when authentication failed (401/407 response. - /* ### do we ever alter the >response_handler? */ + We 'just' trust serf to do the right thing and expect it to tell us + when the state of the request changes. - /* ### do we need to hold onto the returned request object, or just - ### not worry about it (the serf ctx will manage it). */ + ### I fixed a request leak in serf in r2258 on auth failures. + */ (void) serf_connection_request_create(handler->conn->conn, setup_request_cb, handler); } @@ -2415,8 +1620,7 @@ svn_ra_serf__request_create(svn_ra_serf__handler_t *handler) svn_error_t * svn_ra_serf__discover_vcc(const char **vcc_url, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, - apr_pool_t *pool) + apr_pool_t *scratch_pool) { const char *path; const char *relative_path; @@ -2429,12 +1633,6 @@ svn_ra_serf__discover_vcc(const char **vcc_url, return SVN_NO_ERROR; } - /* If no connection is provided, use the default one. */ - if (! conn) - { - conn = session->conns[0]; - } - path = session->session_url.path; *vcc_url = NULL; uuid = NULL; @@ -2444,9 +1642,10 @@ svn_ra_serf__discover_vcc(const char **vcc_url, apr_hash_t *props; svn_error_t *err; - err = svn_ra_serf__fetch_node_props(&props, conn, + err = svn_ra_serf__fetch_node_props(&props, session, path, SVN_INVALID_REVNUM, - base_props, pool, pool); + base_props, + scratch_pool, scratch_pool); if (! err) { apr_hash_t *ns_props; @@ -2474,12 +1673,7 @@ svn_ra_serf__discover_vcc(const char **vcc_url, svn_error_clear(err); /* Okay, strip off a component from PATH. */ - path = svn_urlpath__dirname(path, pool); - - /* An error occurred on conns. serf 0.4.0 remembers that - the connection had a problem. We need to reset it, in - order to use it again. */ - serf_connection_reset(conn->conn); + path = svn_urlpath__dirname(path, scratch_pool); } } } @@ -2505,7 +1699,7 @@ svn_ra_serf__discover_vcc(const char **vcc_url, { svn_stringbuf_t *url_buf; - url_buf = svn_stringbuf_create(path, pool); + url_buf = svn_stringbuf_create(path, scratch_pool); svn_path_remove_components(url_buf, svn_path_component_count(relative_path)); @@ -2533,7 +1727,6 @@ svn_error_t * svn_ra_serf__get_relative_path(const char **rel_path, const char *orig_path, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, apr_pool_t *pool) { const char *decoded_root, *decoded_orig; @@ -2550,7 +1743,6 @@ svn_ra_serf__get_relative_path(const char **rel_path, promises to populate the session's root-url cache, and that's what we really want. */ SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, - conn ? conn : session->conns[0], pool)); } @@ -2564,7 +1756,6 @@ svn_ra_serf__get_relative_path(const char **rel_path, svn_error_t * svn_ra_serf__report_resource(const char **report_target, svn_ra_serf__session_t *session, - svn_ra_serf__connection_t *conn, apr_pool_t *pool) { /* If we have HTTP v2 support, we want to report against the 'me' @@ -2574,7 +1765,7 @@ svn_ra_serf__report_resource(const char **report_target, /* Otherwise, we'll use the default VCC. */ else - SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, conn, pool)); + SVN_ERR(svn_ra_serf__discover_vcc(report_target, session, pool)); return SVN_NO_ERROR; } @@ -2588,13 +1779,14 @@ svn_ra_serf__error_on_status(serf_status_line sline, { case 301: case 302: + case 303: case 307: + case 308: return svn_error_createf(SVN_ERR_RA_DAV_RELOCATED, NULL, (sline.code == 301) - ? _("Repository moved permanently to '%s';" - " please relocate") - : _("Repository moved temporarily to '%s';" - " please relocate"), location); + ? _("Repository moved permanently to '%s'") + : _("Repository moved temporarily to '%s'"), + location); case 403: return svn_error_createf(SVN_ERR_RA_DAV_FORBIDDEN, NULL, _("Access to '%s' forbidden"), path); @@ -2602,6 +1794,16 @@ svn_ra_serf__error_on_status(serf_status_line sline, case 404: return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, _("'%s' path not found"), path); + case 405: + return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL, + _("HTTP method is not allowed on '%s'"), + path); + case 409: + return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, + _("'%s' conflicts"), path); + case 412: + return svn_error_createf(SVN_ERR_RA_DAV_PRECONDITION_FAILED, NULL, + _("Precondition on '%s' failed"), path); case 423: return svn_error_createf(SVN_ERR_FS_NO_LOCK_TOKEN, NULL, _("'%s': no lock token available"), path); @@ -2612,21 +1814,59 @@ svn_ra_serf__error_on_status(serf_status_line sline, "server or an intermediate proxy does not accept " "chunked encoding. Try setting 'http-chunked-requests' " "to 'auto' or 'no' in your client configuration.")); + case 500: + return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("Unexpected server error %d '%s' on '%s'"), + sline.code, sline.reason, path); case 501: return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("The requested feature is not supported by " "'%s'"), path); } - if (sline.code >= 300) + if (sline.code >= 300 || sline.code <= 199) return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, - _("Unexpected HTTP status %d '%s' on '%s'\n"), + _("Unexpected HTTP status %d '%s' on '%s'"), sline.code, sline.reason, path); return SVN_NO_ERROR; } svn_error_t * +svn_ra_serf__unexpected_status(svn_ra_serf__handler_t *handler) +{ + /* Is it a standard error status? */ + if (handler->sline.code != 405) + SVN_ERR(svn_ra_serf__error_on_status(handler->sline, + handler->path, + handler->location)); + + switch (handler->sline.code) + { + case 201: + return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("Path '%s' unexpectedly created"), + handler->path); + case 204: + return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, + _("Path '%s' already exists"), + handler->path); + + case 405: + return svn_error_createf(SVN_ERR_RA_DAV_METHOD_NOT_ALLOWED, NULL, + _("The HTTP method '%s' is not allowed" + " on '%s'"), + handler->method, handler->path); + default: + return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, + _("Unexpected HTTP status %d '%s' on '%s' " + "request to '%s'"), + handler->sline.code, handler->sline.reason, + handler->method, handler->path); + } +} + +svn_error_t * svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session, svn_delta_shim_callbacks_t *callbacks) { @@ -2636,185 +1876,102 @@ svn_ra_serf__register_editor_shim_callbacks(svn_ra_session_t *ra_session, return SVN_NO_ERROR; } - -/* Conforms to Expat's XML_StartElementHandler */ -static void -expat_start(void *userData, const char *raw_name, const char **attrs) -{ - struct expat_ctx_t *ectx = userData; - - if (ectx->inner_error != NULL) - return; - - ectx->inner_error = svn_error_trace( - svn_ra_serf__xml_cb_start(ectx->xmlctx, - raw_name, attrs)); - -#ifdef EXPAT_HAS_STOPPARSER - if (ectx->inner_error) - (void) XML_StopParser(ectx->parser, 0 /* resumable */); -#endif -} - - -/* Conforms to Expat's XML_EndElementHandler */ -static void -expat_end(void *userData, const char *raw_name) -{ - struct expat_ctx_t *ectx = userData; - - if (ectx->inner_error != NULL) - return; - - ectx->inner_error = svn_error_trace( - svn_ra_serf__xml_cb_end(ectx->xmlctx, raw_name)); - -#ifdef EXPAT_HAS_STOPPARSER - if (ectx->inner_error) - (void) XML_StopParser(ectx->parser, 0 /* resumable */); -#endif -} - - -/* Conforms to Expat's XML_CharacterDataHandler */ -static void -expat_cdata(void *userData, const char *data, int len) +/* Shared/standard done_delegate handler */ +static svn_error_t * +response_done(serf_request_t *request, + void *handler_baton, + apr_pool_t *scratch_pool) { - struct expat_ctx_t *ectx = userData; + svn_ra_serf__handler_t *handler = handler_baton; - if (ectx->inner_error != NULL) - return; - - ectx->inner_error = svn_error_trace( - svn_ra_serf__xml_cb_cdata(ectx->xmlctx, data, len)); - -#ifdef EXPAT_HAS_STOPPARSER - if (ectx->inner_error) - (void) XML_StopParser(ectx->parser, 0 /* resumable */); -#endif -} + assert(handler->done); + if (handler->no_fail_on_http_failure_status) + return SVN_NO_ERROR; -/* Implements svn_ra_serf__response_handler_t */ -static svn_error_t * -expat_response_handler(serf_request_t *request, - serf_bucket_t *response, - void *baton, - apr_pool_t *scratch_pool) -{ - struct expat_ctx_t *ectx = baton; + if (handler->server_error) + return svn_ra_serf__server_error_create(handler, scratch_pool); - if (!ectx->parser) + if (handler->sline.code >= 400 || handler->sline.code <= 199) { - ectx->parser = XML_ParserCreate(NULL); - apr_pool_cleanup_register(ectx->cleanup_pool, &ectx->parser, - xml_parser_cleanup, apr_pool_cleanup_null); - XML_SetUserData(ectx->parser, ectx); - XML_SetElementHandler(ectx->parser, expat_start, expat_end); - XML_SetCharacterDataHandler(ectx->parser, expat_cdata); + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); } - /* ### TODO: sline.code < 200 should really be handled by the core */ - if ((ectx->handler->sline.code < 200) || (ectx->handler->sline.code >= 300)) + if ((handler->sline.code >= 300 && handler->sline.code < 399) + && !handler->no_fail_on_http_redirect_status) { - /* By deferring to expect_empty_body(), it will make a choice on - how to handle the body. Whatever the decision, the core handler - will take over, and we will not be called again. */ - return svn_error_trace(svn_ra_serf__expect_empty_body( - request, response, ectx->handler, - scratch_pool)); + return svn_error_trace(svn_ra_serf__unexpected_status(handler)); } - while (1) - { - apr_status_t status; - const char *data; - apr_size_t len; - int expat_status; - - status = serf_bucket_read(response, PARSE_CHUNK_SIZE, &data, &len); - if (SERF_BUCKET_READ_ERROR(status)) - return svn_ra_serf__wrap_err(status, NULL); - -#if 0 - /* ### move restart/skip into the core handler */ - ectx->handler->read_size += len; -#endif - - /* ### move PAUSED behavior to a new response handler that can feed - ### an inner handler, or can pause for a while. */ - - /* ### should we have an IGNORE_ERRORS flag like the v1 parser? */ - - expat_status = XML_Parse(ectx->parser, data, (int)len, 0 /* isFinal */); - - /* We need to check INNER_ERROR first. This is an error from the - callbacks that has been "dropped off" for us to retrieve. On - current Expat parsers, we stop the parser when an error occurs, - so we want to ignore EXPAT_STATUS (which reports the stoppage). - - If an error is not present, THEN we go ahead and look for parsing - errors. */ - if (ectx->inner_error) - { - apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser, - xml_parser_cleanup); - return svn_error_trace(ectx->inner_error); - } - if (expat_status == XML_STATUS_ERROR) - return svn_error_createf(SVN_ERR_XML_MALFORMED, - ectx->inner_error, - _("The %s response contains invalid XML" - " (%d %s)"), - ectx->handler->method, - ectx->handler->sline.code, - ectx->handler->sline.reason); - - /* The parsing went fine. What has the bucket told us? */ - - if (APR_STATUS_IS_EOF(status)) - { - /* Tell expat we've reached the end of the content. Ignore the - return status. We just don't care. */ - (void) XML_Parse(ectx->parser, NULL, 0, 1 /* isFinal */); + return SVN_NO_ERROR; +} - svn_ra_serf__xml_context_destroy(ectx->xmlctx); - apr_pool_cleanup_run(ectx->cleanup_pool, &ectx->parser, - xml_parser_cleanup); +/* Pool cleanup handler for request handlers. - /* ### should check XMLCTX to see if it has returned to the - ### INITIAL state. we may have ended early... */ - } + If a serf context run stops for some outside error, like when the user + cancels a request via ^C in the context loop, the handler is still + registered in the serf context. With the pool cleanup there would be + handlers registered in no freed memory. - if (status && !SERF_BUCKET_READ_ERROR(status)) - { - return svn_ra_serf__wrap_err(status, NULL); - } + This fallback kills the connection for this case, which will make serf + unregister any outstanding requests on it. */ +static apr_status_t +handler_cleanup(void *baton) +{ + svn_ra_serf__handler_t *handler = baton; + if (handler->scheduled) + { + svn_ra_serf__unschedule_handler(handler); } - /* NOTREACHED */ + return APR_SUCCESS; } - svn_ra_serf__handler_t * -svn_ra_serf__create_expat_handler(svn_ra_serf__xml_context_t *xmlctx, - apr_pool_t *result_pool) +svn_ra_serf__create_handler(svn_ra_serf__session_t *session, + apr_pool_t *result_pool) { svn_ra_serf__handler_t *handler; - struct expat_ctx_t *ectx; - - ectx = apr_pcalloc(result_pool, sizeof(*ectx)); - ectx->xmlctx = xmlctx; - ectx->parser = NULL; - ectx->cleanup_pool = result_pool; - handler = apr_pcalloc(result_pool, sizeof(*handler)); handler->handler_pool = result_pool; - handler->response_handler = expat_response_handler; - handler->response_baton = ectx; - ectx->handler = handler; + apr_pool_cleanup_register(result_pool, handler, handler_cleanup, + apr_pool_cleanup_null); + + handler->session = session; + handler->conn = session->conns[0]; + + /* Setup the default done handler, to handle server errors */ + handler->done_delegate_baton = handler; + handler->done_delegate = response_done; return handler; } + +svn_error_t * +svn_ra_serf__uri_parse(apr_uri_t *uri, + const char *url_str, + apr_pool_t *result_pool) +{ + apr_status_t status; + + status = apr_uri_parse(result_pool, url_str, uri); + if (status) + { + /* Do not use returned error status in error message because currently + apr_uri_parse() returns APR_EGENERAL for all parsing errors. */ + return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, + _("Illegal URL '%s'"), + url_str); + } + + /* Depending the version of apr-util in use, for root paths uri.path + will be NULL or "", where serf requires "/". */ + if (uri->path == NULL || uri->path[0] == '\0') + { + uri->path = apr_pstrdup(result_pool, "/"); + } + + return SVN_NO_ERROR; +} |