/* * get_file.c : entry point for update RA functions for ra_serf * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ #define APR_WANT_STRFUNC #include #include #include #include #include "svn_private_config.h" #include "svn_hash.h" #include "svn_pools.h" #include "svn_ra.h" #include "svn_delta.h" #include "svn_path.h" #include "svn_props.h" #include "private/svn_dep_compat.h" #include "private/svn_string_private.h" #include "ra_serf.h" #include "../libsvn_ra/ra_loader.h" /* * This structure represents a single request to GET (fetch) a file with * its associated Serf session/connection. */ typedef struct stream_ctx_t { /* The handler representing this particular fetch. */ svn_ra_serf__handler_t *handler; /* Have we read our response headers yet? */ svn_boolean_t read_headers; svn_boolean_t using_compression; /* This flag is set when our response is aborted before we reach the * end and we decide to requeue this request. */ svn_boolean_t aborted_read; apr_off_t aborted_read_size; /* This is the amount of data that we have read so far. */ apr_off_t read_size; /* If we're writing this file to a stream, this will be non-NULL. */ svn_stream_t *result_stream; } stream_ctx_t; /** Routines called when we are fetching a file */ static svn_error_t * headers_fetch(serf_bucket_t *headers, void *baton, apr_pool_t *pool /* request pool */, apr_pool_t *scratch_pool) { stream_ctx_t *fetch_ctx = baton; if (fetch_ctx->using_compression) { serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip"); } return SVN_NO_ERROR; } static svn_error_t * cancel_fetch(serf_request_t *request, serf_bucket_t *response, int status_code, void *baton) { stream_ctx_t *fetch_ctx = baton; /* Uh-oh. Our connection died on us. * * The core ra_serf layer will requeue our request - we just need to note * that we got cut off in the middle of our song. */ if (!response) { /* If we already started the fetch and opened the file handle, we need * to hold subsequent read() ops until we get back to where we were * before the close and we can then resume the textdelta() calls. */ if (fetch_ctx->read_headers) { if (!fetch_ctx->aborted_read && fetch_ctx->read_size) { fetch_ctx->aborted_read = TRUE; fetch_ctx->aborted_read_size = fetch_ctx->read_size; } fetch_ctx->read_size = 0; } return SVN_NO_ERROR; } /* We have no idea what went wrong. */ SVN_ERR_MALFUNCTION(); } /* Helper svn_ra_serf__get_file(). Attempts to fetch file contents * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is * present in PROPS. * * Sets *FOUND_P to TRUE if file contents was successfuly fetched. * * Performs all temporary allocations in POOL. */ static svn_error_t * try_get_wc_contents(svn_boolean_t *found_p, svn_ra_serf__session_t *session, const char *sha1_checksum_prop, svn_stream_t *dst_stream, apr_pool_t *pool) { svn_checksum_t *checksum; svn_stream_t *wc_stream; svn_error_t *err; /* No contents found by default. */ *found_p = FALSE; if (!session->wc_callbacks->get_wc_contents || sha1_checksum_prop == NULL) { /* Nothing to do. */ return SVN_NO_ERROR; } SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, sha1_checksum_prop, pool)); err = session->wc_callbacks->get_wc_contents( session->wc_callback_baton, &wc_stream, checksum, pool); if (err) { svn_error_clear(err); /* Ignore errors for now. */ return SVN_NO_ERROR; } if (wc_stream) { SVN_ERR(svn_stream_copy3(wc_stream, svn_stream_disown(dst_stream, pool), NULL, NULL, pool)); *found_p = TRUE; } return SVN_NO_ERROR; } /* ----------------------------------------------------------------------- svn_ra_get_file() specific */ /* Implements svn_ra_serf__response_handler_t */ static svn_error_t * handle_stream(serf_request_t *request, serf_bucket_t *response, void *handler_baton, apr_pool_t *pool) { stream_ctx_t *fetch_ctx = handler_baton; apr_status_t status; if (fetch_ctx->handler->sline.code != 200) return svn_error_trace(svn_ra_serf__unexpected_status(fetch_ctx->handler)); while (1) { const char *data; apr_size_t len; status = serf_bucket_read(response, 8000, &data, &len); if (SERF_BUCKET_READ_ERROR(status)) { return svn_ra_serf__wrap_err(status, NULL); } fetch_ctx->read_size += len; if (fetch_ctx->aborted_read) { apr_off_t skip; /* We haven't caught up to where we were before. */ if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) { /* Eek. What did the file shrink or something? */ if (APR_STATUS_IS_EOF(status)) { SVN_ERR_MALFUNCTION(); } /* Skip on to the next iteration of this loop. */ if (APR_STATUS_IS_EAGAIN(status)) { return svn_ra_serf__wrap_err(status, NULL); } continue; } /* Woo-hoo. We're back. */ fetch_ctx->aborted_read = FALSE; /* Increment data and len by the difference. */ skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size); data += skip; len -= (apr_size_t)skip; } if (len) { apr_size_t written_len; written_len = len; SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, &written_len)); } if (status) { return svn_ra_serf__wrap_err(status, NULL); } } /* not reached */ } /* Baton for get_file_prop_cb */ struct file_prop_baton_t { apr_pool_t *result_pool; svn_node_kind_t kind; apr_hash_t *props; const char *sha1_checksum; }; /* Implements svn_ra_serf__prop_func_t for svn_ra_serf__get_file */ static svn_error_t * get_file_prop_cb(void *baton, const char *path, const char *ns, const char *name, const svn_string_t *value, apr_pool_t *scratch_pool) { struct file_prop_baton_t *fb = baton; const char *svn_name; if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) { const char *val = value->data; if (strcmp(val, "collection") == 0) fb->kind = svn_node_dir; else fb->kind = svn_node_file; return SVN_NO_ERROR; } else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0 && strcmp(name, "sha1-checksum") == 0) { fb->sha1_checksum = apr_pstrdup(fb->result_pool, value->data); } if (!fb->props) return SVN_NO_ERROR; svn_name = svn_ra_serf__svnname_from_wirename(ns, name, fb->result_pool); if (svn_name) { svn_hash_sets(fb->props, svn_name, svn_string_dup(value, fb->result_pool)); } return SVN_NO_ERROR; } svn_error_t * svn_ra_serf__get_file(svn_ra_session_t *ra_session, const char *path, svn_revnum_t revision, svn_stream_t *stream, svn_revnum_t *fetched_rev, apr_hash_t **props, apr_pool_t *pool) { svn_ra_serf__session_t *session = ra_session->priv; const char *fetch_url; const svn_ra_serf__dav_props_t *which_props; svn_ra_serf__handler_t *propfind_handler; struct file_prop_baton_t fb; /* Fetch properties. */ fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool); /* The simple case is if we want HEAD - then a GET on the fetch_url is fine. * * Otherwise, we need to get the baseline version for this particular * revision and then fetch that file. */ if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) { SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev, session, fetch_url, revision, pool, pool)); revision = SVN_INVALID_REVNUM; } /* REVISION is always SVN_INVALID_REVNUM */ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); if (props) which_props = all_props; else if (stream && session->wc_callbacks->get_wc_contents) which_props = type_and_checksum_props; else which_props = check_path_props; fb.result_pool = pool; fb.props = props ? apr_hash_make(pool) : NULL; fb.kind = svn_node_unknown; fb.sha1_checksum = NULL; SVN_ERR(svn_ra_serf__create_propfind_handler(&propfind_handler, session, fetch_url, SVN_INVALID_REVNUM, "0", which_props, get_file_prop_cb, &fb, pool)); SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, pool)); /* Verify that resource type is not collection. */ if (fb.kind != svn_node_file) { return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, _("Can't get text contents of a directory")); } if (props) *props = fb.props; if (stream) { svn_boolean_t found; SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream, pool)); /* No contents found in the WC, let's fetch from server. */ if (!found) { stream_ctx_t *stream_ctx; svn_ra_serf__handler_t *handler; /* Create the fetch context. */ stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx)); stream_ctx->result_stream = stream; stream_ctx->using_compression = session->using_compression; handler = svn_ra_serf__create_handler(session, pool); handler->method = "GET"; handler->path = fetch_url; handler->custom_accept_encoding = TRUE; handler->no_dav_headers = TRUE; handler->header_delegate = headers_fetch; handler->header_delegate_baton = stream_ctx; handler->response_handler = handle_stream; handler->response_baton = stream_ctx; handler->response_error = cancel_fetch; handler->response_error_baton = stream_ctx; stream_ctx->handler = handler; SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); if (handler->sline.code != 200) return svn_error_trace(svn_ra_serf__unexpected_status(handler)); } } return SVN_NO_ERROR; }