1251881Speter/* 2251881Speter * property.c : property routines for ra_serf 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#include <serf.h> 27251881Speter 28251881Speter#include "svn_hash.h" 29251881Speter#include "svn_path.h" 30251881Speter#include "svn_base64.h" 31251881Speter#include "svn_xml.h" 32251881Speter#include "svn_props.h" 33251881Speter#include "svn_dirent_uri.h" 34251881Speter 35251881Speter#include "private/svn_dav_protocol.h" 36251881Speter#include "private/svn_fspath.h" 37251881Speter#include "private/svn_string_private.h" 38251881Speter#include "svn_private_config.h" 39251881Speter 40251881Speter#include "ra_serf.h" 41251881Speter 42251881Speter 43251881Speter/* Our current parsing state we're in for the PROPFIND response. */ 44251881Spetertypedef enum prop_state_e { 45289180Speter INITIAL = XML_STATE_INITIAL, 46251881Speter MULTISTATUS, 47251881Speter RESPONSE, 48251881Speter HREF, 49251881Speter PROPSTAT, 50251881Speter STATUS, 51251881Speter PROP, 52251881Speter PROPVAL, 53251881Speter COLLECTION, 54251881Speter HREF_VALUE 55251881Speter} prop_state_e; 56251881Speter 57251881Speter 58251881Speter/* 59251881Speter * This structure represents a pending PROPFIND response. 60251881Speter */ 61251881Spetertypedef struct propfind_context_t { 62251881Speter svn_ra_serf__handler_t *handler; 63251881Speter 64251881Speter /* the requested path */ 65251881Speter const char *path; 66251881Speter 67289180Speter /* the requested version (in string form) */ 68251881Speter const char *label; 69251881Speter 70251881Speter /* the request depth */ 71251881Speter const char *depth; 72251881Speter 73251881Speter /* the list of requested properties */ 74251881Speter const svn_ra_serf__dav_props_t *find_props; 75251881Speter 76289180Speter svn_ra_serf__prop_func_t prop_func; 77289180Speter void *prop_func_baton; 78251881Speter 79251881Speter /* hash table containing all the properties associated with the 80251881Speter * "current" <propstat> tag. These will get copied into RET_PROPS 81251881Speter * if the status code similarly associated indicates that they are 82251881Speter * "good"; otherwise, they'll get discarded. 83251881Speter */ 84251881Speter apr_hash_t *ps_props; 85251881Speter} propfind_context_t; 86251881Speter 87251881Speter 88251881Speter#define D_ "DAV:" 89251881Speter#define S_ SVN_XML_NAMESPACE 90251881Speterstatic const svn_ra_serf__xml_transition_t propfind_ttable[] = { 91251881Speter { INITIAL, D_, "multistatus", MULTISTATUS, 92251881Speter FALSE, { NULL }, TRUE }, 93251881Speter 94251881Speter { MULTISTATUS, D_, "response", RESPONSE, 95251881Speter FALSE, { NULL }, FALSE }, 96251881Speter 97251881Speter { RESPONSE, D_, "href", HREF, 98251881Speter TRUE, { NULL }, TRUE }, 99251881Speter 100251881Speter { RESPONSE, D_, "propstat", PROPSTAT, 101251881Speter FALSE, { NULL }, TRUE }, 102251881Speter 103251881Speter { PROPSTAT, D_, "status", STATUS, 104251881Speter TRUE, { NULL }, TRUE }, 105251881Speter 106251881Speter { PROPSTAT, D_, "prop", PROP, 107251881Speter FALSE, { NULL }, FALSE }, 108251881Speter 109251881Speter { PROP, "*", "*", PROPVAL, 110251881Speter TRUE, { "?V:encoding", NULL }, TRUE }, 111251881Speter 112251881Speter { PROPVAL, D_, "collection", COLLECTION, 113251881Speter FALSE, { NULL }, TRUE }, 114251881Speter 115251881Speter { PROPVAL, D_, "href", HREF_VALUE, 116251881Speter TRUE, { NULL }, TRUE }, 117251881Speter 118251881Speter { 0 } 119251881Speter}; 120251881Speter 121289180Speterstatic const int propfind_expected_status[] = { 122289180Speter 207, 123289180Speter 0 124289180Speter}; 125251881Speter 126251881Speter/* Return the HTTP status code contained in STATUS_LINE, or 0 if 127251881Speter there's a problem parsing it. */ 128289180Speterstatic apr_int64_t parse_status_code(const char *status_line) 129251881Speter{ 130251881Speter /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */ 131251881Speter if (status_line[0] == 'H' && 132251881Speter status_line[1] == 'T' && 133251881Speter status_line[2] == 'T' && 134251881Speter status_line[3] == 'P' && 135251881Speter status_line[4] == '/' && 136251881Speter (status_line[5] >= '0' && status_line[5] <= '9') && 137251881Speter status_line[6] == '.' && 138251881Speter (status_line[7] >= '0' && status_line[7] <= '9') && 139251881Speter status_line[8] == ' ') 140251881Speter { 141251881Speter char *reason; 142251881Speter 143251881Speter return apr_strtoi64(status_line + 8, &reason, 10); 144251881Speter } 145251881Speter return 0; 146251881Speter} 147251881Speter 148251881Speter/* Conforms to svn_ra_serf__xml_opened_t */ 149251881Speterstatic svn_error_t * 150251881Speterpropfind_opened(svn_ra_serf__xml_estate_t *xes, 151251881Speter void *baton, 152251881Speter int entered_state, 153251881Speter const svn_ra_serf__dav_props_t *tag, 154251881Speter apr_pool_t *scratch_pool) 155251881Speter{ 156251881Speter propfind_context_t *ctx = baton; 157251881Speter 158251881Speter if (entered_state == PROPVAL) 159251881Speter { 160289180Speter svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->xmlns); 161251881Speter svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name); 162251881Speter } 163251881Speter else if (entered_state == PROPSTAT) 164251881Speter { 165289180Speter ctx->ps_props = apr_hash_make(svn_ra_serf__xml_state_pool(xes)); 166251881Speter } 167251881Speter 168251881Speter return SVN_NO_ERROR; 169251881Speter} 170251881Speter 171289180Speter/* Set PROPS for NS:NAME VAL. Helper for propfind_closed */ 172289180Speterstatic void 173289180Speterset_ns_prop(apr_hash_t *ns_props, 174289180Speter const char *ns, const char *name, 175289180Speter const svn_string_t *val, apr_pool_t *result_pool) 176289180Speter{ 177289180Speter apr_hash_t *props = svn_hash_gets(ns_props, ns); 178251881Speter 179289180Speter if (!props) 180289180Speter { 181289180Speter props = apr_hash_make(result_pool); 182289180Speter ns = apr_pstrdup(result_pool, ns); 183289180Speter svn_hash_sets(ns_props, ns, props); 184289180Speter } 185289180Speter 186289180Speter if (val) 187289180Speter { 188289180Speter name = apr_pstrdup(result_pool, name); 189289180Speter val = svn_string_dup(val, result_pool); 190289180Speter } 191289180Speter 192289180Speter svn_hash_sets(props, name, val); 193289180Speter} 194289180Speter 195251881Speter/* Conforms to svn_ra_serf__xml_closed_t */ 196251881Speterstatic svn_error_t * 197251881Speterpropfind_closed(svn_ra_serf__xml_estate_t *xes, 198251881Speter void *baton, 199251881Speter int leaving_state, 200251881Speter const svn_string_t *cdata, 201251881Speter apr_hash_t *attrs, 202251881Speter apr_pool_t *scratch_pool) 203251881Speter{ 204251881Speter propfind_context_t *ctx = baton; 205251881Speter 206251881Speter if (leaving_state == MULTISTATUS) 207251881Speter { 208251881Speter /* We've gathered all the data from the reponse. Add this item 209251881Speter onto the "done list". External callers will then know this 210251881Speter request has been completed (tho stray response bytes may still 211251881Speter arrive). */ 212251881Speter } 213251881Speter else if (leaving_state == HREF) 214251881Speter { 215251881Speter const char *path; 216251881Speter 217251881Speter if (strcmp(ctx->depth, "1") == 0) 218251881Speter path = svn_urlpath__canonicalize(cdata->data, scratch_pool); 219251881Speter else 220251881Speter path = ctx->path; 221251881Speter 222251881Speter svn_ra_serf__xml_note(xes, RESPONSE, "path", path); 223251881Speter 224289180Speter SVN_ERR(ctx->prop_func(ctx->prop_func_baton, 225289180Speter path, 226289180Speter D_, "href", 227289180Speter cdata, scratch_pool)); 228251881Speter } 229251881Speter else if (leaving_state == COLLECTION) 230251881Speter { 231251881Speter svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection"); 232251881Speter } 233251881Speter else if (leaving_state == HREF_VALUE) 234251881Speter { 235251881Speter svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data); 236251881Speter } 237251881Speter else if (leaving_state == STATUS) 238251881Speter { 239251881Speter /* Parse the status field, and remember if this is a property 240251881Speter that we wish to ignore. (Typically, if it's not a 200, the 241251881Speter status will be 404 to indicate that a property we 242251881Speter specifically requested from the server doesn't exist.) */ 243289180Speter apr_int64_t status = parse_status_code(cdata->data); 244251881Speter if (status != 200) 245251881Speter svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*"); 246251881Speter } 247251881Speter else if (leaving_state == PROPVAL) 248251881Speter { 249289180Speter const char *encoding; 250251881Speter const svn_string_t *val_str; 251251881Speter const char *ns; 252251881Speter const char *name; 253251881Speter const char *altvalue; 254251881Speter 255289180Speter if ((altvalue = svn_hash_gets(attrs, "altvalue")) != NULL) 256251881Speter { 257289180Speter val_str = svn_string_create(altvalue, scratch_pool); 258289180Speter } 259289180Speter else if ((encoding = svn_hash_gets(attrs, "V:encoding")) != NULL) 260289180Speter { 261251881Speter if (strcmp(encoding, "base64") != 0) 262251881Speter return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, 263251881Speter NULL, 264251881Speter _("Got unrecognized encoding '%s'"), 265251881Speter encoding); 266251881Speter 267251881Speter /* Decode into the right pool. */ 268289180Speter val_str = svn_base64_decode_string(cdata, scratch_pool); 269251881Speter } 270251881Speter else 271251881Speter { 272251881Speter /* Copy into the right pool. */ 273289180Speter val_str = cdata; 274251881Speter } 275251881Speter 276289180Speter /* The current path sits on the RESPONSE state. 277251881Speter 278251881Speter Now, it would be nice if we could, at this point, know that 279251881Speter the status code for this property indicated a problem -- then 280251881Speter we could simply bail out here and ignore the property. 281251881Speter Sadly, though, we might get the status code *after* we get 282251881Speter the property value. So we'll carry on with our processing 283251881Speter here, setting the property and value as expected. Once we 284251881Speter know for sure the status code associate with the property, 285251881Speter we'll decide its fate. */ 286251881Speter 287251881Speter ns = svn_hash_gets(attrs, "ns"); 288289180Speter name = svn_hash_gets(attrs, "name"); 289251881Speter 290289180Speter set_ns_prop(ctx->ps_props, ns, name, val_str, 291289180Speter apr_hash_pool_get(ctx->ps_props)); 292251881Speter } 293251881Speter else 294251881Speter { 295251881Speter apr_hash_t *gathered; 296251881Speter 297251881Speter SVN_ERR_ASSERT(leaving_state == PROPSTAT); 298251881Speter 299289180Speter gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE); 300251881Speter 301251881Speter /* If we've squirreled away a note that says we want to ignore 302251881Speter these properties, we'll do so. Otherwise, we need to copy 303251881Speter them from the temporary hash into the ctx->ret_props hash. */ 304251881Speter if (! svn_hash_gets(gathered, "ignore-prop")) 305251881Speter { 306289180Speter apr_hash_index_t *hi_ns; 307289180Speter const char *path; 308289180Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 309251881Speter 310251881Speter 311289180Speter path = svn_hash_gets(gathered, "path"); 312289180Speter if (!path) 313289180Speter path = ctx->path; 314251881Speter 315289180Speter for (hi_ns = apr_hash_first(scratch_pool, ctx->ps_props); 316289180Speter hi_ns; 317289180Speter hi_ns = apr_hash_next(hi_ns)) 318289180Speter { 319289180Speter const char *ns = apr_hash_this_key(hi_ns); 320289180Speter apr_hash_t *props = apr_hash_this_val(hi_ns); 321289180Speter apr_hash_index_t *hi_prop; 322251881Speter 323289180Speter svn_pool_clear(iterpool); 324251881Speter 325289180Speter for (hi_prop = apr_hash_first(iterpool, props); 326289180Speter hi_prop; 327289180Speter hi_prop = apr_hash_next(hi_prop)) 328289180Speter { 329289180Speter const char *name = apr_hash_this_key(hi_prop); 330289180Speter const svn_string_t *value = apr_hash_this_val(hi_prop); 331251881Speter 332289180Speter SVN_ERR(ctx->prop_func(ctx->prop_func_baton, path, 333289180Speter ns, name, value, iterpool)); 334289180Speter } 335251881Speter } 336289180Speter 337289180Speter svn_pool_destroy(iterpool); 338251881Speter } 339251881Speter 340289180Speter ctx->ps_props = NULL; /* Allocated in PROPSTAT state pool */ 341251881Speter } 342251881Speter 343289180Speter return SVN_NO_ERROR; 344251881Speter} 345251881Speter 346251881Speter 347251881Speter 348251881Speterstatic svn_error_t * 349251881Spetersetup_propfind_headers(serf_bucket_t *headers, 350289180Speter void *setup_baton, 351289180Speter apr_pool_t *pool /* request pool */, 352289180Speter apr_pool_t *scratch_pool) 353251881Speter{ 354251881Speter propfind_context_t *ctx = setup_baton; 355251881Speter 356251881Speter serf_bucket_headers_setn(headers, "Depth", ctx->depth); 357251881Speter if (ctx->label) 358251881Speter { 359251881Speter serf_bucket_headers_setn(headers, "Label", ctx->label); 360251881Speter } 361251881Speter 362251881Speter return SVN_NO_ERROR; 363251881Speter} 364251881Speter 365251881Speter#define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">" 366251881Speter#define PROPFIND_TRAILER "</propfind>" 367251881Speter 368289180Speter/* Implements svn_ra_serf__request_body_delegate_t */ 369251881Speterstatic svn_error_t * 370251881Spetercreate_propfind_body(serf_bucket_t **bkt, 371251881Speter void *setup_baton, 372251881Speter serf_bucket_alloc_t *alloc, 373289180Speter apr_pool_t *pool /* request pool */, 374289180Speter apr_pool_t *scratch_pool) 375251881Speter{ 376251881Speter propfind_context_t *ctx = setup_baton; 377251881Speter 378251881Speter serf_bucket_t *body_bkt, *tmp; 379251881Speter const svn_ra_serf__dav_props_t *prop; 380251881Speter svn_boolean_t requested_allprop = FALSE; 381251881Speter 382251881Speter body_bkt = serf_bucket_aggregate_create(alloc); 383251881Speter 384251881Speter prop = ctx->find_props; 385289180Speter while (prop && prop->xmlns) 386251881Speter { 387251881Speter /* special case the allprop case. */ 388251881Speter if (strcmp(prop->name, "allprop") == 0) 389251881Speter { 390251881Speter requested_allprop = TRUE; 391251881Speter } 392251881Speter 393362181Sdim prop++; 394362181Sdim } 395362181Sdim 396362181Sdim tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER, 397362181Sdim sizeof(PROPFIND_HEADER)-1, 398362181Sdim alloc); 399362181Sdim serf_bucket_aggregate_append(body_bkt, tmp); 400362181Sdim 401362181Sdim /* If we're not doing an allprop, add <prop> tags. */ 402362181Sdim if (!requested_allprop) 403362181Sdim { 404362181Sdim tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>", 405362181Sdim sizeof("<prop>")-1, 406362181Sdim alloc); 407362181Sdim serf_bucket_aggregate_append(body_bkt, tmp); 408362181Sdim } 409362181Sdim 410362181Sdim prop = ctx->find_props; 411362181Sdim while (prop && prop->xmlns) 412362181Sdim { 413251881Speter /* <*propname* xmlns="*propns*" /> */ 414251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc); 415251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 416251881Speter 417251881Speter tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc); 418251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 419251881Speter 420251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"", 421251881Speter sizeof(" xmlns=\"")-1, 422251881Speter alloc); 423251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 424251881Speter 425289180Speter tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc); 426251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 427251881Speter 428251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1, 429251881Speter alloc); 430251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 431251881Speter 432251881Speter prop++; 433251881Speter } 434251881Speter 435251881Speter if (!requested_allprop) 436251881Speter { 437251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>", 438251881Speter sizeof("</prop>")-1, 439251881Speter alloc); 440251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 441251881Speter } 442251881Speter 443251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER, 444251881Speter sizeof(PROPFIND_TRAILER)-1, 445251881Speter alloc); 446251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 447251881Speter 448251881Speter *bkt = body_bkt; 449251881Speter return SVN_NO_ERROR; 450251881Speter} 451251881Speter 452251881Speter 453251881Spetersvn_error_t * 454289180Spetersvn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler, 455289180Speter svn_ra_serf__session_t *sess, 456289180Speter const char *path, 457289180Speter svn_revnum_t rev, 458289180Speter const char *depth, 459289180Speter const svn_ra_serf__dav_props_t *find_props, 460289180Speter svn_ra_serf__prop_func_t prop_func, 461289180Speter void *prop_func_baton, 462289180Speter apr_pool_t *pool) 463251881Speter{ 464251881Speter propfind_context_t *new_prop_ctx; 465251881Speter svn_ra_serf__handler_t *handler; 466251881Speter svn_ra_serf__xml_context_t *xmlctx; 467251881Speter 468251881Speter new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx)); 469251881Speter 470251881Speter new_prop_ctx->path = path; 471251881Speter new_prop_ctx->find_props = find_props; 472289180Speter new_prop_ctx->prop_func = prop_func; 473289180Speter new_prop_ctx->prop_func_baton = prop_func_baton; 474251881Speter new_prop_ctx->depth = depth; 475251881Speter 476251881Speter if (SVN_IS_VALID_REVNUM(rev)) 477251881Speter { 478251881Speter new_prop_ctx->label = apr_ltoa(pool, rev); 479251881Speter } 480251881Speter else 481251881Speter { 482251881Speter new_prop_ctx->label = NULL; 483251881Speter } 484251881Speter 485251881Speter xmlctx = svn_ra_serf__xml_context_create(propfind_ttable, 486251881Speter propfind_opened, 487251881Speter propfind_closed, 488251881Speter NULL, 489251881Speter new_prop_ctx, 490251881Speter pool); 491289180Speter handler = svn_ra_serf__create_expat_handler(sess, xmlctx, 492289180Speter propfind_expected_status, 493289180Speter pool); 494251881Speter 495251881Speter handler->method = "PROPFIND"; 496251881Speter handler->path = path; 497251881Speter handler->body_delegate = create_propfind_body; 498251881Speter handler->body_type = "text/xml"; 499251881Speter handler->body_delegate_baton = new_prop_ctx; 500251881Speter handler->header_delegate = setup_propfind_headers; 501251881Speter handler->header_delegate_baton = new_prop_ctx; 502251881Speter 503289180Speter handler->no_dav_headers = TRUE; 504251881Speter 505251881Speter new_prop_ctx->handler = handler; 506251881Speter 507251881Speter *propfind_handler = handler; 508251881Speter 509251881Speter return SVN_NO_ERROR; 510251881Speter} 511251881Speter 512251881Spetersvn_error_t * 513289180Spetersvn_ra_serf__deliver_svn_props(void *baton, 514289180Speter const char *path, 515289180Speter const char *ns, 516289180Speter const char *name, 517289180Speter const svn_string_t *value, 518289180Speter apr_pool_t *scratch_pool) 519251881Speter{ 520289180Speter apr_hash_t *props = baton; 521289180Speter apr_pool_t *result_pool = apr_hash_pool_get(props); 522289180Speter const char *prop_name; 523251881Speter 524289180Speter prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool); 525289180Speter if (prop_name == NULL) 526289180Speter return SVN_NO_ERROR; 527251881Speter 528289180Speter svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool)); 529251881Speter 530289180Speter return SVN_NO_ERROR; 531251881Speter} 532251881Speter 533251881Speter/* 534289180Speter * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties 535289180Speter * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash 536289180Speter * (const char * -> svn_string_t *) to the values. 537251881Speter */ 538289180Speterstatic svn_error_t * 539289180Speterdeliver_node_props(void *baton, 540289180Speter const char *path, 541289180Speter const char *ns, 542289180Speter const char *name, 543289180Speter const svn_string_t *value, 544289180Speter apr_pool_t *scratch_pool) 545251881Speter{ 546289180Speter apr_hash_t *nss = baton; 547289180Speter apr_hash_t *props; 548289180Speter apr_pool_t *result_pool = apr_hash_pool_get(nss); 549251881Speter 550289180Speter props = svn_hash_gets(nss, ns); 551251881Speter 552289180Speter if (!props) 553289180Speter { 554289180Speter props = apr_hash_make(result_pool); 555251881Speter 556289180Speter ns = apr_pstrdup(result_pool, ns); 557289180Speter svn_hash_sets(nss, ns, props); 558289180Speter } 559289180Speter 560289180Speter name = apr_pstrdup(result_pool, name); 561289180Speter svn_hash_sets(props, name, svn_string_dup(value, result_pool)); 562289180Speter 563251881Speter return SVN_NO_ERROR; 564251881Speter} 565251881Speter 566251881Spetersvn_error_t * 567251881Spetersvn_ra_serf__fetch_node_props(apr_hash_t **results, 568289180Speter svn_ra_serf__session_t *session, 569251881Speter const char *url, 570251881Speter svn_revnum_t revision, 571251881Speter const svn_ra_serf__dav_props_t *which_props, 572251881Speter apr_pool_t *result_pool, 573251881Speter apr_pool_t *scratch_pool) 574251881Speter{ 575289180Speter apr_hash_t *props; 576289180Speter svn_ra_serf__handler_t *handler; 577251881Speter 578289180Speter props = apr_hash_make(result_pool); 579251881Speter 580289180Speter SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, 581289180Speter url, revision, "0", which_props, 582289180Speter deliver_node_props, 583289180Speter props, scratch_pool)); 584251881Speter 585289180Speter SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 586251881Speter 587289180Speter *results = props; 588251881Speter return SVN_NO_ERROR; 589251881Speter} 590251881Speter 591251881Speterconst char * 592251881Spetersvn_ra_serf__svnname_from_wirename(const char *ns, 593251881Speter const char *name, 594251881Speter apr_pool_t *result_pool) 595251881Speter{ 596251881Speter if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 597251881Speter return apr_pstrdup(result_pool, name); 598251881Speter 599251881Speter if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 600289180Speter return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); 601251881Speter 602251881Speter if (strcmp(ns, SVN_PROP_PREFIX) == 0) 603289180Speter return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); 604251881Speter 605251881Speter if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) 606251881Speter return SVN_PROP_ENTRY_COMMITTED_REV; 607251881Speter 608251881Speter if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) 609251881Speter return SVN_PROP_ENTRY_COMMITTED_DATE; 610251881Speter 611251881Speter if (strcmp(name, "creator-displayname") == 0) 612251881Speter return SVN_PROP_ENTRY_LAST_AUTHOR; 613251881Speter 614251881Speter if (strcmp(name, "repository-uuid") == 0) 615251881Speter return SVN_PROP_ENTRY_UUID; 616251881Speter 617251881Speter if (strcmp(name, "lock-token") == 0) 618251881Speter return SVN_PROP_ENTRY_LOCK_TOKEN; 619251881Speter 620251881Speter if (strcmp(name, "checked-in") == 0) 621251881Speter return SVN_RA_SERF__WC_CHECKED_IN_URL; 622251881Speter 623251881Speter if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) 624251881Speter { 625251881Speter /* Here DAV: properties not yet converted to svn: properties should be 626251881Speter ignored. */ 627251881Speter return NULL; 628251881Speter } 629251881Speter 630251881Speter /* An unknown namespace, must be a custom property. */ 631289180Speter return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL); 632251881Speter} 633251881Speter 634251881Speter/* 635251881Speter * Contact the server (using CONN) to calculate baseline 636251881Speter * information for BASELINE_URL at REVISION (which may be 637251881Speter * SVN_INVALID_REVNUM to query the HEAD revision). 638251881Speter * 639251881Speter * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision 640251881Speter * retrieved from the server as part of this process (which should 641251881Speter * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the 642251881Speter * baseline collection URL. 643251881Speter */ 644251881Speterstatic svn_error_t * 645251881Speterretrieve_baseline_info(svn_revnum_t *actual_revision, 646251881Speter const char **basecoll_url_p, 647289180Speter svn_ra_serf__session_t *session, 648251881Speter const char *baseline_url, 649251881Speter svn_revnum_t revision, 650251881Speter apr_pool_t *result_pool, 651251881Speter apr_pool_t *scratch_pool) 652251881Speter{ 653251881Speter apr_hash_t *props; 654251881Speter apr_hash_t *dav_props; 655251881Speter const char *basecoll_url; 656251881Speter 657289180Speter SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, 658251881Speter baseline_url, revision, 659251881Speter baseline_props, 660251881Speter scratch_pool, scratch_pool)); 661251881Speter dav_props = apr_hash_get(props, "DAV:", 4); 662251881Speter /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */ 663251881Speter 664251881Speter basecoll_url = svn_prop_get_value(dav_props, "baseline-collection"); 665251881Speter if (!basecoll_url) 666251881Speter { 667251881Speter return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 668251881Speter _("The PROPFIND response did not include " 669251881Speter "the requested baseline-collection value")); 670251881Speter } 671251881Speter *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool); 672251881Speter 673251881Speter if (actual_revision) 674251881Speter { 675251881Speter const char *version_name; 676251881Speter 677251881Speter version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME); 678289180Speter if (version_name) 679289180Speter { 680289180Speter apr_int64_t rev; 681289180Speter 682289180Speter SVN_ERR(svn_cstring_atoi64(&rev, version_name)); 683289180Speter *actual_revision = (svn_revnum_t)rev; 684289180Speter } 685289180Speter 686289180Speter if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision)) 687251881Speter return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 688251881Speter _("The PROPFIND response did not include " 689251881Speter "the requested version-name value")); 690251881Speter } 691251881Speter 692251881Speter return SVN_NO_ERROR; 693251881Speter} 694251881Speter 695251881Speter 696251881Speter/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest 697251881Speter revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline 698251881Speter collection URL is also returned. 699251881Speter 700251881Speter Do the work over CONN. 701251881Speter 702251881Speter *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All 703251881Speter temporary allocations will be made in SCRATCH_POOL. */ 704251881Speterstatic svn_error_t * 705251881Speterv1_get_youngest_revnum(svn_revnum_t *youngest, 706251881Speter const char **basecoll_url, 707289180Speter svn_ra_serf__session_t *session, 708251881Speter const char *vcc_url, 709251881Speter apr_pool_t *result_pool, 710251881Speter apr_pool_t *scratch_pool) 711251881Speter{ 712251881Speter const char *baseline_url; 713251881Speter const char *bc_url; 714251881Speter 715251881Speter /* Fetching DAV:checked-in from the VCC (with no Label: to specify a 716251881Speter revision) will return the latest Baseline resource's URL. */ 717289180Speter SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url, 718251881Speter SVN_INVALID_REVNUM, 719251881Speter "checked-in", 720251881Speter scratch_pool, scratch_pool)); 721251881Speter if (!baseline_url) 722251881Speter { 723251881Speter return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 724251881Speter _("The OPTIONS response did not include " 725251881Speter "the requested checked-in value")); 726251881Speter } 727251881Speter baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool); 728251881Speter 729251881Speter /* From the Baseline resource, we can fetch the DAV:baseline-collection 730251881Speter and DAV:version-name properties. The latter is the revision number, 731251881Speter which is formally the name used in Label: headers. */ 732251881Speter 733251881Speter /* First check baseline information cache. */ 734251881Speter SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url, 735251881Speter youngest, 736289180Speter session->blncache, 737251881Speter baseline_url, 738251881Speter scratch_pool)); 739251881Speter if (!bc_url) 740251881Speter { 741289180Speter SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session, 742251881Speter baseline_url, SVN_INVALID_REVNUM, 743251881Speter scratch_pool, scratch_pool)); 744289180Speter SVN_ERR(svn_ra_serf__blncache_set(session->blncache, 745251881Speter baseline_url, *youngest, 746251881Speter bc_url, scratch_pool)); 747251881Speter } 748251881Speter 749251881Speter if (basecoll_url != NULL) 750251881Speter *basecoll_url = apr_pstrdup(result_pool, bc_url); 751251881Speter 752251881Speter return SVN_NO_ERROR; 753251881Speter} 754251881Speter 755251881Speter 756251881Spetersvn_error_t * 757251881Spetersvn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest, 758251881Speter svn_ra_serf__session_t *session, 759251881Speter apr_pool_t *scratch_pool) 760251881Speter{ 761251881Speter const char *vcc_url; 762251881Speter 763251881Speter if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 764251881Speter return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum( 765289180Speter youngest, session, scratch_pool)); 766251881Speter 767289180Speter SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); 768251881Speter 769251881Speter return svn_error_trace(v1_get_youngest_revnum(youngest, NULL, 770289180Speter session, vcc_url, 771251881Speter scratch_pool, scratch_pool)); 772251881Speter} 773251881Speter 774251881Speter 775251881Speter/* Set *BC_URL to the baseline collection url for REVISION. If REVISION 776251881Speter is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used. 777251881Speter 778251881Speter *REVNUM_USED will be set to the revision used. 779251881Speter 780251881Speter Uses the specified CONN, which is part of SESSION. 781251881Speter 782251881Speter All allocations (results and temporary) are performed in POOL. */ 783251881Speterstatic svn_error_t * 784251881Speterget_baseline_info(const char **bc_url, 785251881Speter svn_revnum_t *revnum_used, 786251881Speter svn_ra_serf__session_t *session, 787251881Speter svn_revnum_t revision, 788289180Speter apr_pool_t *result_pool, 789289180Speter apr_pool_t *scratch_pool) 790251881Speter{ 791251881Speter /* If we detected HTTP v2 support on the server, we can construct 792251881Speter the baseline collection URL ourselves, and fetch the latest 793251881Speter revision (if needed) with an OPTIONS request. */ 794251881Speter if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 795251881Speter { 796251881Speter if (SVN_IS_VALID_REVNUM(revision)) 797251881Speter { 798251881Speter *revnum_used = revision; 799251881Speter } 800251881Speter else 801251881Speter { 802251881Speter SVN_ERR(svn_ra_serf__v2_get_youngest_revnum( 803289180Speter revnum_used, session, scratch_pool)); 804251881Speter } 805251881Speter 806289180Speter *bc_url = apr_psprintf(result_pool, "%s/%ld", 807251881Speter session->rev_root_stub, *revnum_used); 808251881Speter } 809251881Speter 810251881Speter /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */ 811251881Speter else 812251881Speter { 813251881Speter const char *vcc_url; 814251881Speter 815289180Speter SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); 816251881Speter 817251881Speter if (SVN_IS_VALID_REVNUM(revision)) 818251881Speter { 819251881Speter /* First check baseline information cache. */ 820251881Speter SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url, 821251881Speter session->blncache, 822289180Speter revision, result_pool)); 823251881Speter if (!*bc_url) 824251881Speter { 825289180Speter SVN_ERR(retrieve_baseline_info(NULL, bc_url, session, 826289180Speter vcc_url, revision, 827289180Speter result_pool, scratch_pool)); 828251881Speter SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL, 829289180Speter revision, *bc_url, 830289180Speter scratch_pool)); 831251881Speter } 832251881Speter 833251881Speter *revnum_used = revision; 834251881Speter } 835251881Speter else 836251881Speter { 837251881Speter SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url, 838289180Speter session, vcc_url, 839289180Speter result_pool, scratch_pool)); 840251881Speter } 841251881Speter } 842251881Speter 843251881Speter return SVN_NO_ERROR; 844251881Speter} 845251881Speter 846251881Speter 847251881Spetersvn_error_t * 848251881Spetersvn_ra_serf__get_stable_url(const char **stable_url, 849251881Speter svn_revnum_t *latest_revnum, 850251881Speter svn_ra_serf__session_t *session, 851251881Speter const char *url, 852251881Speter svn_revnum_t revision, 853251881Speter apr_pool_t *result_pool, 854251881Speter apr_pool_t *scratch_pool) 855251881Speter{ 856251881Speter const char *basecoll_url; 857251881Speter const char *repos_relpath; 858251881Speter svn_revnum_t revnum_used; 859251881Speter 860251881Speter /* No URL? No sweat. We'll use the session URL. */ 861251881Speter if (! url) 862251881Speter url = session->session_url.path; 863251881Speter 864251881Speter SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used, 865289180Speter session, revision, scratch_pool, scratch_pool)); 866251881Speter SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url, 867289180Speter session, scratch_pool)); 868251881Speter 869251881Speter *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath, 870251881Speter result_pool); 871251881Speter if (latest_revnum) 872251881Speter *latest_revnum = revnum_used; 873251881Speter 874251881Speter return SVN_NO_ERROR; 875251881Speter} 876251881Speter 877251881Speter 878251881Spetersvn_error_t * 879251881Spetersvn_ra_serf__fetch_dav_prop(const char **value, 880289180Speter svn_ra_serf__session_t *session, 881251881Speter const char *url, 882251881Speter svn_revnum_t revision, 883251881Speter const char *propname, 884251881Speter apr_pool_t *result_pool, 885251881Speter apr_pool_t *scratch_pool) 886251881Speter{ 887251881Speter apr_hash_t *props; 888251881Speter apr_hash_t *dav_props; 889251881Speter 890289180Speter SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision, 891251881Speter checked_in_props, 892251881Speter scratch_pool, scratch_pool)); 893251881Speter dav_props = apr_hash_get(props, "DAV:", 4); 894251881Speter if (dav_props == NULL) 895251881Speter return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 896251881Speter _("The PROPFIND response did not include " 897251881Speter "the requested 'DAV:' properties")); 898251881Speter 899251881Speter /* We wouldn't get here if the resource was not found (404), so the 900251881Speter property should be present. 901251881Speter 902251881Speter Note: it is okay to call apr_pstrdup() with NULL. */ 903251881Speter *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname)); 904251881Speter 905251881Speter return SVN_NO_ERROR; 906251881Speter} 907289180Speter 908289180Speter/* Removes all non regular properties from PROPS */ 909289180Spetervoid 910289180Spetersvn_ra_serf__keep_only_regular_props(apr_hash_t *props, 911289180Speter apr_pool_t *scratch_pool) 912289180Speter{ 913289180Speter apr_hash_index_t *hi; 914289180Speter 915289180Speter for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi)) 916289180Speter { 917289180Speter const char *propname = apr_hash_this_key(hi); 918289180Speter 919289180Speter if (svn_property_kind2(propname) != svn_prop_regular_kind) 920289180Speter svn_hash_sets(props, propname, NULL); 921289180Speter } 922289180Speter} 923