]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/get_file.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_serf / get_file.c
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 \f
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 \f
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_boolean_t using_compression;
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 \f
81 /** Routines called when we are fetching a file */
82
83 static svn_error_t *
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->using_compression)
92     {
93       serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip");
94     }
95
96   return SVN_NO_ERROR;
97 }
98
99 static svn_error_t *
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 *
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 *
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 *
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 *
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 *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   struct file_prop_baton_t fb;
331
332   /* Fetch properties. */
333
334   fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool);
335
336   /* The simple case is if we want HEAD - then a GET on the fetch_url is fine.
337    *
338    * Otherwise, we need to get the baseline version for this particular
339    * revision and then fetch that file.
340    */
341   if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
342     {
343       SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev,
344                                           session,
345                                           fetch_url, revision,
346                                           pool, pool));
347       revision = SVN_INVALID_REVNUM;
348     }
349   /* REVISION is always SVN_INVALID_REVNUM  */
350   SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
351
352   if (props)
353       which_props = all_props;
354   else if (stream && session->wc_callbacks->get_wc_contents)
355       which_props = type_and_checksum_props;
356   else
357       which_props = check_path_props;
358
359   fb.result_pool = pool;
360   fb.props = props ? apr_hash_make(pool) : NULL;
361   fb.kind = svn_node_unknown;
362   fb.sha1_checksum = NULL;
363
364   SVN_ERR(svn_ra_serf__create_propfind_handler(&propfind_handler, session,
365                                                fetch_url, SVN_INVALID_REVNUM,
366                                                "0", which_props,
367                                                get_file_prop_cb, &fb,
368                                                pool));
369
370   SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, pool));
371
372   /* Verify that resource type is not collection. */
373   if (fb.kind != svn_node_file)
374     {
375       return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
376                               _("Can't get text contents of a directory"));
377     }
378
379   if (props)
380     *props = fb.props;
381
382   if (stream)
383     {
384       svn_boolean_t found;
385       SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream, pool));
386
387       /* No contents found in the WC, let's fetch from server. */
388       if (!found)
389         {
390           stream_ctx_t *stream_ctx;
391           svn_ra_serf__handler_t *handler;
392
393           /* Create the fetch context. */
394           stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx));
395           stream_ctx->result_stream = stream;
396           stream_ctx->using_compression = session->using_compression;
397
398           handler = svn_ra_serf__create_handler(session, pool);
399
400           handler->method = "GET";
401           handler->path = fetch_url;
402
403           handler->custom_accept_encoding = TRUE;
404           handler->no_dav_headers = TRUE;
405
406           handler->header_delegate = headers_fetch;
407           handler->header_delegate_baton = stream_ctx;
408
409           handler->response_handler = handle_stream;
410           handler->response_baton = stream_ctx;
411
412           handler->response_error = cancel_fetch;
413           handler->response_error_baton = stream_ctx;
414
415           stream_ctx->handler = handler;
416
417           SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
418
419           if (handler->sline.code != 200)
420             return svn_error_trace(svn_ra_serf__unexpected_status(handler));
421         }
422     }
423
424   return SVN_NO_ERROR;
425 }