1/* 2 * merge.c : MERGE response parsing functions for ra_serf 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#include <apr_uri.h> 27 28#include <serf.h> 29 30#include "svn_hash.h" 31#include "svn_pools.h" 32#include "svn_ra.h" 33#include "svn_dav.h" 34#include "svn_xml.h" 35#include "svn_config.h" 36#include "svn_dirent_uri.h" 37#include "svn_props.h" 38 39#include "private/svn_dav_protocol.h" 40#include "private/svn_fspath.h" 41#include "svn_private_config.h" 42 43#include "ra_serf.h" 44#include "../libsvn_ra/ra_loader.h" 45 46 47/* 48 * This enum represents the current state of our XML parsing for a MERGE. 49 */ 50typedef enum merge_state_e { 51 INITIAL = 0, 52 MERGE_RESPONSE, 53 UPDATED_SET, 54 RESPONSE, 55 HREF, 56 PROPSTAT, 57 PROP, 58 RESOURCE_TYPE, 59 BASELINE, 60 COLLECTION, 61 SKIP_HREF, 62 CHECKED_IN, 63 VERSION_NAME, 64 DATE, 65 AUTHOR, 66 POST_COMMIT_ERR, 67 68 PROP_VAL 69} merge_state_e; 70 71 72/* Structure associated with a MERGE request. */ 73typedef struct merge_context_t 74{ 75 apr_pool_t *pool; 76 77 svn_ra_serf__session_t *session; 78 svn_ra_serf__handler_t *handler; 79 80 apr_hash_t *lock_tokens; 81 svn_boolean_t keep_locks; 82 83 const char *merge_resource_url; /* URL of resource to be merged. */ 84 const char *merge_url; /* URL at which the MERGE request is aimed. */ 85 86 svn_commit_info_t *commit_info; 87 88} merge_context_t; 89 90 91#define D_ "DAV:" 92#define S_ SVN_XML_NAMESPACE 93static const svn_ra_serf__xml_transition_t merge_ttable[] = { 94 { INITIAL, D_, "merge-response", MERGE_RESPONSE, 95 FALSE, { NULL }, FALSE }, 96 97 { MERGE_RESPONSE, D_, "updated-set", UPDATED_SET, 98 FALSE, { NULL }, FALSE }, 99 100 { UPDATED_SET, D_, "response", RESPONSE, 101 FALSE, { NULL }, TRUE }, 102 103 { RESPONSE, D_, "href", HREF, 104 TRUE, { NULL }, TRUE }, 105 106 { RESPONSE, D_, "propstat", PROPSTAT, 107 FALSE, { NULL }, FALSE }, 108 109#if 0 110 /* Not needed. */ 111 { PROPSTAT, D_, "status", STATUS, 112 FALSE, { NULL }, FALSE }, 113#endif 114 115 { PROPSTAT, D_, "prop", PROP, 116 FALSE, { NULL }, FALSE }, 117 118 { PROP, D_, "resourcetype", RESOURCE_TYPE, 119 FALSE, { NULL }, FALSE }, 120 121 { RESOURCE_TYPE, D_, "baseline", BASELINE, 122 FALSE, { NULL }, TRUE }, 123 124 { RESOURCE_TYPE, D_, "collection", COLLECTION, 125 FALSE, { NULL }, TRUE }, 126 127 { PROP, D_, "checked-in", SKIP_HREF, 128 FALSE, { NULL }, FALSE }, 129 130 { SKIP_HREF, D_, "href", CHECKED_IN, 131 TRUE, { NULL }, TRUE }, 132 133 { PROP, D_, SVN_DAV__VERSION_NAME, VERSION_NAME, 134 TRUE, { NULL }, TRUE }, 135 136 { PROP, D_, SVN_DAV__CREATIONDATE, DATE, 137 TRUE, { NULL }, TRUE }, 138 139 { PROP, D_, "creator-displayname", AUTHOR, 140 TRUE, { NULL }, TRUE }, 141 142 { PROP, S_, "post-commit-err", POST_COMMIT_ERR, 143 TRUE, { NULL }, TRUE }, 144 145 { 0 } 146}; 147 148 149/* Conforms to svn_ra_serf__xml_closed_t */ 150static svn_error_t * 151merge_closed(svn_ra_serf__xml_estate_t *xes, 152 void *baton, 153 int leaving_state, 154 const svn_string_t *cdata, 155 apr_hash_t *attrs, 156 apr_pool_t *scratch_pool) 157{ 158 merge_context_t *merge_ctx = baton; 159 160 if (leaving_state == RESPONSE) 161 { 162 const char *rtype; 163 164 rtype = svn_hash_gets(attrs, "resourcetype"); 165 166 /* rtype can only be "baseline" or "collection" (or NULL). We can 167 keep this check simple. */ 168 if (rtype && *rtype == 'b') 169 { 170 const char *rev_str; 171 172 rev_str = svn_hash_gets(attrs, "revision"); 173 if (rev_str) 174 merge_ctx->commit_info->revision = SVN_STR_TO_REV(rev_str); 175 else 176 merge_ctx->commit_info->revision = SVN_INVALID_REVNUM; 177 178 merge_ctx->commit_info->date = 179 apr_pstrdup(merge_ctx->pool, 180 svn_hash_gets(attrs, "date")); 181 182 merge_ctx->commit_info->author = 183 apr_pstrdup(merge_ctx->pool, 184 svn_hash_gets(attrs, "author")); 185 186 merge_ctx->commit_info->post_commit_err = 187 apr_pstrdup(merge_ctx->pool, 188 svn_hash_gets(attrs, "post-commit-err")); 189 } 190 else 191 { 192 const char *href; 193 194 href = svn_urlpath__skip_ancestor( 195 merge_ctx->merge_url, 196 svn_hash_gets(attrs, "href")); 197 198 if (href == NULL) 199 return svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL, 200 _("A MERGE response for '%s' is not " 201 "a child of the destination ('%s')"), 202 href, merge_ctx->merge_url); 203 204 /* We now need to dive all the way into the WC to update the 205 base VCC url. */ 206 if (!SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(merge_ctx->session) 207 && merge_ctx->session->wc_callbacks->push_wc_prop) 208 { 209 const char *checked_in; 210 svn_string_t checked_in_str; 211 212 checked_in = svn_hash_gets(attrs, "checked-in"); 213 checked_in_str.data = checked_in; 214 checked_in_str.len = strlen(checked_in); 215 216 SVN_ERR(merge_ctx->session->wc_callbacks->push_wc_prop( 217 merge_ctx->session->wc_callback_baton, 218 href, 219 SVN_RA_SERF__WC_CHECKED_IN_URL, 220 &checked_in_str, 221 scratch_pool)); 222 } 223 } 224 } 225 else if (leaving_state == BASELINE) 226 { 227 svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "baseline"); 228 } 229 else if (leaving_state == COLLECTION) 230 { 231 svn_ra_serf__xml_note(xes, RESPONSE, "resourcetype", "collection"); 232 } 233 else 234 { 235 const char *name; 236 const char *value = cdata->data; 237 238 if (leaving_state == HREF) 239 { 240 name = "href"; 241 value = svn_urlpath__canonicalize(value, scratch_pool); 242 } 243 else if (leaving_state == CHECKED_IN) 244 { 245 name = "checked-in"; 246 value = svn_urlpath__canonicalize(value, scratch_pool); 247 } 248 else if (leaving_state == VERSION_NAME) 249 name = "revision"; 250 else if (leaving_state == DATE) 251 name = "date"; 252 else if (leaving_state == AUTHOR) 253 name = "author"; 254 else if (leaving_state == POST_COMMIT_ERR) 255 name = "post-commit-err"; 256 else 257 SVN_ERR_MALFUNCTION(); 258 259 svn_ra_serf__xml_note(xes, RESPONSE, name, value); 260 } 261 262 return SVN_NO_ERROR; 263} 264 265 266static svn_error_t * 267setup_merge_headers(serf_bucket_t *headers, 268 void *baton, 269 apr_pool_t *pool) 270{ 271 merge_context_t *ctx = baton; 272 273 if (!ctx->keep_locks) 274 { 275 serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER, 276 SVN_DAV_OPTION_RELEASE_LOCKS); 277 } 278 279 return SVN_NO_ERROR; 280} 281 282void 283svn_ra_serf__merge_lock_token_list(apr_hash_t *lock_tokens, 284 const char *parent, 285 serf_bucket_t *body, 286 serf_bucket_alloc_t *alloc, 287 apr_pool_t *pool) 288{ 289 apr_hash_index_t *hi; 290 291 if (!lock_tokens || apr_hash_count(lock_tokens) == 0) 292 return; 293 294 svn_ra_serf__add_open_tag_buckets(body, alloc, 295 "S:lock-token-list", 296 "xmlns:S", SVN_XML_NAMESPACE, 297 NULL); 298 299 for (hi = apr_hash_first(pool, lock_tokens); 300 hi; 301 hi = apr_hash_next(hi)) 302 { 303 const void *key; 304 apr_ssize_t klen; 305 void *val; 306 svn_string_t path; 307 308 apr_hash_this(hi, &key, &klen, &val); 309 310 path.data = key; 311 path.len = klen; 312 313 if (parent && !svn_relpath_skip_ancestor(parent, key)) 314 continue; 315 316 svn_ra_serf__add_open_tag_buckets(body, alloc, "S:lock", NULL); 317 318 svn_ra_serf__add_open_tag_buckets(body, alloc, "lock-path", NULL); 319 svn_ra_serf__add_cdata_len_buckets(body, alloc, path.data, path.len); 320 svn_ra_serf__add_close_tag_buckets(body, alloc, "lock-path"); 321 322 svn_ra_serf__add_tag_buckets(body, "lock-token", val, alloc); 323 324 svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock"); 325 } 326 327 svn_ra_serf__add_close_tag_buckets(body, alloc, "S:lock-token-list"); 328} 329 330static svn_error_t* 331create_merge_body(serf_bucket_t **bkt, 332 void *baton, 333 serf_bucket_alloc_t *alloc, 334 apr_pool_t *pool) 335{ 336 merge_context_t *ctx = baton; 337 serf_bucket_t *body_bkt; 338 339 body_bkt = serf_bucket_aggregate_create(alloc); 340 341 svn_ra_serf__add_xml_header_buckets(body_bkt, alloc); 342 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:merge", 343 "xmlns:D", "DAV:", 344 NULL); 345 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:source", NULL); 346 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:href", NULL); 347 348 svn_ra_serf__add_cdata_len_buckets(body_bkt, alloc, 349 ctx->merge_resource_url, 350 strlen(ctx->merge_resource_url)); 351 352 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:href"); 353 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:source"); 354 355 svn_ra_serf__add_tag_buckets(body_bkt, "D:no-auto-merge", NULL, alloc); 356 svn_ra_serf__add_tag_buckets(body_bkt, "D:no-checkout", NULL, alloc); 357 358 svn_ra_serf__add_open_tag_buckets(body_bkt, alloc, "D:prop", NULL); 359 svn_ra_serf__add_tag_buckets(body_bkt, "D:checked-in", NULL, alloc); 360 svn_ra_serf__add_tag_buckets(body_bkt, "D:" SVN_DAV__VERSION_NAME, NULL, alloc); 361 svn_ra_serf__add_tag_buckets(body_bkt, "D:resourcetype", NULL, alloc); 362 svn_ra_serf__add_tag_buckets(body_bkt, "D:" SVN_DAV__CREATIONDATE, NULL, alloc); 363 svn_ra_serf__add_tag_buckets(body_bkt, "D:creator-displayname", NULL, alloc); 364 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:prop"); 365 366 svn_ra_serf__merge_lock_token_list(ctx->lock_tokens, NULL, body_bkt, alloc, 367 pool); 368 369 svn_ra_serf__add_close_tag_buckets(body_bkt, alloc, "D:merge"); 370 371 *bkt = body_bkt; 372 373 return SVN_NO_ERROR; 374} 375 376 377svn_error_t * 378svn_ra_serf__run_merge(const svn_commit_info_t **commit_info, 379 int *response_code, 380 svn_ra_serf__session_t *session, 381 svn_ra_serf__connection_t *conn, 382 const char *merge_resource_url, 383 apr_hash_t *lock_tokens, 384 svn_boolean_t keep_locks, 385 apr_pool_t *result_pool, 386 apr_pool_t *scratch_pool) 387{ 388 merge_context_t *merge_ctx; 389 svn_ra_serf__handler_t *handler; 390 svn_ra_serf__xml_context_t *xmlctx; 391 392 merge_ctx = apr_pcalloc(scratch_pool, sizeof(*merge_ctx)); 393 394 merge_ctx->pool = result_pool; 395 merge_ctx->session = session; 396 397 merge_ctx->merge_resource_url = merge_resource_url; 398 399 merge_ctx->lock_tokens = lock_tokens; 400 merge_ctx->keep_locks = keep_locks; 401 402 merge_ctx->commit_info = svn_create_commit_info(result_pool); 403 404 merge_ctx->merge_url = session->session_url.path; 405 406 xmlctx = svn_ra_serf__xml_context_create(merge_ttable, 407 NULL, merge_closed, NULL, 408 merge_ctx, 409 scratch_pool); 410 handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool); 411 412 handler->method = "MERGE"; 413 handler->path = merge_ctx->merge_url; 414 handler->body_delegate = create_merge_body; 415 handler->body_delegate_baton = merge_ctx; 416 handler->conn = conn; 417 handler->session = session; 418 419 handler->header_delegate = setup_merge_headers; 420 handler->header_delegate_baton = merge_ctx; 421 422 merge_ctx->handler = handler; 423 424 SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool)); 425 426 *commit_info = merge_ctx->commit_info; 427 *response_code = handler->sline.code; 428 429 return SVN_NO_ERROR; 430} 431