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