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(). */ 52svn_error_t * 53svn_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() */ 121struct 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 */ 133static svn_error_t * 134fill_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 213static const svn_ra_serf__dav_props_t * 214get_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(). */ 230svn_error_t * 231svn_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 */ 306struct 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 */ 317static svn_error_t * 318get_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 */ 368static svn_error_t * 369get_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(). */ 406svn_error_t * 407svn_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