1 /*
2 * stat.c : file and directory stat and read functions
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_want.h>
28
29 #include <serf.h>
30
31 #include "svn_private_config.h"
32 #include "svn_pools.h"
33 #include "svn_xml.h"
34 #include "../libsvn_ra/ra_loader.h"
35 #include "svn_config.h"
36 #include "svn_dirent_uri.h"
37 #include "svn_hash.h"
38 #include "svn_path.h"
39 #include "svn_props.h"
40 #include "svn_time.h"
41 #include "svn_version.h"
42
43 #include "private/svn_dav_protocol.h"
44 #include "private/svn_dep_compat.h"
45 #include "private/svn_fspath.h"
46
47 #include "ra_serf.h"
48
49
50
51 /* Implements svn_ra__vtable_t.check_path(). */
52 svn_error_t *
svn_ra_serf__check_path(svn_ra_session_t * ra_session,const char * relpath,svn_revnum_t revision,svn_node_kind_t * kind,apr_pool_t * scratch_pool)53 svn_ra_serf__check_path(svn_ra_session_t *ra_session,
54 const char *relpath,
55 svn_revnum_t revision,
56 svn_node_kind_t *kind,
57 apr_pool_t *scratch_pool)
58 {
59 svn_ra_serf__session_t *session = ra_session->priv;
60 apr_hash_t *props;
61 svn_error_t *err;
62 const char *url;
63
64 url = session->session_url.path;
65
66 /* If we have a relative path, append it. */
67 if (relpath)
68 url = svn_path_url_add_component2(url, relpath, scratch_pool);
69
70 /* If we were given a specific revision, get a URL that refers to that
71 specific revision (rather than floating with HEAD). */
72 if (SVN_IS_VALID_REVNUM(revision))
73 {
74 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
75 session,
76 url, revision,
77 scratch_pool, scratch_pool));
78 }
79
80 /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
81 Or we started with SVN_INVALID_REVNUM and URL may be floating. */
82 err = svn_ra_serf__fetch_node_props(&props, session,
83 url, SVN_INVALID_REVNUM,
84 check_path_props,
85 scratch_pool, scratch_pool);
86
87 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
88 {
89 svn_error_clear(err);
90 *kind = svn_node_none;
91 }
92 else
93 {
94 apr_hash_t *dav_props;
95 const char *res_type;
96
97 /* Any other error, raise to caller. */
98 SVN_ERR(err);
99
100 dav_props = apr_hash_get(props, "DAV:", 4);
101 res_type = svn_prop_get_value(dav_props, "resourcetype");
102 if (!res_type)
103 {
104 /* How did this happen? */
105 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
106 _("The PROPFIND response did not include the "
107 "requested resourcetype value"));
108 }
109
110 if (strcmp(res_type, "collection") == 0)
111 *kind = svn_node_dir;
112 else
113 *kind = svn_node_file;
114 }
115
116 return SVN_NO_ERROR;
117 }
118
119
120 /* Baton for fill_dirent_propfunc() */
121 struct fill_dirent_baton_t
122 {
123 /* Update the fields in this entry. */
124 svn_dirent_t *entry;
125
126 svn_tristate_t *supports_deadprop_count;
127
128 /* If allocations are necessary, then use this pool. */
129 apr_pool_t *result_pool;
130 };
131
132 /* Implements svn_ra_serf__prop_func_t */
133 static svn_error_t *
fill_dirent_propfunc(void * baton,const char * path,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * scratch_pool)134 fill_dirent_propfunc(void *baton,
135 const char *path,
136 const char *ns,
137 const char *name,
138 const svn_string_t *val,
139 apr_pool_t *scratch_pool)
140 {
141 struct fill_dirent_baton_t *fdb = baton;
142
143 if (strcmp(ns, "DAV:") == 0)
144 {
145 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
146 {
147 apr_int64_t rev;
148 SVN_ERR(svn_cstring_atoi64(&rev, val->data));
149
150 fdb->entry->created_rev = (svn_revnum_t)rev;
151 }
152 else if (strcmp(name, "creator-displayname") == 0)
153 {
154 fdb->entry->last_author = apr_pstrdup(fdb->result_pool, val->data);
155 }
156 else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
157 {
158 SVN_ERR(svn_time_from_cstring(&fdb->entry->time,
159 val->data,
160 fdb->result_pool));
161 }
162 else if (strcmp(name, "getcontentlength") == 0)
163 {
164 /* 'getcontentlength' property is empty for directories. */
165 if (val->len)
166 {
167 SVN_ERR(svn_cstring_atoi64(&fdb->entry->size, val->data));
168 }
169 }
170 else if (strcmp(name, "resourcetype") == 0)
171 {
172 if (strcmp(val->data, "collection") == 0)
173 {
174 fdb->entry->kind = svn_node_dir;
175 }
176 else
177 {
178 fdb->entry->kind = svn_node_file;
179 }
180 }
181 }
182 else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
183 {
184 fdb->entry->has_props = TRUE;
185 }
186 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
187 {
188 fdb->entry->has_props = TRUE;
189 }
190 else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
191 {
192 if(strcmp(name, "deadprop-count") == 0)
193 {
194 if (*val->data)
195 {
196 /* Note: 1.8.x and earlier servers send the count proper; 1.9.0
197 * and newer send "1" if there are properties and "0" otherwise.
198 */
199 apr_int64_t deadprop_count;
200 SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
201 fdb->entry->has_props = deadprop_count > 0;
202 if (fdb->supports_deadprop_count)
203 *fdb->supports_deadprop_count = svn_tristate_true;
204 }
205 else if (fdb->supports_deadprop_count)
206 *fdb->supports_deadprop_count = svn_tristate_false;
207 }
208 }
209
210 return SVN_NO_ERROR;
211 }
212
213 static const svn_ra_serf__dav_props_t *
get_dirent_props(apr_uint32_t dirent_fields,svn_ra_serf__session_t * session,apr_pool_t * pool)214 get_dirent_props(apr_uint32_t dirent_fields,
215 svn_ra_serf__session_t *session,
216 apr_pool_t *pool)
217 {
218 svn_ra_serf__dav_props_t *prop;
219 apr_array_header_t *props = svn_ra_serf__get_dirent_props(dirent_fields,
220 session, pool);
221
222 prop = apr_array_push(props);
223 prop->xmlns = NULL;
224 prop->name = NULL;
225
226 return (svn_ra_serf__dav_props_t *) props->elts;
227 }
228
229 /* Implements svn_ra__vtable_t.stat(). */
230 svn_error_t *
svn_ra_serf__stat(svn_ra_session_t * ra_session,const char * relpath,svn_revnum_t revision,svn_dirent_t ** dirent,apr_pool_t * pool)231 svn_ra_serf__stat(svn_ra_session_t *ra_session,
232 const char *relpath,
233 svn_revnum_t revision,
234 svn_dirent_t **dirent,
235 apr_pool_t *pool)
236 {
237 svn_ra_serf__session_t *session = ra_session->priv;
238 svn_error_t *err;
239 struct fill_dirent_baton_t fdb;
240 svn_tristate_t deadprop_count = svn_tristate_unknown;
241 svn_ra_serf__handler_t *handler;
242 const char *url;
243
244 url = session->session_url.path;
245
246 /* If we have a relative path, append it. */
247 if (relpath)
248 url = svn_path_url_add_component2(url, relpath, pool);
249
250 /* If we were given a specific revision, get a URL that refers to that
251 specific revision (rather than floating with HEAD). */
252 if (SVN_IS_VALID_REVNUM(revision))
253 {
254 SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
255 session,
256 url, revision,
257 pool, pool));
258 }
259
260 fdb.entry = svn_dirent_create(pool);
261 fdb.supports_deadprop_count = &deadprop_count;
262 fdb.result_pool = pool;
263
264 SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, url,
265 SVN_INVALID_REVNUM, "0",
266 get_dirent_props(SVN_DIRENT_ALL,
267 session,
268 pool),
269 fill_dirent_propfunc, &fdb, pool));
270
271 err = svn_ra_serf__context_run_one(handler, pool);
272
273 if (err)
274 {
275 if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
276 {
277 svn_error_clear(err);
278 *dirent = NULL;
279 return SVN_NO_ERROR;
280 }
281 else
282 return svn_error_trace(err);
283 }
284
285 if (deadprop_count == svn_tristate_false
286 && session->supports_deadprop_count == svn_tristate_unknown
287 && !fdb.entry->has_props)
288 {
289 /* We have to requery as the server didn't give us the right
290 information */
291 session->supports_deadprop_count = svn_tristate_false;
292
293 /* Run the same handler again */
294 SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
295 }
296
297 if (deadprop_count != svn_tristate_unknown)
298 session->supports_deadprop_count = deadprop_count;
299
300 *dirent = fdb.entry;
301
302 return SVN_NO_ERROR;
303 }
304
305 /* Baton for get_dir_dirents_cb and get_dir_props_cb */
306 struct get_dir_baton_t
307 {
308 apr_pool_t *result_pool;
309 apr_hash_t *dirents;
310 apr_hash_t *ret_props;
311 svn_boolean_t is_directory;
312 svn_tristate_t supports_deadprop_count;
313 const char *path;
314 };
315
316 /* Implements svn_ra_serf__prop_func_t */
317 static svn_error_t *
get_dir_dirents_cb(void * baton,const char * path,const char * ns,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)318 get_dir_dirents_cb(void *baton,
319 const char *path,
320 const char *ns,
321 const char *name,
322 const svn_string_t *value,
323 apr_pool_t *scratch_pool)
324 {
325 struct get_dir_baton_t *db = baton;
326 const char *relpath;
327
328 relpath = svn_fspath__skip_ancestor(db->path, path);
329
330 if (relpath && relpath[0] != '\0')
331 {
332 struct fill_dirent_baton_t fdb;
333
334 relpath = svn_path_uri_decode(relpath, scratch_pool);
335 fdb.entry = svn_hash_gets(db->dirents, relpath);
336
337 if (!fdb.entry)
338 {
339 fdb.entry = svn_dirent_create(db->result_pool);
340 svn_hash_sets(db->dirents,
341 apr_pstrdup(db->result_pool, relpath),
342 fdb.entry);
343 }
344
345 fdb.result_pool = db->result_pool;
346 fdb.supports_deadprop_count = &db->supports_deadprop_count;
347 SVN_ERR(fill_dirent_propfunc(&fdb, path, ns, name, value, scratch_pool));
348 }
349 else if (relpath && !db->is_directory)
350 {
351 if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
352 {
353 if (strcmp(value->data, "collection") != 0)
354 {
355 /* Tell a lie to exit early */
356 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
357 _("Can't get properties of non-directory"));
358 }
359 else
360 db->is_directory = TRUE;
361 }
362 }
363
364 return SVN_NO_ERROR;
365 }
366
367 /* Implements svn_ra_serf__prop_func */
368 static svn_error_t *
get_dir_props_cb(void * baton,const char * path,const char * ns,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)369 get_dir_props_cb(void *baton,
370 const char *path,
371 const char *ns,
372 const char *name,
373 const svn_string_t *value,
374 apr_pool_t *scratch_pool)
375 {
376 struct get_dir_baton_t *db = baton;
377 const char *propname;
378
379 propname = svn_ra_serf__svnname_from_wirename(ns, name, db->result_pool);
380 if (propname)
381 {
382 svn_hash_sets(db->ret_props, propname,
383 svn_string_dup(value, db->result_pool));
384 return SVN_NO_ERROR;
385 }
386
387 if (!db->is_directory)
388 {
389 if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
390 {
391 if (strcmp(value->data, "collection") != 0)
392 {
393 /* Tell a lie to exit early */
394 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
395 _("Can't get properties of non-directory"));
396 }
397 else
398 db->is_directory = TRUE;
399 }
400 }
401
402 return SVN_NO_ERROR;
403 }
404
405 /* Implements svn_ra__vtable_t.get_dir(). */
406 svn_error_t *
svn_ra_serf__get_dir(svn_ra_session_t * ra_session,apr_hash_t ** dirents,svn_revnum_t * fetched_rev,apr_hash_t ** ret_props,const char * rel_path,svn_revnum_t revision,apr_uint32_t dirent_fields,apr_pool_t * result_pool)407 svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
408 apr_hash_t **dirents,
409 svn_revnum_t *fetched_rev,
410 apr_hash_t **ret_props,
411 const char *rel_path,
412 svn_revnum_t revision,
413 apr_uint32_t dirent_fields,
414 apr_pool_t *result_pool)
415 {
416 svn_ra_serf__session_t *session = ra_session->priv;
417 apr_pool_t *scratch_pool = svn_pool_create(result_pool);
418 svn_ra_serf__handler_t *dirent_handler = NULL;
419 svn_ra_serf__handler_t *props_handler = NULL;
420 const char *path;
421 struct get_dir_baton_t gdb;
422 svn_error_t *err = SVN_NO_ERROR;
423
424 gdb.result_pool = result_pool;
425 gdb.is_directory = FALSE;
426 gdb.supports_deadprop_count = svn_tristate_unknown;
427
428 path = session->session_url.path;
429
430 /* If we have a relative path, URI encode and append it. */
431 if (rel_path)
432 {
433 path = svn_path_url_add_component2(path, rel_path, scratch_pool);
434 }
435
436 /* If the user specified a peg revision other than HEAD, we have to fetch
437 the baseline collection url for that revision. If not, we can use the
438 public url. */
439 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
440 {
441 SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
442 session,
443 path, revision,
444 scratch_pool, scratch_pool));
445 revision = SVN_INVALID_REVNUM;
446 }
447 /* REVISION is always SVN_INVALID_REVNUM */
448 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
449
450 gdb.path = path;
451
452 /* If we're asked for children, fetch them now. */
453 if (dirents)
454 {
455 /* Always request node kind to check that path is really a
456 * directory. */
457 if (!ret_props)
458 dirent_fields |= SVN_DIRENT_KIND;
459
460 gdb.dirents = apr_hash_make(result_pool);
461
462 SVN_ERR(svn_ra_serf__create_propfind_handler(
463 &dirent_handler, session,
464 path, SVN_INVALID_REVNUM, "1",
465 get_dirent_props(dirent_fields,
466 session,
467 scratch_pool),
468 get_dir_dirents_cb, &gdb,
469 scratch_pool));
470
471 svn_ra_serf__request_create(dirent_handler);
472 }
473 else
474 gdb.dirents = NULL;
475
476 if (ret_props)
477 {
478 gdb.ret_props = apr_hash_make(result_pool);
479 SVN_ERR(svn_ra_serf__create_propfind_handler(
480 &props_handler, session,
481 path, SVN_INVALID_REVNUM, "0",
482 all_props,
483 get_dir_props_cb, &gdb,
484 scratch_pool));
485
486 svn_ra_serf__request_create(props_handler);
487 }
488 else
489 gdb.ret_props = NULL;
490
491 if (dirent_handler)
492 {
493 err = svn_error_trace(
494 svn_ra_serf__context_run_wait(&dirent_handler->done,
495 session,
496 scratch_pool));
497
498 if (err)
499 {
500 svn_pool_clear(scratch_pool); /* Unregisters outstanding requests */
501 return err;
502 }
503
504 if (gdb.supports_deadprop_count == svn_tristate_false
505 && session->supports_deadprop_count == svn_tristate_unknown
506 && dirent_fields & SVN_DIRENT_HAS_PROPS)
507 {
508 /* We have to requery as the server didn't give us the right
509 information */
510 session->supports_deadprop_count = svn_tristate_false;
511
512 apr_hash_clear(gdb.dirents);
513
514 SVN_ERR(svn_ra_serf__create_propfind_handler(
515 &dirent_handler, session,
516 path, SVN_INVALID_REVNUM, "1",
517 get_dirent_props(dirent_fields,
518 session,
519 scratch_pool),
520 get_dir_dirents_cb, &gdb,
521 scratch_pool));
522
523 svn_ra_serf__request_create(dirent_handler);
524 }
525 }
526
527 if (props_handler)
528 {
529 err = svn_error_trace(
530 svn_ra_serf__context_run_wait(&props_handler->done,
531 session,
532 scratch_pool));
533 }
534
535 /* And dirent again for the case when we had to send the request again */
536 if (! err && dirent_handler)
537 {
538 err = svn_error_trace(
539 svn_ra_serf__context_run_wait(&dirent_handler->done,
540 session,
541 scratch_pool));
542 }
543
544 if (!err && gdb.supports_deadprop_count != svn_tristate_unknown)
545 session->supports_deadprop_count = gdb.supports_deadprop_count;
546
547 svn_pool_destroy(scratch_pool); /* Unregisters outstanding requests */
548
549 SVN_ERR(err);
550
551 if (!gdb.is_directory)
552 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
553 _("Can't get entries of non-directory"));
554
555 if (ret_props)
556 *ret_props = gdb.ret_props;
557
558 if (dirents)
559 *dirents = gdb.dirents;
560
561 return SVN_NO_ERROR;
562 }
563