1289177Speter/* 2289177Speter * get_file.c : entry point for update RA functions for ra_serf 3289177Speter * 4289177Speter * ==================================================================== 5289177Speter * Licensed to the Apache Software Foundation (ASF) under one 6289177Speter * or more contributor license agreements. See the NOTICE file 7289177Speter * distributed with this work for additional information 8289177Speter * regarding copyright ownership. The ASF licenses this file 9289177Speter * to you under the Apache License, Version 2.0 (the 10289177Speter * "License"); you may not use this file except in compliance 11289177Speter * with the License. You may obtain a copy of the License at 12289177Speter * 13289177Speter * http://www.apache.org/licenses/LICENSE-2.0 14289177Speter * 15289177Speter * Unless required by applicable law or agreed to in writing, 16289177Speter * software distributed under the License is distributed on an 17289177Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18289177Speter * KIND, either express or implied. See the License for the 19289177Speter * specific language governing permissions and limitations 20289177Speter * under the License. 21289177Speter * ==================================================================== 22289177Speter */ 23289177Speter 24289177Speter 25289177Speter 26289177Speter#define APR_WANT_STRFUNC 27289177Speter#include <apr_version.h> 28289177Speter#include <apr_want.h> 29289177Speter 30289177Speter#include <apr_uri.h> 31289177Speter 32289177Speter#include <serf.h> 33289177Speter 34289177Speter#include "svn_private_config.h" 35289177Speter#include "svn_hash.h" 36289177Speter#include "svn_pools.h" 37289177Speter#include "svn_ra.h" 38289177Speter#include "svn_delta.h" 39289177Speter#include "svn_path.h" 40289177Speter#include "svn_props.h" 41289177Speter 42289177Speter#include "private/svn_dep_compat.h" 43289177Speter#include "private/svn_string_private.h" 44289177Speter 45289177Speter#include "ra_serf.h" 46289177Speter#include "../libsvn_ra/ra_loader.h" 47289177Speter 48289177Speter 49289177Speter 50289177Speter 51289177Speter/* 52289177Speter * This structure represents a single request to GET (fetch) a file with 53289177Speter * its associated Serf session/connection. 54289177Speter */ 55289177Spetertypedef struct stream_ctx_t { 56289177Speter 57289177Speter /* The handler representing this particular fetch. */ 58289177Speter svn_ra_serf__handler_t *handler; 59289177Speter 60289177Speter /* Have we read our response headers yet? */ 61289177Speter svn_boolean_t read_headers; 62289177Speter 63289177Speter svn_boolean_t using_compression; 64289177Speter 65289177Speter /* This flag is set when our response is aborted before we reach the 66289177Speter * end and we decide to requeue this request. 67289177Speter */ 68289177Speter svn_boolean_t aborted_read; 69289177Speter apr_off_t aborted_read_size; 70289177Speter 71289177Speter /* This is the amount of data that we have read so far. */ 72289177Speter apr_off_t read_size; 73289177Speter 74289177Speter /* If we're writing this file to a stream, this will be non-NULL. */ 75289177Speter svn_stream_t *result_stream; 76289177Speter 77289177Speter} stream_ctx_t; 78289177Speter 79289177Speter 80289177Speter 81289177Speter/** Routines called when we are fetching a file */ 82289177Speter 83289177Speterstatic svn_error_t * 84289177Speterheaders_fetch(serf_bucket_t *headers, 85289177Speter void *baton, 86289177Speter apr_pool_t *pool /* request pool */, 87289177Speter apr_pool_t *scratch_pool) 88289177Speter{ 89289177Speter stream_ctx_t *fetch_ctx = baton; 90289177Speter 91289177Speter if (fetch_ctx->using_compression) 92289177Speter { 93289177Speter serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip"); 94289177Speter } 95289177Speter 96289177Speter return SVN_NO_ERROR; 97289177Speter} 98289177Speter 99289177Speterstatic svn_error_t * 100289177Spetercancel_fetch(serf_request_t *request, 101289177Speter serf_bucket_t *response, 102289177Speter int status_code, 103289177Speter void *baton) 104289177Speter{ 105289177Speter stream_ctx_t *fetch_ctx = baton; 106289177Speter 107289177Speter /* Uh-oh. Our connection died on us. 108289177Speter * 109289177Speter * The core ra_serf layer will requeue our request - we just need to note 110289177Speter * that we got cut off in the middle of our song. 111289177Speter */ 112289177Speter if (!response) 113289177Speter { 114289177Speter /* If we already started the fetch and opened the file handle, we need 115289177Speter * to hold subsequent read() ops until we get back to where we were 116289177Speter * before the close and we can then resume the textdelta() calls. 117289177Speter */ 118289177Speter if (fetch_ctx->read_headers) 119289177Speter { 120289177Speter if (!fetch_ctx->aborted_read && fetch_ctx->read_size) 121289177Speter { 122289177Speter fetch_ctx->aborted_read = TRUE; 123289177Speter fetch_ctx->aborted_read_size = fetch_ctx->read_size; 124289177Speter } 125289177Speter fetch_ctx->read_size = 0; 126289177Speter } 127289177Speter 128289177Speter return SVN_NO_ERROR; 129289177Speter } 130289177Speter 131289177Speter /* We have no idea what went wrong. */ 132289177Speter SVN_ERR_MALFUNCTION(); 133289177Speter} 134289177Speter 135289177Speter 136289177Speter/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents 137289177Speter * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is 138289177Speter * present in PROPS. 139289177Speter * 140289177Speter * Sets *FOUND_P to TRUE if file contents was successfuly fetched. 141289177Speter * 142289177Speter * Performs all temporary allocations in POOL. 143289177Speter */ 144289177Speterstatic svn_error_t * 145289177Spetertry_get_wc_contents(svn_boolean_t *found_p, 146289177Speter svn_ra_serf__session_t *session, 147289177Speter const char *sha1_checksum_prop, 148289177Speter svn_stream_t *dst_stream, 149289177Speter apr_pool_t *pool) 150289177Speter{ 151289177Speter svn_checksum_t *checksum; 152289177Speter svn_stream_t *wc_stream; 153289177Speter svn_error_t *err; 154289177Speter 155289177Speter /* No contents found by default. */ 156289177Speter *found_p = FALSE; 157289177Speter 158289177Speter if (!session->wc_callbacks->get_wc_contents 159289177Speter || sha1_checksum_prop == NULL) 160289177Speter { 161289177Speter /* Nothing to do. */ 162289177Speter return SVN_NO_ERROR; 163289177Speter } 164289177Speter 165289177Speter SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, 166289177Speter sha1_checksum_prop, pool)); 167289177Speter 168289177Speter err = session->wc_callbacks->get_wc_contents( 169289177Speter session->wc_callback_baton, &wc_stream, checksum, pool); 170289177Speter 171289177Speter if (err) 172289177Speter { 173289177Speter svn_error_clear(err); 174289177Speter 175289177Speter /* Ignore errors for now. */ 176289177Speter return SVN_NO_ERROR; 177289177Speter } 178289177Speter 179289177Speter if (wc_stream) 180289177Speter { 181289177Speter SVN_ERR(svn_stream_copy3(wc_stream, 182289177Speter svn_stream_disown(dst_stream, pool), 183289177Speter NULL, NULL, pool)); 184289177Speter *found_p = TRUE; 185289177Speter } 186289177Speter 187289177Speter return SVN_NO_ERROR; 188289177Speter} 189289177Speter 190289177Speter/* ----------------------------------------------------------------------- 191289177Speter svn_ra_get_file() specific */ 192289177Speter 193289177Speter/* Implements svn_ra_serf__response_handler_t */ 194289177Speterstatic svn_error_t * 195289177Speterhandle_stream(serf_request_t *request, 196289177Speter serf_bucket_t *response, 197289177Speter void *handler_baton, 198289177Speter apr_pool_t *pool) 199289177Speter{ 200289177Speter stream_ctx_t *fetch_ctx = handler_baton; 201289177Speter apr_status_t status; 202289177Speter 203289177Speter if (fetch_ctx->handler->sline.code != 200) 204289177Speter return svn_error_trace(svn_ra_serf__unexpected_status(fetch_ctx->handler)); 205289177Speter 206289177Speter while (1) 207289177Speter { 208289177Speter const char *data; 209289177Speter apr_size_t len; 210289177Speter 211289177Speter status = serf_bucket_read(response, 8000, &data, &len); 212289177Speter if (SERF_BUCKET_READ_ERROR(status)) 213289177Speter { 214289177Speter return svn_ra_serf__wrap_err(status, NULL); 215289177Speter } 216289177Speter 217289177Speter fetch_ctx->read_size += len; 218289177Speter 219289177Speter if (fetch_ctx->aborted_read) 220289177Speter { 221289177Speter apr_off_t skip; 222289177Speter 223289177Speter /* We haven't caught up to where we were before. */ 224289177Speter if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) 225289177Speter { 226289177Speter /* Eek. What did the file shrink or something? */ 227289177Speter if (APR_STATUS_IS_EOF(status)) 228289177Speter { 229289177Speter SVN_ERR_MALFUNCTION(); 230289177Speter } 231289177Speter 232289177Speter /* Skip on to the next iteration of this loop. */ 233289177Speter if (APR_STATUS_IS_EAGAIN(status)) 234289177Speter { 235289177Speter return svn_ra_serf__wrap_err(status, NULL); 236289177Speter } 237289177Speter continue; 238289177Speter } 239289177Speter 240289177Speter /* Woo-hoo. We're back. */ 241289177Speter fetch_ctx->aborted_read = FALSE; 242289177Speter 243289177Speter /* Increment data and len by the difference. */ 244289177Speter skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size); 245289177Speter data += skip; 246289177Speter len -= (apr_size_t)skip; 247289177Speter } 248289177Speter 249289177Speter if (len) 250289177Speter { 251289177Speter apr_size_t written_len; 252289177Speter 253289177Speter written_len = len; 254289177Speter 255289177Speter SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, 256289177Speter &written_len)); 257289177Speter } 258289177Speter 259289177Speter if (status) 260289177Speter { 261289177Speter return svn_ra_serf__wrap_err(status, NULL); 262289177Speter } 263289177Speter } 264289177Speter /* not reached */ 265289177Speter} 266289177Speter 267289177Speter/* Baton for get_file_prop_cb */ 268289177Speterstruct file_prop_baton_t 269289177Speter{ 270289177Speter apr_pool_t *result_pool; 271289177Speter svn_node_kind_t kind; 272289177Speter apr_hash_t *props; 273289177Speter const char *sha1_checksum; 274289177Speter}; 275289177Speter 276289177Speter/* Implements svn_ra_serf__prop_func_t for svn_ra_serf__get_file */ 277289177Speterstatic svn_error_t * 278289177Speterget_file_prop_cb(void *baton, 279289177Speter const char *path, 280289177Speter const char *ns, 281289177Speter const char *name, 282289177Speter const svn_string_t *value, 283289177Speter apr_pool_t *scratch_pool) 284289177Speter{ 285289177Speter struct file_prop_baton_t *fb = baton; 286289177Speter const char *svn_name; 287289177Speter 288289177Speter if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) 289289177Speter { 290289177Speter const char *val = value->data; 291289177Speter 292289177Speter if (strcmp(val, "collection") == 0) 293289177Speter fb->kind = svn_node_dir; 294289177Speter else 295289177Speter fb->kind = svn_node_file; 296289177Speter 297289177Speter return SVN_NO_ERROR; 298289177Speter } 299289177Speter else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0 300289177Speter && strcmp(name, "sha1-checksum") == 0) 301289177Speter { 302289177Speter fb->sha1_checksum = apr_pstrdup(fb->result_pool, value->data); 303289177Speter } 304289177Speter 305289177Speter if (!fb->props) 306289177Speter return SVN_NO_ERROR; 307289177Speter 308289177Speter svn_name = svn_ra_serf__svnname_from_wirename(ns, name, fb->result_pool); 309289177Speter if (svn_name) 310289177Speter { 311289177Speter svn_hash_sets(fb->props, svn_name, 312289177Speter svn_string_dup(value, fb->result_pool)); 313289177Speter } 314289177Speter return SVN_NO_ERROR; 315289177Speter} 316289177Speter 317289177Spetersvn_error_t * 318289177Spetersvn_ra_serf__get_file(svn_ra_session_t *ra_session, 319289177Speter const char *path, 320289177Speter svn_revnum_t revision, 321289177Speter svn_stream_t *stream, 322289177Speter svn_revnum_t *fetched_rev, 323289177Speter apr_hash_t **props, 324289177Speter apr_pool_t *pool) 325289177Speter{ 326289177Speter svn_ra_serf__session_t *session = ra_session->priv; 327289177Speter const char *fetch_url; 328289177Speter const svn_ra_serf__dav_props_t *which_props; 329289177Speter svn_ra_serf__handler_t *propfind_handler; 330289177Speter struct file_prop_baton_t fb; 331289177Speter 332289177Speter /* Fetch properties. */ 333289177Speter 334289177Speter fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool); 335289177Speter 336289177Speter /* The simple case is if we want HEAD - then a GET on the fetch_url is fine. 337289177Speter * 338289177Speter * Otherwise, we need to get the baseline version for this particular 339289177Speter * revision and then fetch that file. 340289177Speter */ 341289177Speter if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) 342289177Speter { 343289177Speter SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev, 344289177Speter session, 345289177Speter fetch_url, revision, 346289177Speter pool, pool)); 347289177Speter revision = SVN_INVALID_REVNUM; 348289177Speter } 349289177Speter /* REVISION is always SVN_INVALID_REVNUM */ 350289177Speter SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); 351289177Speter 352289177Speter if (props) 353289177Speter which_props = all_props; 354289177Speter else if (stream && session->wc_callbacks->get_wc_contents) 355289177Speter which_props = type_and_checksum_props; 356289177Speter else 357289177Speter which_props = check_path_props; 358289177Speter 359289177Speter fb.result_pool = pool; 360289177Speter fb.props = props ? apr_hash_make(pool) : NULL; 361289177Speter fb.kind = svn_node_unknown; 362289177Speter fb.sha1_checksum = NULL; 363289177Speter 364289177Speter SVN_ERR(svn_ra_serf__create_propfind_handler(&propfind_handler, session, 365289177Speter fetch_url, SVN_INVALID_REVNUM, 366289177Speter "0", which_props, 367289177Speter get_file_prop_cb, &fb, 368289177Speter pool)); 369289177Speter 370289177Speter SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, pool)); 371289177Speter 372289177Speter /* Verify that resource type is not collection. */ 373289177Speter if (fb.kind != svn_node_file) 374289177Speter { 375289177Speter return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, 376289177Speter _("Can't get text contents of a directory")); 377289177Speter } 378289177Speter 379289177Speter if (props) 380289177Speter *props = fb.props; 381289177Speter 382289177Speter if (stream) 383289177Speter { 384289177Speter svn_boolean_t found; 385289177Speter SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream, pool)); 386289177Speter 387289177Speter /* No contents found in the WC, let's fetch from server. */ 388289177Speter if (!found) 389289177Speter { 390289177Speter stream_ctx_t *stream_ctx; 391289177Speter svn_ra_serf__handler_t *handler; 392289177Speter 393289177Speter /* Create the fetch context. */ 394289177Speter stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx)); 395289177Speter stream_ctx->result_stream = stream; 396289177Speter stream_ctx->using_compression = session->using_compression; 397289177Speter 398289177Speter handler = svn_ra_serf__create_handler(session, pool); 399289177Speter 400289177Speter handler->method = "GET"; 401289177Speter handler->path = fetch_url; 402289177Speter 403289177Speter handler->custom_accept_encoding = TRUE; 404289177Speter handler->no_dav_headers = TRUE; 405289177Speter 406289177Speter handler->header_delegate = headers_fetch; 407289177Speter handler->header_delegate_baton = stream_ctx; 408289177Speter 409289177Speter handler->response_handler = handle_stream; 410289177Speter handler->response_baton = stream_ctx; 411289177Speter 412289177Speter handler->response_error = cancel_fetch; 413289177Speter handler->response_error_baton = stream_ctx; 414289177Speter 415289177Speter stream_ctx->handler = handler; 416289177Speter 417289177Speter SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); 418289177Speter 419289177Speter if (handler->sline.code != 200) 420289177Speter return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 421289177Speter } 422289177Speter } 423289177Speter 424289177Speter return SVN_NO_ERROR; 425289177Speter} 426