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