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 { 45299742Sdim 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 67299742Sdim /* 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 76299742Sdim svn_ra_serf__prop_func_t prop_func; 77299742Sdim 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 121299742Sdimstatic const int propfind_expected_status[] = { 122299742Sdim 207, 123299742Sdim 0 124299742Sdim}; 125251881Speter 126251881Speter/* Return the HTTP status code contained in STATUS_LINE, or 0 if 127251881Speter there's a problem parsing it. */ 128299742Sdimstatic 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 { 160299742Sdim 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 { 165299742Sdim ctx->ps_props = apr_hash_make(svn_ra_serf__xml_state_pool(xes)); 166251881Speter } 167251881Speter 168251881Speter return SVN_NO_ERROR; 169251881Speter} 170251881Speter 171299742Sdim/* Set PROPS for NS:NAME VAL. Helper for propfind_closed */ 172299742Sdimstatic void 173299742Sdimset_ns_prop(apr_hash_t *ns_props, 174299742Sdim const char *ns, const char *name, 175299742Sdim const svn_string_t *val, apr_pool_t *result_pool) 176299742Sdim{ 177299742Sdim apr_hash_t *props = svn_hash_gets(ns_props, ns); 178251881Speter 179299742Sdim if (!props) 180299742Sdim { 181299742Sdim props = apr_hash_make(result_pool); 182299742Sdim ns = apr_pstrdup(result_pool, ns); 183299742Sdim svn_hash_sets(ns_props, ns, props); 184299742Sdim } 185299742Sdim 186299742Sdim if (val) 187299742Sdim { 188299742Sdim name = apr_pstrdup(result_pool, name); 189299742Sdim val = svn_string_dup(val, result_pool); 190299742Sdim } 191299742Sdim 192299742Sdim svn_hash_sets(props, name, val); 193299742Sdim} 194299742Sdim 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 224299742Sdim SVN_ERR(ctx->prop_func(ctx->prop_func_baton, 225299742Sdim path, 226299742Sdim D_, "href", 227299742Sdim 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.) */ 243299742Sdim 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 { 249299742Sdim const char *encoding; 250251881Speter const svn_string_t *val_str; 251251881Speter const char *ns; 252251881Speter const char *name; 253251881Speter const char *altvalue; 254251881Speter 255299742Sdim if ((altvalue = svn_hash_gets(attrs, "altvalue")) != NULL) 256251881Speter { 257299742Sdim val_str = svn_string_create(altvalue, scratch_pool); 258299742Sdim } 259299742Sdim else if ((encoding = svn_hash_gets(attrs, "V:encoding")) != NULL) 260299742Sdim { 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. */ 268299742Sdim val_str = svn_base64_decode_string(cdata, scratch_pool); 269251881Speter } 270251881Speter else 271251881Speter { 272251881Speter /* Copy into the right pool. */ 273299742Sdim val_str = cdata; 274251881Speter } 275251881Speter 276299742Sdim /* 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"); 288299742Sdim name = svn_hash_gets(attrs, "name"); 289251881Speter 290299742Sdim set_ns_prop(ctx->ps_props, ns, name, val_str, 291299742Sdim 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 299299742Sdim 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 { 306299742Sdim apr_hash_index_t *hi_ns; 307299742Sdim const char *path; 308299742Sdim apr_pool_t *iterpool = svn_pool_create(scratch_pool); 309251881Speter 310251881Speter 311299742Sdim path = svn_hash_gets(gathered, "path"); 312299742Sdim if (!path) 313299742Sdim path = ctx->path; 314251881Speter 315299742Sdim for (hi_ns = apr_hash_first(scratch_pool, ctx->ps_props); 316299742Sdim hi_ns; 317299742Sdim hi_ns = apr_hash_next(hi_ns)) 318299742Sdim { 319299742Sdim const char *ns = apr_hash_this_key(hi_ns); 320299742Sdim apr_hash_t *props = apr_hash_this_val(hi_ns); 321299742Sdim apr_hash_index_t *hi_prop; 322251881Speter 323299742Sdim svn_pool_clear(iterpool); 324251881Speter 325299742Sdim for (hi_prop = apr_hash_first(iterpool, props); 326299742Sdim hi_prop; 327299742Sdim hi_prop = apr_hash_next(hi_prop)) 328299742Sdim { 329299742Sdim const char *name = apr_hash_this_key(hi_prop); 330299742Sdim const svn_string_t *value = apr_hash_this_val(hi_prop); 331251881Speter 332299742Sdim SVN_ERR(ctx->prop_func(ctx->prop_func_baton, path, 333299742Sdim ns, name, value, iterpool)); 334299742Sdim } 335251881Speter } 336299742Sdim 337299742Sdim svn_pool_destroy(iterpool); 338251881Speter } 339251881Speter 340299742Sdim ctx->ps_props = NULL; /* Allocated in PROPSTAT state pool */ 341251881Speter } 342251881Speter 343299742Sdim return SVN_NO_ERROR; 344251881Speter} 345251881Speter 346251881Speter 347251881Speter 348251881Speterstatic svn_error_t * 349251881Spetersetup_propfind_headers(serf_bucket_t *headers, 350299742Sdim void *setup_baton, 351299742Sdim apr_pool_t *pool /* request pool */, 352299742Sdim 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 368299742Sdim/* 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, 373299742Sdim apr_pool_t *pool /* request pool */, 374299742Sdim 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; 385299742Sdim 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 393251881Speter /* <*propname* xmlns="*propns*" /> */ 394251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc); 395251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 396251881Speter 397251881Speter tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc); 398251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 399251881Speter 400251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"", 401251881Speter sizeof(" xmlns=\"")-1, 402251881Speter alloc); 403251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 404251881Speter 405299742Sdim tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc); 406251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 407251881Speter 408251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1, 409251881Speter alloc); 410251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 411251881Speter 412251881Speter prop++; 413251881Speter } 414251881Speter 415251881Speter /* If we're not doing an allprop, add <prop> tags. */ 416251881Speter if (!requested_allprop) 417251881Speter { 418251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>", 419251881Speter sizeof("<prop>")-1, 420251881Speter alloc); 421251881Speter serf_bucket_aggregate_prepend(body_bkt, tmp); 422251881Speter } 423251881Speter 424251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER, 425251881Speter sizeof(PROPFIND_HEADER)-1, 426251881Speter alloc); 427251881Speter 428251881Speter serf_bucket_aggregate_prepend(body_bkt, tmp); 429251881Speter 430251881Speter if (!requested_allprop) 431251881Speter { 432251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>", 433251881Speter sizeof("</prop>")-1, 434251881Speter alloc); 435251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 436251881Speter } 437251881Speter 438251881Speter tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER, 439251881Speter sizeof(PROPFIND_TRAILER)-1, 440251881Speter alloc); 441251881Speter serf_bucket_aggregate_append(body_bkt, tmp); 442251881Speter 443251881Speter *bkt = body_bkt; 444251881Speter return SVN_NO_ERROR; 445251881Speter} 446251881Speter 447251881Speter 448251881Spetersvn_error_t * 449299742Sdimsvn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler, 450299742Sdim svn_ra_serf__session_t *sess, 451299742Sdim const char *path, 452299742Sdim svn_revnum_t rev, 453299742Sdim const char *depth, 454299742Sdim const svn_ra_serf__dav_props_t *find_props, 455299742Sdim svn_ra_serf__prop_func_t prop_func, 456299742Sdim void *prop_func_baton, 457299742Sdim apr_pool_t *pool) 458251881Speter{ 459251881Speter propfind_context_t *new_prop_ctx; 460251881Speter svn_ra_serf__handler_t *handler; 461251881Speter svn_ra_serf__xml_context_t *xmlctx; 462251881Speter 463251881Speter new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx)); 464251881Speter 465251881Speter new_prop_ctx->path = path; 466251881Speter new_prop_ctx->find_props = find_props; 467299742Sdim new_prop_ctx->prop_func = prop_func; 468299742Sdim new_prop_ctx->prop_func_baton = prop_func_baton; 469251881Speter new_prop_ctx->depth = depth; 470251881Speter 471251881Speter if (SVN_IS_VALID_REVNUM(rev)) 472251881Speter { 473251881Speter new_prop_ctx->label = apr_ltoa(pool, rev); 474251881Speter } 475251881Speter else 476251881Speter { 477251881Speter new_prop_ctx->label = NULL; 478251881Speter } 479251881Speter 480251881Speter xmlctx = svn_ra_serf__xml_context_create(propfind_ttable, 481251881Speter propfind_opened, 482251881Speter propfind_closed, 483251881Speter NULL, 484251881Speter new_prop_ctx, 485251881Speter pool); 486299742Sdim handler = svn_ra_serf__create_expat_handler(sess, xmlctx, 487299742Sdim propfind_expected_status, 488299742Sdim pool); 489251881Speter 490251881Speter handler->method = "PROPFIND"; 491251881Speter handler->path = path; 492251881Speter handler->body_delegate = create_propfind_body; 493251881Speter handler->body_type = "text/xml"; 494251881Speter handler->body_delegate_baton = new_prop_ctx; 495251881Speter handler->header_delegate = setup_propfind_headers; 496251881Speter handler->header_delegate_baton = new_prop_ctx; 497251881Speter 498299742Sdim handler->no_dav_headers = TRUE; 499251881Speter 500251881Speter new_prop_ctx->handler = handler; 501251881Speter 502251881Speter *propfind_handler = handler; 503251881Speter 504251881Speter return SVN_NO_ERROR; 505251881Speter} 506251881Speter 507251881Spetersvn_error_t * 508299742Sdimsvn_ra_serf__deliver_svn_props(void *baton, 509299742Sdim const char *path, 510299742Sdim const char *ns, 511299742Sdim const char *name, 512299742Sdim const svn_string_t *value, 513299742Sdim apr_pool_t *scratch_pool) 514251881Speter{ 515299742Sdim apr_hash_t *props = baton; 516299742Sdim apr_pool_t *result_pool = apr_hash_pool_get(props); 517299742Sdim const char *prop_name; 518251881Speter 519299742Sdim prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool); 520299742Sdim if (prop_name == NULL) 521299742Sdim return SVN_NO_ERROR; 522251881Speter 523299742Sdim svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool)); 524251881Speter 525299742Sdim return SVN_NO_ERROR; 526251881Speter} 527251881Speter 528251881Speter/* 529299742Sdim * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties 530299742Sdim * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash 531299742Sdim * (const char * -> svn_string_t *) to the values. 532251881Speter */ 533299742Sdimstatic svn_error_t * 534299742Sdimdeliver_node_props(void *baton, 535299742Sdim const char *path, 536299742Sdim const char *ns, 537299742Sdim const char *name, 538299742Sdim const svn_string_t *value, 539299742Sdim apr_pool_t *scratch_pool) 540251881Speter{ 541299742Sdim apr_hash_t *nss = baton; 542299742Sdim apr_hash_t *props; 543299742Sdim apr_pool_t *result_pool = apr_hash_pool_get(nss); 544251881Speter 545299742Sdim props = svn_hash_gets(nss, ns); 546251881Speter 547299742Sdim if (!props) 548299742Sdim { 549299742Sdim props = apr_hash_make(result_pool); 550251881Speter 551299742Sdim ns = apr_pstrdup(result_pool, ns); 552299742Sdim svn_hash_sets(nss, ns, props); 553299742Sdim } 554299742Sdim 555299742Sdim name = apr_pstrdup(result_pool, name); 556299742Sdim svn_hash_sets(props, name, svn_string_dup(value, result_pool)); 557299742Sdim 558251881Speter return SVN_NO_ERROR; 559251881Speter} 560251881Speter 561251881Spetersvn_error_t * 562251881Spetersvn_ra_serf__fetch_node_props(apr_hash_t **results, 563299742Sdim svn_ra_serf__session_t *session, 564251881Speter const char *url, 565251881Speter svn_revnum_t revision, 566251881Speter const svn_ra_serf__dav_props_t *which_props, 567251881Speter apr_pool_t *result_pool, 568251881Speter apr_pool_t *scratch_pool) 569251881Speter{ 570299742Sdim apr_hash_t *props; 571299742Sdim svn_ra_serf__handler_t *handler; 572251881Speter 573299742Sdim props = apr_hash_make(result_pool); 574251881Speter 575299742Sdim SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, 576299742Sdim url, revision, "0", which_props, 577299742Sdim deliver_node_props, 578299742Sdim props, scratch_pool)); 579251881Speter 580299742Sdim SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 581251881Speter 582299742Sdim *results = props; 583251881Speter return SVN_NO_ERROR; 584251881Speter} 585251881Speter 586251881Speterconst char * 587251881Spetersvn_ra_serf__svnname_from_wirename(const char *ns, 588251881Speter const char *name, 589251881Speter apr_pool_t *result_pool) 590251881Speter{ 591251881Speter if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0) 592251881Speter return apr_pstrdup(result_pool, name); 593251881Speter 594251881Speter if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0) 595299742Sdim return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); 596251881Speter 597251881Speter if (strcmp(ns, SVN_PROP_PREFIX) == 0) 598299742Sdim return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL); 599251881Speter 600251881Speter if (strcmp(name, SVN_DAV__VERSION_NAME) == 0) 601251881Speter return SVN_PROP_ENTRY_COMMITTED_REV; 602251881Speter 603251881Speter if (strcmp(name, SVN_DAV__CREATIONDATE) == 0) 604251881Speter return SVN_PROP_ENTRY_COMMITTED_DATE; 605251881Speter 606251881Speter if (strcmp(name, "creator-displayname") == 0) 607251881Speter return SVN_PROP_ENTRY_LAST_AUTHOR; 608251881Speter 609251881Speter if (strcmp(name, "repository-uuid") == 0) 610251881Speter return SVN_PROP_ENTRY_UUID; 611251881Speter 612251881Speter if (strcmp(name, "lock-token") == 0) 613251881Speter return SVN_PROP_ENTRY_LOCK_TOKEN; 614251881Speter 615251881Speter if (strcmp(name, "checked-in") == 0) 616251881Speter return SVN_RA_SERF__WC_CHECKED_IN_URL; 617251881Speter 618251881Speter if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0) 619251881Speter { 620251881Speter /* Here DAV: properties not yet converted to svn: properties should be 621251881Speter ignored. */ 622251881Speter return NULL; 623251881Speter } 624251881Speter 625251881Speter /* An unknown namespace, must be a custom property. */ 626299742Sdim return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL); 627251881Speter} 628251881Speter 629251881Speter/* 630251881Speter * Contact the server (using CONN) to calculate baseline 631251881Speter * information for BASELINE_URL at REVISION (which may be 632251881Speter * SVN_INVALID_REVNUM to query the HEAD revision). 633251881Speter * 634251881Speter * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision 635251881Speter * retrieved from the server as part of this process (which should 636251881Speter * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the 637251881Speter * baseline collection URL. 638251881Speter */ 639251881Speterstatic svn_error_t * 640251881Speterretrieve_baseline_info(svn_revnum_t *actual_revision, 641251881Speter const char **basecoll_url_p, 642299742Sdim svn_ra_serf__session_t *session, 643251881Speter const char *baseline_url, 644251881Speter svn_revnum_t revision, 645251881Speter apr_pool_t *result_pool, 646251881Speter apr_pool_t *scratch_pool) 647251881Speter{ 648251881Speter apr_hash_t *props; 649251881Speter apr_hash_t *dav_props; 650251881Speter const char *basecoll_url; 651251881Speter 652299742Sdim SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, 653251881Speter baseline_url, revision, 654251881Speter baseline_props, 655251881Speter scratch_pool, scratch_pool)); 656251881Speter dav_props = apr_hash_get(props, "DAV:", 4); 657251881Speter /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */ 658251881Speter 659251881Speter basecoll_url = svn_prop_get_value(dav_props, "baseline-collection"); 660251881Speter if (!basecoll_url) 661251881Speter { 662251881Speter return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 663251881Speter _("The PROPFIND response did not include " 664251881Speter "the requested baseline-collection value")); 665251881Speter } 666251881Speter *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool); 667251881Speter 668251881Speter if (actual_revision) 669251881Speter { 670251881Speter const char *version_name; 671251881Speter 672251881Speter version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME); 673299742Sdim if (version_name) 674299742Sdim { 675299742Sdim apr_int64_t rev; 676299742Sdim 677299742Sdim SVN_ERR(svn_cstring_atoi64(&rev, version_name)); 678299742Sdim *actual_revision = (svn_revnum_t)rev; 679299742Sdim } 680299742Sdim 681299742Sdim if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision)) 682251881Speter return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 683251881Speter _("The PROPFIND response did not include " 684251881Speter "the requested version-name value")); 685251881Speter } 686251881Speter 687251881Speter return SVN_NO_ERROR; 688251881Speter} 689251881Speter 690251881Speter 691251881Speter/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest 692251881Speter revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline 693251881Speter collection URL is also returned. 694251881Speter 695251881Speter Do the work over CONN. 696251881Speter 697251881Speter *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All 698251881Speter temporary allocations will be made in SCRATCH_POOL. */ 699251881Speterstatic svn_error_t * 700251881Speterv1_get_youngest_revnum(svn_revnum_t *youngest, 701251881Speter const char **basecoll_url, 702299742Sdim svn_ra_serf__session_t *session, 703251881Speter const char *vcc_url, 704251881Speter apr_pool_t *result_pool, 705251881Speter apr_pool_t *scratch_pool) 706251881Speter{ 707251881Speter const char *baseline_url; 708251881Speter const char *bc_url; 709251881Speter 710251881Speter /* Fetching DAV:checked-in from the VCC (with no Label: to specify a 711251881Speter revision) will return the latest Baseline resource's URL. */ 712299742Sdim SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url, 713251881Speter SVN_INVALID_REVNUM, 714251881Speter "checked-in", 715251881Speter scratch_pool, scratch_pool)); 716251881Speter if (!baseline_url) 717251881Speter { 718251881Speter return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL, 719251881Speter _("The OPTIONS response did not include " 720251881Speter "the requested checked-in value")); 721251881Speter } 722251881Speter baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool); 723251881Speter 724251881Speter /* From the Baseline resource, we can fetch the DAV:baseline-collection 725251881Speter and DAV:version-name properties. The latter is the revision number, 726251881Speter which is formally the name used in Label: headers. */ 727251881Speter 728251881Speter /* First check baseline information cache. */ 729251881Speter SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url, 730251881Speter youngest, 731299742Sdim session->blncache, 732251881Speter baseline_url, 733251881Speter scratch_pool)); 734251881Speter if (!bc_url) 735251881Speter { 736299742Sdim SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session, 737251881Speter baseline_url, SVN_INVALID_REVNUM, 738251881Speter scratch_pool, scratch_pool)); 739299742Sdim SVN_ERR(svn_ra_serf__blncache_set(session->blncache, 740251881Speter baseline_url, *youngest, 741251881Speter bc_url, scratch_pool)); 742251881Speter } 743251881Speter 744251881Speter if (basecoll_url != NULL) 745251881Speter *basecoll_url = apr_pstrdup(result_pool, bc_url); 746251881Speter 747251881Speter return SVN_NO_ERROR; 748251881Speter} 749251881Speter 750251881Speter 751251881Spetersvn_error_t * 752251881Spetersvn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest, 753251881Speter svn_ra_serf__session_t *session, 754251881Speter apr_pool_t *scratch_pool) 755251881Speter{ 756251881Speter const char *vcc_url; 757251881Speter 758251881Speter if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 759251881Speter return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum( 760299742Sdim youngest, session, scratch_pool)); 761251881Speter 762299742Sdim SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); 763251881Speter 764251881Speter return svn_error_trace(v1_get_youngest_revnum(youngest, NULL, 765299742Sdim session, vcc_url, 766251881Speter scratch_pool, scratch_pool)); 767251881Speter} 768251881Speter 769251881Speter 770251881Speter/* Set *BC_URL to the baseline collection url for REVISION. If REVISION 771251881Speter is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used. 772251881Speter 773251881Speter *REVNUM_USED will be set to the revision used. 774251881Speter 775251881Speter Uses the specified CONN, which is part of SESSION. 776251881Speter 777251881Speter All allocations (results and temporary) are performed in POOL. */ 778251881Speterstatic svn_error_t * 779251881Speterget_baseline_info(const char **bc_url, 780251881Speter svn_revnum_t *revnum_used, 781251881Speter svn_ra_serf__session_t *session, 782251881Speter svn_revnum_t revision, 783299742Sdim apr_pool_t *result_pool, 784299742Sdim apr_pool_t *scratch_pool) 785251881Speter{ 786251881Speter /* If we detected HTTP v2 support on the server, we can construct 787251881Speter the baseline collection URL ourselves, and fetch the latest 788251881Speter revision (if needed) with an OPTIONS request. */ 789251881Speter if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session)) 790251881Speter { 791251881Speter if (SVN_IS_VALID_REVNUM(revision)) 792251881Speter { 793251881Speter *revnum_used = revision; 794251881Speter } 795251881Speter else 796251881Speter { 797251881Speter SVN_ERR(svn_ra_serf__v2_get_youngest_revnum( 798299742Sdim revnum_used, session, scratch_pool)); 799251881Speter } 800251881Speter 801299742Sdim *bc_url = apr_psprintf(result_pool, "%s/%ld", 802251881Speter session->rev_root_stub, *revnum_used); 803251881Speter } 804251881Speter 805251881Speter /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */ 806251881Speter else 807251881Speter { 808251881Speter const char *vcc_url; 809251881Speter 810299742Sdim SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool)); 811251881Speter 812251881Speter if (SVN_IS_VALID_REVNUM(revision)) 813251881Speter { 814251881Speter /* First check baseline information cache. */ 815251881Speter SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url, 816251881Speter session->blncache, 817299742Sdim revision, result_pool)); 818251881Speter if (!*bc_url) 819251881Speter { 820299742Sdim SVN_ERR(retrieve_baseline_info(NULL, bc_url, session, 821299742Sdim vcc_url, revision, 822299742Sdim result_pool, scratch_pool)); 823251881Speter SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL, 824299742Sdim revision, *bc_url, 825299742Sdim scratch_pool)); 826251881Speter } 827251881Speter 828251881Speter *revnum_used = revision; 829251881Speter } 830251881Speter else 831251881Speter { 832251881Speter SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url, 833299742Sdim session, vcc_url, 834299742Sdim result_pool, scratch_pool)); 835251881Speter } 836251881Speter } 837251881Speter 838251881Speter return SVN_NO_ERROR; 839251881Speter} 840251881Speter 841251881Speter 842251881Spetersvn_error_t * 843251881Spetersvn_ra_serf__get_stable_url(const char **stable_url, 844251881Speter svn_revnum_t *latest_revnum, 845251881Speter svn_ra_serf__session_t *session, 846251881Speter const char *url, 847251881Speter svn_revnum_t revision, 848251881Speter apr_pool_t *result_pool, 849251881Speter apr_pool_t *scratch_pool) 850251881Speter{ 851251881Speter const char *basecoll_url; 852251881Speter const char *repos_relpath; 853251881Speter svn_revnum_t revnum_used; 854251881Speter 855251881Speter /* No URL? No sweat. We'll use the session URL. */ 856251881Speter if (! url) 857251881Speter url = session->session_url.path; 858251881Speter 859251881Speter SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used, 860299742Sdim session, revision, scratch_pool, scratch_pool)); 861251881Speter SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url, 862299742Sdim session, scratch_pool)); 863251881Speter 864251881Speter *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath, 865251881Speter result_pool); 866251881Speter if (latest_revnum) 867251881Speter *latest_revnum = revnum_used; 868251881Speter 869251881Speter return SVN_NO_ERROR; 870251881Speter} 871251881Speter 872251881Speter 873251881Spetersvn_error_t * 874251881Spetersvn_ra_serf__fetch_dav_prop(const char **value, 875299742Sdim svn_ra_serf__session_t *session, 876251881Speter const char *url, 877251881Speter svn_revnum_t revision, 878251881Speter const char *propname, 879251881Speter apr_pool_t *result_pool, 880251881Speter apr_pool_t *scratch_pool) 881251881Speter{ 882251881Speter apr_hash_t *props; 883251881Speter apr_hash_t *dav_props; 884251881Speter 885299742Sdim SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision, 886251881Speter checked_in_props, 887251881Speter scratch_pool, scratch_pool)); 888251881Speter dav_props = apr_hash_get(props, "DAV:", 4); 889251881Speter if (dav_props == NULL) 890251881Speter return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL, 891251881Speter _("The PROPFIND response did not include " 892251881Speter "the requested 'DAV:' properties")); 893251881Speter 894251881Speter /* We wouldn't get here if the resource was not found (404), so the 895251881Speter property should be present. 896251881Speter 897251881Speter Note: it is okay to call apr_pstrdup() with NULL. */ 898251881Speter *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname)); 899251881Speter 900251881Speter return SVN_NO_ERROR; 901251881Speter} 902299742Sdim 903299742Sdim/* Removes all non regular properties from PROPS */ 904299742Sdimvoid 905299742Sdimsvn_ra_serf__keep_only_regular_props(apr_hash_t *props, 906299742Sdim apr_pool_t *scratch_pool) 907299742Sdim{ 908299742Sdim apr_hash_index_t *hi; 909299742Sdim 910299742Sdim for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi)) 911299742Sdim { 912299742Sdim const char *propname = apr_hash_this_key(hi); 913299742Sdim 914299742Sdim if (svn_property_kind2(propname) != svn_prop_regular_kind) 915299742Sdim svn_hash_sets(props, propname, NULL); 916299742Sdim } 917299742Sdim} 918