1 /*
2  * get_file.c :  entry point for update RA functions for ra_serf
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 
25 
26 #define APR_WANT_STRFUNC
27 #include <apr_version.h>
28 #include <apr_want.h>
29 
30 #include <apr_uri.h>
31 
32 #include <serf.h>
33 
34 #include "svn_private_config.h"
35 #include "svn_hash.h"
36 #include "svn_pools.h"
37 #include "svn_ra.h"
38 #include "svn_delta.h"
39 #include "svn_path.h"
40 #include "svn_props.h"
41 
42 #include "private/svn_dep_compat.h"
43 #include "private/svn_string_private.h"
44 
45 #include "ra_serf.h"
46 #include "../libsvn_ra/ra_loader.h"
47 
48 
49 
50 
51 /*
52  * This structure represents a single request to GET (fetch) a file with
53  * its associated Serf session/connection.
54  */
55 typedef struct stream_ctx_t {
56 
57   /* The handler representing this particular fetch.  */
58   svn_ra_serf__handler_t *handler;
59 
60   /* Have we read our response headers yet? */
61   svn_boolean_t read_headers;
62 
63   svn_ra_serf__session_t *session;
64 
65   /* This flag is set when our response is aborted before we reach the
66    * end and we decide to requeue this request.
67    */
68   svn_boolean_t aborted_read;
69   apr_off_t aborted_read_size;
70 
71   /* This is the amount of data that we have read so far. */
72   apr_off_t read_size;
73 
74   /* If we're writing this file to a stream, this will be non-NULL. */
75   svn_stream_t *result_stream;
76 
77 } stream_ctx_t;
78 
79 
80 
81 /** Routines called when we are fetching a file */
82 
83 static svn_error_t *
headers_fetch(serf_bucket_t * headers,void * baton,apr_pool_t * pool,apr_pool_t * scratch_pool)84 headers_fetch(serf_bucket_t *headers,
85               void *baton,
86               apr_pool_t *pool /* request pool */,
87               apr_pool_t *scratch_pool)
88 {
89   stream_ctx_t *fetch_ctx = baton;
90 
91   if (fetch_ctx->session->using_compression != svn_tristate_false)
92     {
93       serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
94     }
95 
96   return SVN_NO_ERROR;
97 }
98 
99 static svn_error_t *
cancel_fetch(serf_request_t * request,serf_bucket_t * response,int status_code,void * baton)100 cancel_fetch(serf_request_t *request,
101              serf_bucket_t *response,
102              int status_code,
103              void *baton)
104 {
105   stream_ctx_t *fetch_ctx = baton;
106 
107   /* Uh-oh.  Our connection died on us.
108    *
109    * The core ra_serf layer will requeue our request - we just need to note
110    * that we got cut off in the middle of our song.
111    */
112   if (!response)
113     {
114       /* If we already started the fetch and opened the file handle, we need
115        * to hold subsequent read() ops until we get back to where we were
116        * before the close and we can then resume the textdelta() calls.
117        */
118       if (fetch_ctx->read_headers)
119         {
120           if (!fetch_ctx->aborted_read && fetch_ctx->read_size)
121             {
122               fetch_ctx->aborted_read = TRUE;
123               fetch_ctx->aborted_read_size = fetch_ctx->read_size;
124             }
125           fetch_ctx->read_size = 0;
126         }
127 
128       return SVN_NO_ERROR;
129     }
130 
131   /* We have no idea what went wrong. */
132   SVN_ERR_MALFUNCTION();
133 }
134 
135 
136 /* Helper svn_ra_serf__get_file(). Attempts to fetch file contents
137  * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is
138  * present in PROPS.
139  *
140  * Sets *FOUND_P to TRUE if file contents was successfuly fetched.
141  *
142  * Performs all temporary allocations in POOL.
143  */
144 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)145 try_get_wc_contents(svn_boolean_t *found_p,
146                     svn_ra_serf__session_t *session,
147                     const char *sha1_checksum_prop,
148                     svn_stream_t *dst_stream,
149                     apr_pool_t *pool)
150 {
151   svn_checksum_t *checksum;
152   svn_stream_t *wc_stream;
153   svn_error_t *err;
154 
155   /* No contents found by default. */
156   *found_p = FALSE;
157 
158   if (!session->wc_callbacks->get_wc_contents
159       || sha1_checksum_prop == NULL)
160     {
161       /* Nothing to do. */
162       return SVN_NO_ERROR;
163     }
164 
165   SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
166                                  sha1_checksum_prop, pool));
167 
168   err = session->wc_callbacks->get_wc_contents(
169           session->wc_callback_baton, &wc_stream, checksum, pool);
170 
171   if (err)
172     {
173       svn_error_clear(err);
174 
175       /* Ignore errors for now. */
176       return SVN_NO_ERROR;
177     }
178 
179   if (wc_stream)
180     {
181         SVN_ERR(svn_stream_copy3(wc_stream,
182                                  svn_stream_disown(dst_stream, pool),
183                                  NULL, NULL, pool));
184       *found_p = TRUE;
185     }
186 
187   return SVN_NO_ERROR;
188 }
189 
190 /* -----------------------------------------------------------------------
191    svn_ra_get_file() specific */
192 
193 /* Implements svn_ra_serf__response_handler_t */
194 static svn_error_t *
handle_stream(serf_request_t * request,serf_bucket_t * response,void * handler_baton,apr_pool_t * pool)195 handle_stream(serf_request_t *request,
196               serf_bucket_t *response,
197               void *handler_baton,
198               apr_pool_t *pool)
199 {
200   stream_ctx_t *fetch_ctx = handler_baton;
201   apr_status_t status;
202 
203   if (fetch_ctx->handler->sline.code != 200)
204     return svn_error_trace(svn_ra_serf__unexpected_status(fetch_ctx->handler));
205 
206   while (1)
207     {
208       const char *data;
209       apr_size_t len;
210 
211       status = serf_bucket_read(response, 8000, &data, &len);
212       if (SERF_BUCKET_READ_ERROR(status))
213         {
214           return svn_ra_serf__wrap_err(status, NULL);
215         }
216 
217       fetch_ctx->read_size += len;
218 
219       if (fetch_ctx->aborted_read)
220         {
221           apr_off_t skip;
222 
223           /* We haven't caught up to where we were before. */
224           if (fetch_ctx->read_size < fetch_ctx->aborted_read_size)
225             {
226               /* Eek.  What did the file shrink or something? */
227               if (APR_STATUS_IS_EOF(status))
228                 {
229                   SVN_ERR_MALFUNCTION();
230                 }
231 
232               /* Skip on to the next iteration of this loop. */
233               if (APR_STATUS_IS_EAGAIN(status))
234                 {
235                   return svn_ra_serf__wrap_err(status, NULL);
236                 }
237               continue;
238             }
239 
240           /* Woo-hoo.  We're back. */
241           fetch_ctx->aborted_read = FALSE;
242 
243           /* Increment data and len by the difference. */
244           skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size);
245           data += skip;
246           len -= (apr_size_t)skip;
247         }
248 
249       if (len)
250         {
251           apr_size_t written_len;
252 
253           written_len = len;
254 
255           SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data,
256                                    &written_len));
257         }
258 
259       if (status)
260         {
261           return svn_ra_serf__wrap_err(status, NULL);
262         }
263     }
264   /* not reached */
265 }
266 
267 /* Baton for get_file_prop_cb */
268 struct file_prop_baton_t
269 {
270   apr_pool_t *result_pool;
271   svn_node_kind_t kind;
272   apr_hash_t *props;
273   const char *sha1_checksum;
274 };
275 
276 /* Implements svn_ra_serf__prop_func_t for svn_ra_serf__get_file */
277 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)278 get_file_prop_cb(void *baton,
279                  const char *path,
280                  const char *ns,
281                  const char *name,
282                  const svn_string_t *value,
283                  apr_pool_t *scratch_pool)
284 {
285   struct file_prop_baton_t *fb = baton;
286   const char *svn_name;
287 
288   if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
289     {
290       const char *val = value->data;
291 
292       if (strcmp(val, "collection") == 0)
293         fb->kind = svn_node_dir;
294       else
295         fb->kind = svn_node_file;
296 
297       return SVN_NO_ERROR;
298     }
299   else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0
300            && strcmp(name, "sha1-checksum") == 0)
301     {
302       fb->sha1_checksum = apr_pstrdup(fb->result_pool, value->data);
303     }
304 
305   if (!fb->props)
306     return SVN_NO_ERROR;
307 
308   svn_name = svn_ra_serf__svnname_from_wirename(ns, name, fb->result_pool);
309   if (svn_name)
310     {
311       svn_hash_sets(fb->props, svn_name,
312                     svn_string_dup(value, fb->result_pool));
313     }
314   return SVN_NO_ERROR;
315 }
316 
317 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 * result_pool)318 svn_ra_serf__get_file(svn_ra_session_t *ra_session,
319                       const char *path,
320                       svn_revnum_t revision,
321                       svn_stream_t *stream,
322                       svn_revnum_t *fetched_rev,
323                       apr_hash_t **props,
324                       apr_pool_t *result_pool)
325 {
326   svn_ra_serf__session_t *session = ra_session->priv;
327   const char *fetch_url;
328   const svn_ra_serf__dav_props_t *which_props;
329   svn_ra_serf__handler_t *propfind_handler;
330   apr_pool_t *scratch_pool = svn_pool_create(result_pool);
331   struct file_prop_baton_t fb;
332 
333   /* Fetch properties. */
334 
335   fetch_url = svn_path_url_add_component2(session->session_url.path, path,
336                                           scratch_pool);
337 
338   /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
339    *
340    * Otherwise, we need to get the baseline version for this particular
341    * revision and then fetch that file.
342    */
343   if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
344     {
345       SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
346                                           session,
347                                           fetch_url, revision,
348                                           scratch_pool, scratch_pool));
349       revision = SVN_INVALID_REVNUM;
350     }
351   /* REVISION is always SVN_INVALID_REVNUM  */
352   SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
353 
354   if (props)
355       which_props = all_props;
356   else if (stream && session->wc_callbacks->get_wc_contents)
357       which_props = type_and_checksum_props;
358   else
359       which_props = check_path_props;
360 
361   fb.result_pool = result_pool;
362   fb.props = props ? apr_hash_make(result_pool) : NULL;
363   fb.kind = svn_node_unknown;
364   fb.sha1_checksum = NULL;
365 
366   SVN_ERR(svn_ra_serf__create_propfind_handler(&propfind_handler, session,
367                                                fetch_url, SVN_INVALID_REVNUM,
368                                                "0", which_props,
369                                                get_file_prop_cb, &fb,
370                                                scratch_pool));
371 
372   SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, scratch_pool));
373 
374   /* Verify that resource type is not collection. */
375   if (fb.kind != svn_node_file)
376     {
377       return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
378                               _("Can't get text contents of a directory"));
379     }
380 
381   if (props)
382     *props = fb.props;
383 
384   if (stream)
385     {
386       svn_boolean_t found;
387       SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream,
388                                   scratch_pool));
389 
390       /* No contents found in the WC, let's fetch from server. */
391       if (!found)
392         {
393           stream_ctx_t *stream_ctx;
394           svn_ra_serf__handler_t *handler;
395 
396           /* Create the fetch context. */
397           stream_ctx = apr_pcalloc(scratch_pool, sizeof(*stream_ctx));
398           stream_ctx->result_stream = stream;
399           stream_ctx->session = session;
400 
401           handler = svn_ra_serf__create_handler(session, scratch_pool);
402 
403           handler->method = "GET";
404           handler->path = fetch_url;
405 
406           handler->custom_accept_encoding = TRUE;
407           handler->no_dav_headers = TRUE;
408 
409           handler->header_delegate = headers_fetch;
410           handler->header_delegate_baton = stream_ctx;
411 
412           handler->response_handler = handle_stream;
413           handler->response_baton = stream_ctx;
414 
415           handler->response_error = cancel_fetch;
416           handler->response_error_baton = stream_ctx;
417 
418           stream_ctx->handler = handler;
419 
420           SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
421 
422           if (handler->sline.code != 200)
423             return svn_error_trace(svn_ra_serf__unexpected_status(handler));
424         }
425     }
426 
427   svn_pool_destroy(scratch_pool);
428 
429   return SVN_NO_ERROR;
430 }
431