1 /*
2 * property.c : property routines 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 <serf.h>
27
28 #include "svn_hash.h"
29 #include "svn_path.h"
30 #include "svn_base64.h"
31 #include "svn_xml.h"
32 #include "svn_props.h"
33 #include "svn_dirent_uri.h"
34
35 #include "private/svn_dav_protocol.h"
36 #include "private/svn_fspath.h"
37 #include "private/svn_string_private.h"
38 #include "svn_private_config.h"
39
40 #include "ra_serf.h"
41
42
43 /* Our current parsing state we're in for the PROPFIND response. */
44 typedef enum prop_state_e {
45 INITIAL = 0,
46 MULTISTATUS,
47 RESPONSE,
48 HREF,
49 PROPSTAT,
50 STATUS,
51 PROP,
52 PROPVAL,
53 COLLECTION,
54 HREF_VALUE
55 } prop_state_e;
56
57
58 /*
59 * This structure represents a pending PROPFIND response.
60 */
61 typedef struct propfind_context_t {
62 /* pool to issue allocations from */
63 apr_pool_t *pool;
64
65 svn_ra_serf__handler_t *handler;
66
67 /* associated serf session */
68 svn_ra_serf__session_t *sess;
69 svn_ra_serf__connection_t *conn;
70
71 /* the requested path */
72 const char *path;
73
74 /* the requested version (number and string form) */
75 svn_revnum_t rev;
76 const char *label;
77
78 /* the request depth */
79 const char *depth;
80
81 /* the list of requested properties */
82 const svn_ra_serf__dav_props_t *find_props;
83
84 /* hash table that will be updated with the properties
85 *
86 * This can be shared between multiple propfind_context_t
87 * structures
88 */
89 apr_hash_t *ret_props;
90
91 /* hash table containing all the properties associated with the
92 * "current" <propstat> tag. These will get copied into RET_PROPS
93 * if the status code similarly associated indicates that they are
94 * "good"; otherwise, they'll get discarded.
95 */
96 apr_hash_t *ps_props;
97
98 /* If not-NULL, add us to this list when we're done. */
99 svn_ra_serf__list_t **done_list;
100
101 svn_ra_serf__list_t done_item;
102
103 } propfind_context_t;
104
105
106 #define D_ "DAV:"
107 #define S_ SVN_XML_NAMESPACE
108 static const svn_ra_serf__xml_transition_t propfind_ttable[] = {
109 { INITIAL, D_, "multistatus", MULTISTATUS,
110 FALSE, { NULL }, TRUE },
111
112 { MULTISTATUS, D_, "response", RESPONSE,
113 FALSE, { NULL }, FALSE },
114
115 { RESPONSE, D_, "href", HREF,
116 TRUE, { NULL }, TRUE },
117
118 { RESPONSE, D_, "propstat", PROPSTAT,
119 FALSE, { NULL }, TRUE },
120
121 { PROPSTAT, D_, "status", STATUS,
122 TRUE, { NULL }, TRUE },
123
124 { PROPSTAT, D_, "prop", PROP,
125 FALSE, { NULL }, FALSE },
126
127 { PROP, "*", "*", PROPVAL,
128 TRUE, { "?V:encoding", NULL }, TRUE },
129
130 { PROPVAL, D_, "collection", COLLECTION,
131 FALSE, { NULL }, TRUE },
132
133 { PROPVAL, D_, "href", HREF_VALUE,
134 TRUE, { NULL }, TRUE },
135
136 { 0 }
137 };
138
139
140 /* Return the HTTP status code contained in STATUS_LINE, or 0 if
141 there's a problem parsing it. */
parse_status_code(const char * status_line)142 static int parse_status_code(const char *status_line)
143 {
144 /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */
145 if (status_line[0] == 'H' &&
146 status_line[1] == 'T' &&
147 status_line[2] == 'T' &&
148 status_line[3] == 'P' &&
149 status_line[4] == '/' &&
150 (status_line[5] >= '0' && status_line[5] <= '9') &&
151 status_line[6] == '.' &&
152 (status_line[7] >= '0' && status_line[7] <= '9') &&
153 status_line[8] == ' ')
154 {
155 char *reason;
156
157 return apr_strtoi64(status_line + 8, &reason, 10);
158 }
159 return 0;
160 }
161
162
163 /* Conforms to svn_ra_serf__path_rev_walker_t */
164 static svn_error_t *
copy_into_ret_props(void * baton,const char * path,apr_ssize_t path_len,const char * ns,apr_ssize_t ns_len,const char * name,apr_ssize_t name_len,const svn_string_t * val,apr_pool_t * pool)165 copy_into_ret_props(void *baton,
166 const char *path, apr_ssize_t path_len,
167 const char *ns, apr_ssize_t ns_len,
168 const char *name, apr_ssize_t name_len,
169 const svn_string_t *val,
170 apr_pool_t *pool)
171 {
172 propfind_context_t *ctx = baton;
173
174 svn_ra_serf__set_ver_prop(ctx->ret_props, path, ctx->rev, ns, name,
175 val, ctx->pool);
176 return SVN_NO_ERROR;
177 }
178
179
180 /* Conforms to svn_ra_serf__xml_opened_t */
181 static svn_error_t *
propfind_opened(svn_ra_serf__xml_estate_t * xes,void * baton,int entered_state,const svn_ra_serf__dav_props_t * tag,apr_pool_t * scratch_pool)182 propfind_opened(svn_ra_serf__xml_estate_t *xes,
183 void *baton,
184 int entered_state,
185 const svn_ra_serf__dav_props_t *tag,
186 apr_pool_t *scratch_pool)
187 {
188 propfind_context_t *ctx = baton;
189
190 if (entered_state == PROPVAL)
191 {
192 svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->namespace);
193 svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
194 }
195 else if (entered_state == PROPSTAT)
196 {
197 ctx->ps_props = apr_hash_make(ctx->pool);
198 }
199
200 return SVN_NO_ERROR;
201 }
202
203
204 /* Conforms to svn_ra_serf__xml_closed_t */
205 static svn_error_t *
propfind_closed(svn_ra_serf__xml_estate_t * xes,void * baton,int leaving_state,const svn_string_t * cdata,apr_hash_t * attrs,apr_pool_t * scratch_pool)206 propfind_closed(svn_ra_serf__xml_estate_t *xes,
207 void *baton,
208 int leaving_state,
209 const svn_string_t *cdata,
210 apr_hash_t *attrs,
211 apr_pool_t *scratch_pool)
212 {
213 propfind_context_t *ctx = baton;
214
215 if (leaving_state == MULTISTATUS)
216 {
217 /* We've gathered all the data from the reponse. Add this item
218 onto the "done list". External callers will then know this
219 request has been completed (tho stray response bytes may still
220 arrive). */
221 if (ctx->done_list)
222 {
223 ctx->done_item.data = ctx->handler;
224 ctx->done_item.next = *ctx->done_list;
225 *ctx->done_list = &ctx->done_item;
226 }
227 }
228 else if (leaving_state == HREF)
229 {
230 const char *path;
231 const svn_string_t *val_str;
232
233 if (strcmp(ctx->depth, "1") == 0)
234 path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
235 else
236 path = ctx->path;
237
238 svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
239
240 /* Copy the value into the right pool, then save the HREF. */
241 val_str = svn_string_dup(cdata, ctx->pool);
242 svn_ra_serf__set_ver_prop(ctx->ret_props,
243 path, ctx->rev, D_, "href", val_str,
244 ctx->pool);
245 }
246 else if (leaving_state == COLLECTION)
247 {
248 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
249 }
250 else if (leaving_state == HREF_VALUE)
251 {
252 svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
253 }
254 else if (leaving_state == STATUS)
255 {
256 /* Parse the status field, and remember if this is a property
257 that we wish to ignore. (Typically, if it's not a 200, the
258 status will be 404 to indicate that a property we
259 specifically requested from the server doesn't exist.) */
260 int status = parse_status_code(cdata->data);
261 if (status != 200)
262 svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
263 }
264 else if (leaving_state == PROPVAL)
265 {
266 const char *encoding = svn_hash_gets(attrs, "V:encoding");
267 const svn_string_t *val_str;
268 apr_hash_t *gathered;
269 const char *path;
270 const char *ns;
271 const char *name;
272 const char *altvalue;
273
274 if (encoding)
275 {
276 if (strcmp(encoding, "base64") != 0)
277 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
278 NULL,
279 _("Got unrecognized encoding '%s'"),
280 encoding);
281
282 /* Decode into the right pool. */
283 val_str = svn_base64_decode_string(cdata, ctx->pool);
284 }
285 else
286 {
287 /* Copy into the right pool. */
288 val_str = svn_string_dup(cdata, ctx->pool);
289 }
290
291 /* The current path sits on the RESPONSE state. Gather up all the
292 state from this PROPVAL to the (grandparent) RESPONSE state,
293 and grab the path from there.
294
295 Now, it would be nice if we could, at this point, know that
296 the status code for this property indicated a problem -- then
297 we could simply bail out here and ignore the property.
298 Sadly, though, we might get the status code *after* we get
299 the property value. So we'll carry on with our processing
300 here, setting the property and value as expected. Once we
301 know for sure the status code associate with the property,
302 we'll decide its fate. */
303 gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE);
304
305 /* These will be dup'd into CTX->POOL, as necessary. */
306 path = svn_hash_gets(gathered, "path");
307 if (path == NULL)
308 path = ctx->path;
309
310 ns = svn_hash_gets(attrs, "ns");
311 name = apr_pstrdup(ctx->pool,
312 svn_hash_gets(attrs, "name"));
313
314 altvalue = svn_hash_gets(attrs, "altvalue");
315 if (altvalue != NULL)
316 val_str = svn_string_create(altvalue, ctx->pool);
317
318 svn_ra_serf__set_ver_prop(ctx->ps_props,
319 path, ctx->rev, ns, name, val_str,
320 ctx->pool);
321 }
322 else
323 {
324 apr_hash_t *gathered;
325
326 SVN_ERR_ASSERT(leaving_state == PROPSTAT);
327
328 gathered = svn_ra_serf__xml_gather_since(xes, PROPSTAT);
329
330 /* If we've squirreled away a note that says we want to ignore
331 these properties, we'll do so. Otherwise, we need to copy
332 them from the temporary hash into the ctx->ret_props hash. */
333 if (! svn_hash_gets(gathered, "ignore-prop"))
334 {
335 SVN_ERR(svn_ra_serf__walk_all_paths(ctx->ps_props, ctx->rev,
336 copy_into_ret_props, ctx,
337 scratch_pool));
338 }
339
340 ctx->ps_props = NULL;
341 }
342
343 return SVN_NO_ERROR;
344 }
345
346
347 const svn_string_t *
svn_ra_serf__get_ver_prop_string(apr_hash_t * props,const char * path,svn_revnum_t rev,const char * ns,const char * name)348 svn_ra_serf__get_ver_prop_string(apr_hash_t *props,
349 const char *path,
350 svn_revnum_t rev,
351 const char *ns,
352 const char *name)
353 {
354 apr_hash_t *ver_props, *path_props, *ns_props;
355 void *val = NULL;
356
357 ver_props = apr_hash_get(props, &rev, sizeof(rev));
358 if (ver_props)
359 {
360 path_props = svn_hash_gets(ver_props, path);
361
362 if (path_props)
363 {
364 ns_props = svn_hash_gets(path_props, ns);
365 if (ns_props)
366 {
367 val = svn_hash_gets(ns_props, name);
368 }
369 }
370 }
371
372 return val;
373 }
374
375 const char *
svn_ra_serf__get_ver_prop(apr_hash_t * props,const char * path,svn_revnum_t rev,const char * ns,const char * name)376 svn_ra_serf__get_ver_prop(apr_hash_t *props,
377 const char *path,
378 svn_revnum_t rev,
379 const char *ns,
380 const char *name)
381 {
382 const svn_string_t *val;
383
384 val = svn_ra_serf__get_ver_prop_string(props, path, rev, ns, name);
385
386 if (val)
387 {
388 return val->data;
389 }
390
391 return NULL;
392 }
393
394 const svn_string_t *
svn_ra_serf__get_prop_string(apr_hash_t * props,const char * path,const char * ns,const char * name)395 svn_ra_serf__get_prop_string(apr_hash_t *props,
396 const char *path,
397 const char *ns,
398 const char *name)
399 {
400 return svn_ra_serf__get_ver_prop_string(props, path, SVN_INVALID_REVNUM,
401 ns, name);
402 }
403
404 const char *
svn_ra_serf__get_prop(apr_hash_t * props,const char * path,const char * ns,const char * name)405 svn_ra_serf__get_prop(apr_hash_t *props,
406 const char *path,
407 const char *ns,
408 const char *name)
409 {
410 return svn_ra_serf__get_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name);
411 }
412
413 void
svn_ra_serf__set_ver_prop(apr_hash_t * props,const char * path,svn_revnum_t rev,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * pool)414 svn_ra_serf__set_ver_prop(apr_hash_t *props,
415 const char *path, svn_revnum_t rev,
416 const char *ns, const char *name,
417 const svn_string_t *val, apr_pool_t *pool)
418 {
419 apr_hash_t *ver_props, *path_props, *ns_props;
420
421 ver_props = apr_hash_get(props, &rev, sizeof(rev));
422 if (!ver_props)
423 {
424 ver_props = apr_hash_make(pool);
425 apr_hash_set(props, apr_pmemdup(pool, &rev, sizeof(rev)), sizeof(rev),
426 ver_props);
427 }
428
429 path_props = svn_hash_gets(ver_props, path);
430
431 if (!path_props)
432 {
433 path_props = apr_hash_make(pool);
434 path = apr_pstrdup(pool, path);
435 svn_hash_sets(ver_props, path, path_props);
436
437 /* todo: we know that we'll fail the next check, but fall through
438 * for now for simplicity's sake.
439 */
440 }
441
442 ns_props = svn_hash_gets(path_props, ns);
443 if (!ns_props)
444 {
445 ns_props = apr_hash_make(pool);
446 ns = apr_pstrdup(pool, ns);
447 svn_hash_sets(path_props, ns, ns_props);
448 }
449
450 svn_hash_sets(ns_props, name, val);
451 }
452
453 void
svn_ra_serf__set_prop(apr_hash_t * props,const char * path,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * pool)454 svn_ra_serf__set_prop(apr_hash_t *props,
455 const char *path,
456 const char *ns, const char *name,
457 const svn_string_t *val, apr_pool_t *pool)
458 {
459 svn_ra_serf__set_ver_prop(props, path, SVN_INVALID_REVNUM, ns, name,
460 val, pool);
461 }
462
463
464 static svn_error_t *
setup_propfind_headers(serf_bucket_t * headers,void * setup_baton,apr_pool_t * pool)465 setup_propfind_headers(serf_bucket_t *headers,
466 void *setup_baton,
467 apr_pool_t *pool)
468 {
469 propfind_context_t *ctx = setup_baton;
470
471 serf_bucket_headers_setn(headers, "Depth", ctx->depth);
472 if (ctx->label)
473 {
474 serf_bucket_headers_setn(headers, "Label", ctx->label);
475 }
476
477 return SVN_NO_ERROR;
478 }
479
480 #define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">"
481 #define PROPFIND_TRAILER "</propfind>"
482
483 static svn_error_t *
create_propfind_body(serf_bucket_t ** bkt,void * setup_baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool)484 create_propfind_body(serf_bucket_t **bkt,
485 void *setup_baton,
486 serf_bucket_alloc_t *alloc,
487 apr_pool_t *pool)
488 {
489 propfind_context_t *ctx = setup_baton;
490
491 serf_bucket_t *body_bkt, *tmp;
492 const svn_ra_serf__dav_props_t *prop;
493 svn_boolean_t requested_allprop = FALSE;
494
495 body_bkt = serf_bucket_aggregate_create(alloc);
496
497 prop = ctx->find_props;
498 while (prop && prop->namespace)
499 {
500 /* special case the allprop case. */
501 if (strcmp(prop->name, "allprop") == 0)
502 {
503 requested_allprop = TRUE;
504 }
505
506 /* <*propname* xmlns="*propns*" /> */
507 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
508 serf_bucket_aggregate_append(body_bkt, tmp);
509
510 tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
511 serf_bucket_aggregate_append(body_bkt, tmp);
512
513 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
514 sizeof(" xmlns=\"")-1,
515 alloc);
516 serf_bucket_aggregate_append(body_bkt, tmp);
517
518 tmp = SERF_BUCKET_SIMPLE_STRING(prop->namespace, alloc);
519 serf_bucket_aggregate_append(body_bkt, tmp);
520
521 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
522 alloc);
523 serf_bucket_aggregate_append(body_bkt, tmp);
524
525 prop++;
526 }
527
528 /* If we're not doing an allprop, add <prop> tags. */
529 if (!requested_allprop)
530 {
531 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
532 sizeof("<prop>")-1,
533 alloc);
534 serf_bucket_aggregate_prepend(body_bkt, tmp);
535 }
536
537 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
538 sizeof(PROPFIND_HEADER)-1,
539 alloc);
540
541 serf_bucket_aggregate_prepend(body_bkt, tmp);
542
543 if (!requested_allprop)
544 {
545 tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
546 sizeof("</prop>")-1,
547 alloc);
548 serf_bucket_aggregate_append(body_bkt, tmp);
549 }
550
551 tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
552 sizeof(PROPFIND_TRAILER)-1,
553 alloc);
554 serf_bucket_aggregate_append(body_bkt, tmp);
555
556 *bkt = body_bkt;
557 return SVN_NO_ERROR;
558 }
559
560
561 svn_error_t *
svn_ra_serf__deliver_props(svn_ra_serf__handler_t ** propfind_handler,apr_hash_t * ret_props,svn_ra_serf__session_t * sess,svn_ra_serf__connection_t * conn,const char * path,svn_revnum_t rev,const char * depth,const svn_ra_serf__dav_props_t * find_props,svn_ra_serf__list_t ** done_list,apr_pool_t * pool)562 svn_ra_serf__deliver_props(svn_ra_serf__handler_t **propfind_handler,
563 apr_hash_t *ret_props,
564 svn_ra_serf__session_t *sess,
565 svn_ra_serf__connection_t *conn,
566 const char *path,
567 svn_revnum_t rev,
568 const char *depth,
569 const svn_ra_serf__dav_props_t *find_props,
570 svn_ra_serf__list_t **done_list,
571 apr_pool_t *pool)
572 {
573 propfind_context_t *new_prop_ctx;
574 svn_ra_serf__handler_t *handler;
575 svn_ra_serf__xml_context_t *xmlctx;
576
577 new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
578
579 new_prop_ctx->pool = apr_hash_pool_get(ret_props);
580 new_prop_ctx->path = path;
581 new_prop_ctx->find_props = find_props;
582 new_prop_ctx->ret_props = ret_props;
583 new_prop_ctx->depth = depth;
584 new_prop_ctx->sess = sess;
585 new_prop_ctx->conn = conn;
586 new_prop_ctx->rev = rev;
587 new_prop_ctx->done_list = done_list;
588
589 if (SVN_IS_VALID_REVNUM(rev))
590 {
591 new_prop_ctx->label = apr_ltoa(pool, rev);
592 }
593 else
594 {
595 new_prop_ctx->label = NULL;
596 }
597
598 xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
599 propfind_opened,
600 propfind_closed,
601 NULL,
602 new_prop_ctx,
603 pool);
604 handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
605
606 handler->method = "PROPFIND";
607 handler->path = path;
608 handler->body_delegate = create_propfind_body;
609 handler->body_type = "text/xml";
610 handler->body_delegate_baton = new_prop_ctx;
611 handler->header_delegate = setup_propfind_headers;
612 handler->header_delegate_baton = new_prop_ctx;
613
614 handler->session = new_prop_ctx->sess;
615 handler->conn = new_prop_ctx->conn;
616
617 new_prop_ctx->handler = handler;
618
619 *propfind_handler = handler;
620
621 return SVN_NO_ERROR;
622 }
623
624
625 /*
626 * This helper function will block until the PROP_CTX indicates that is done
627 * or another error is returned.
628 */
629 svn_error_t *
svn_ra_serf__wait_for_props(svn_ra_serf__handler_t * handler,apr_pool_t * scratch_pool)630 svn_ra_serf__wait_for_props(svn_ra_serf__handler_t *handler,
631 apr_pool_t *scratch_pool)
632 {
633 svn_error_t *err;
634 svn_error_t *err2;
635
636 err = svn_ra_serf__context_run_one(handler, scratch_pool);
637
638 err2 = svn_ra_serf__error_on_status(handler->sline,
639 handler->path,
640 handler->location);
641
642 return svn_error_compose_create(err2, err);
643 }
644
645 /*
646 * This is a blocking version of deliver_props.
647 */
648 svn_error_t *
svn_ra_serf__retrieve_props(apr_hash_t ** results,svn_ra_serf__session_t * sess,svn_ra_serf__connection_t * conn,const char * url,svn_revnum_t rev,const char * depth,const svn_ra_serf__dav_props_t * props,apr_pool_t * result_pool,apr_pool_t * scratch_pool)649 svn_ra_serf__retrieve_props(apr_hash_t **results,
650 svn_ra_serf__session_t *sess,
651 svn_ra_serf__connection_t *conn,
652 const char *url,
653 svn_revnum_t rev,
654 const char *depth,
655 const svn_ra_serf__dav_props_t *props,
656 apr_pool_t *result_pool,
657 apr_pool_t *scratch_pool)
658 {
659 svn_ra_serf__handler_t *handler;
660
661 *results = apr_hash_make(result_pool);
662
663 SVN_ERR(svn_ra_serf__deliver_props(&handler, *results, sess, conn, url,
664 rev, depth, props, NULL, result_pool));
665 SVN_ERR(svn_ra_serf__wait_for_props(handler, scratch_pool));
666
667 return SVN_NO_ERROR;
668 }
669
670
671 svn_error_t *
svn_ra_serf__fetch_node_props(apr_hash_t ** results,svn_ra_serf__connection_t * conn,const char * url,svn_revnum_t revision,const svn_ra_serf__dav_props_t * which_props,apr_pool_t * result_pool,apr_pool_t * scratch_pool)672 svn_ra_serf__fetch_node_props(apr_hash_t **results,
673 svn_ra_serf__connection_t *conn,
674 const char *url,
675 svn_revnum_t revision,
676 const svn_ra_serf__dav_props_t *which_props,
677 apr_pool_t *result_pool,
678 apr_pool_t *scratch_pool)
679 {
680 apr_hash_t *multiprops;
681 apr_hash_t *ver_props;
682
683 /* Note: a couple extra hash tables and whatnot get into RESULT_POOL.
684 Not a big deal at this point. Theoretically, we could fetch all
685 props into SCRATCH_POOL, then copy just the REVISION/URL props
686 into RESULT_POOL. Too much work for too little gain... */
687 SVN_ERR(svn_ra_serf__retrieve_props(&multiprops, conn->session, conn,
688 url, revision, "0", which_props,
689 result_pool, scratch_pool));
690
691 ver_props = apr_hash_get(multiprops, &revision, sizeof(revision));
692 if (ver_props != NULL)
693 {
694 *results = svn_hash_gets(ver_props, url);
695 if (*results != NULL)
696 return SVN_NO_ERROR;
697 }
698
699 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
700 _("The PROPFIND response did not include "
701 "the requested properties"));
702 }
703
704
705 svn_error_t *
svn_ra_serf__walk_node_props(apr_hash_t * props,svn_ra_serf__walker_visitor_t walker,void * baton,apr_pool_t * scratch_pool)706 svn_ra_serf__walk_node_props(apr_hash_t *props,
707 svn_ra_serf__walker_visitor_t walker,
708 void *baton,
709 apr_pool_t *scratch_pool)
710 {
711 apr_pool_t *iterpool;
712 apr_hash_index_t *ns_hi;
713
714 iterpool = svn_pool_create(scratch_pool);
715 for (ns_hi = apr_hash_first(scratch_pool, props); ns_hi;
716 ns_hi = apr_hash_next(ns_hi))
717 {
718 void *ns_val;
719 const void *ns_name;
720 apr_hash_index_t *name_hi;
721
722 /* NOTE: We do not clear ITERPOOL in this loop. Generally, there are
723 very few namespaces, so this loop will not have many iterations.
724 Instead, ITERPOOL is used for the inner loop. */
725
726 apr_hash_this(ns_hi, &ns_name, NULL, &ns_val);
727
728 for (name_hi = apr_hash_first(scratch_pool, ns_val); name_hi;
729 name_hi = apr_hash_next(name_hi))
730 {
731 void *prop_val;
732 const void *prop_name;
733
734 /* See note above, regarding clearing of this pool. */
735 svn_pool_clear(iterpool);
736
737 apr_hash_this(name_hi, &prop_name, NULL, &prop_val);
738
739 SVN_ERR(walker(baton, ns_name, prop_name, prop_val, iterpool));
740 }
741 }
742 svn_pool_destroy(iterpool);
743
744 return SVN_NO_ERROR;
745 }
746
747
748 svn_error_t *
svn_ra_serf__walk_all_props(apr_hash_t * props,const char * name,svn_revnum_t rev,svn_ra_serf__walker_visitor_t walker,void * baton,apr_pool_t * scratch_pool)749 svn_ra_serf__walk_all_props(apr_hash_t *props,
750 const char *name,
751 svn_revnum_t rev,
752 svn_ra_serf__walker_visitor_t walker,
753 void *baton,
754 apr_pool_t *scratch_pool)
755 {
756 apr_hash_t *ver_props;
757 apr_hash_t *path_props;
758
759 ver_props = apr_hash_get(props, &rev, sizeof(rev));
760 if (!ver_props)
761 return SVN_NO_ERROR;
762
763 path_props = svn_hash_gets(ver_props, name);
764 if (!path_props)
765 return SVN_NO_ERROR;
766
767 return svn_error_trace(svn_ra_serf__walk_node_props(path_props,
768 walker, baton,
769 scratch_pool));
770 }
771
772
773 svn_error_t *
svn_ra_serf__walk_all_paths(apr_hash_t * props,svn_revnum_t rev,svn_ra_serf__path_rev_walker_t walker,void * baton,apr_pool_t * pool)774 svn_ra_serf__walk_all_paths(apr_hash_t *props,
775 svn_revnum_t rev,
776 svn_ra_serf__path_rev_walker_t walker,
777 void *baton,
778 apr_pool_t *pool)
779 {
780 apr_hash_index_t *path_hi;
781 apr_hash_t *ver_props;
782
783 ver_props = apr_hash_get(props, &rev, sizeof(rev));
784
785 if (!ver_props)
786 {
787 return SVN_NO_ERROR;
788 }
789
790 for (path_hi = apr_hash_first(pool, ver_props); path_hi;
791 path_hi = apr_hash_next(path_hi))
792 {
793 void *path_props;
794 const void *path_name;
795 apr_ssize_t path_len;
796 apr_hash_index_t *ns_hi;
797
798 apr_hash_this(path_hi, &path_name, &path_len, &path_props);
799 for (ns_hi = apr_hash_first(pool, path_props); ns_hi;
800 ns_hi = apr_hash_next(ns_hi))
801 {
802 void *ns_val;
803 const void *ns_name;
804 apr_ssize_t ns_len;
805 apr_hash_index_t *name_hi;
806 apr_hash_this(ns_hi, &ns_name, &ns_len, &ns_val);
807 for (name_hi = apr_hash_first(pool, ns_val); name_hi;
808 name_hi = apr_hash_next(name_hi))
809 {
810 void *prop_val;
811 const void *prop_name;
812 apr_ssize_t prop_len;
813
814 apr_hash_this(name_hi, &prop_name, &prop_len, &prop_val);
815 /* use a subpool? */
816 SVN_ERR(walker(baton, path_name, path_len, ns_name, ns_len,
817 prop_name, prop_len, prop_val, pool));
818 }
819 }
820 }
821
822 return SVN_NO_ERROR;
823 }
824
825
826 const char *
svn_ra_serf__svnname_from_wirename(const char * ns,const char * name,apr_pool_t * result_pool)827 svn_ra_serf__svnname_from_wirename(const char *ns,
828 const char *name,
829 apr_pool_t *result_pool)
830 {
831 if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
832 return apr_pstrdup(result_pool, name);
833
834 if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
835 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
836
837 if (strcmp(ns, SVN_PROP_PREFIX) == 0)
838 return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
839
840 if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
841 return SVN_PROP_ENTRY_COMMITTED_REV;
842
843 if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
844 return SVN_PROP_ENTRY_COMMITTED_DATE;
845
846 if (strcmp(name, "creator-displayname") == 0)
847 return SVN_PROP_ENTRY_LAST_AUTHOR;
848
849 if (strcmp(name, "repository-uuid") == 0)
850 return SVN_PROP_ENTRY_UUID;
851
852 if (strcmp(name, "lock-token") == 0)
853 return SVN_PROP_ENTRY_LOCK_TOKEN;
854
855 if (strcmp(name, "checked-in") == 0)
856 return SVN_RA_SERF__WC_CHECKED_IN_URL;
857
858 if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
859 {
860 /* Here DAV: properties not yet converted to svn: properties should be
861 ignored. */
862 return NULL;
863 }
864
865 /* An unknown namespace, must be a custom property. */
866 return apr_pstrcat(result_pool, ns, name, (char *)NULL);
867 }
868
869
870 /* Conforms to svn_ra_serf__walker_visitor_t */
871 static svn_error_t *
set_flat_props(void * baton,const char * ns,const char * name,const svn_string_t * value,apr_pool_t * pool)872 set_flat_props(void *baton,
873 const char *ns,
874 const char *name,
875 const svn_string_t *value,
876 apr_pool_t *pool)
877 {
878 apr_hash_t *props = baton;
879 apr_pool_t *result_pool = apr_hash_pool_get(props);
880 const char *prop_name;
881
882 /* ### is VAL in the proper pool? */
883
884 prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
885 if (prop_name != NULL)
886 svn_hash_sets(props, prop_name, value);
887
888 return SVN_NO_ERROR;
889 }
890
891
892 svn_error_t *
svn_ra_serf__flatten_props(apr_hash_t ** flat_props,apr_hash_t * props,apr_pool_t * result_pool,apr_pool_t * scratch_pool)893 svn_ra_serf__flatten_props(apr_hash_t **flat_props,
894 apr_hash_t *props,
895 apr_pool_t *result_pool,
896 apr_pool_t *scratch_pool)
897 {
898 *flat_props = apr_hash_make(result_pool);
899
900 return svn_error_trace(svn_ra_serf__walk_node_props(
901 props,
902 set_flat_props,
903 *flat_props /* baton */,
904 scratch_pool));
905 }
906
907
908 static svn_error_t *
select_revprops(void * baton,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * scratch_pool)909 select_revprops(void *baton,
910 const char *ns,
911 const char *name,
912 const svn_string_t *val,
913 apr_pool_t *scratch_pool)
914 {
915 apr_hash_t *revprops = baton;
916 apr_pool_t *result_pool = apr_hash_pool_get(revprops);
917 const char *prop_name;
918
919 /* ### copy NAME into the RESULT_POOL? */
920 /* ### copy VAL into the RESULT_POOL? */
921
922 if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
923 prop_name = name;
924 else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
925 prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
926 else if (strcmp(ns, SVN_PROP_PREFIX) == 0)
927 prop_name = apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, (char *)NULL);
928 else if (strcmp(ns, "") == 0)
929 prop_name = name;
930 else
931 {
932 /* do nothing for now? */
933 return SVN_NO_ERROR;
934 }
935
936 svn_hash_sets(revprops, prop_name, val);
937
938 return SVN_NO_ERROR;
939 }
940
941
942 svn_error_t *
svn_ra_serf__select_revprops(apr_hash_t ** revprops,const char * name,svn_revnum_t rev,apr_hash_t * all_revprops,apr_pool_t * result_pool,apr_pool_t * scratch_pool)943 svn_ra_serf__select_revprops(apr_hash_t **revprops,
944 const char *name,
945 svn_revnum_t rev,
946 apr_hash_t *all_revprops,
947 apr_pool_t *result_pool,
948 apr_pool_t *scratch_pool)
949 {
950 *revprops = apr_hash_make(result_pool);
951
952 return svn_error_trace(svn_ra_serf__walk_all_props(
953 all_revprops, name, rev,
954 select_revprops, *revprops,
955 scratch_pool));
956 }
957
958
959 /*
960 * Contact the server (using CONN) to calculate baseline
961 * information for BASELINE_URL at REVISION (which may be
962 * SVN_INVALID_REVNUM to query the HEAD revision).
963 *
964 * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
965 * retrieved from the server as part of this process (which should
966 * match REVISION when REVISION is valid). Set *BASECOLL_URL_P to the
967 * baseline collection URL.
968 */
969 static svn_error_t *
retrieve_baseline_info(svn_revnum_t * actual_revision,const char ** basecoll_url_p,svn_ra_serf__connection_t * conn,const char * baseline_url,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)970 retrieve_baseline_info(svn_revnum_t *actual_revision,
971 const char **basecoll_url_p,
972 svn_ra_serf__connection_t *conn,
973 const char *baseline_url,
974 svn_revnum_t revision,
975 apr_pool_t *result_pool,
976 apr_pool_t *scratch_pool)
977 {
978 apr_hash_t *props;
979 apr_hash_t *dav_props;
980 const char *basecoll_url;
981
982 SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn,
983 baseline_url, revision,
984 baseline_props,
985 scratch_pool, scratch_pool));
986 dav_props = apr_hash_get(props, "DAV:", 4);
987 /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL. */
988
989 basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
990 if (!basecoll_url)
991 {
992 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
993 _("The PROPFIND response did not include "
994 "the requested baseline-collection value"));
995 }
996 *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
997
998 if (actual_revision)
999 {
1000 const char *version_name;
1001
1002 version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
1003 if (!version_name)
1004 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
1005 _("The PROPFIND response did not include "
1006 "the requested version-name value"));
1007
1008 *actual_revision = SVN_STR_TO_REV(version_name);
1009 }
1010
1011 return SVN_NO_ERROR;
1012 }
1013
1014
1015 /* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
1016 revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
1017 collection URL is also returned.
1018
1019 Do the work over CONN.
1020
1021 *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
1022 temporary allocations will be made in SCRATCH_POOL. */
1023 static svn_error_t *
v1_get_youngest_revnum(svn_revnum_t * youngest,const char ** basecoll_url,svn_ra_serf__connection_t * conn,const char * vcc_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1024 v1_get_youngest_revnum(svn_revnum_t *youngest,
1025 const char **basecoll_url,
1026 svn_ra_serf__connection_t *conn,
1027 const char *vcc_url,
1028 apr_pool_t *result_pool,
1029 apr_pool_t *scratch_pool)
1030 {
1031 const char *baseline_url;
1032 const char *bc_url;
1033
1034 /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
1035 revision) will return the latest Baseline resource's URL. */
1036 SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, conn, vcc_url,
1037 SVN_INVALID_REVNUM,
1038 "checked-in",
1039 scratch_pool, scratch_pool));
1040 if (!baseline_url)
1041 {
1042 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1043 _("The OPTIONS response did not include "
1044 "the requested checked-in value"));
1045 }
1046 baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
1047
1048 /* From the Baseline resource, we can fetch the DAV:baseline-collection
1049 and DAV:version-name properties. The latter is the revision number,
1050 which is formally the name used in Label: headers. */
1051
1052 /* First check baseline information cache. */
1053 SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
1054 youngest,
1055 conn->session->blncache,
1056 baseline_url,
1057 scratch_pool));
1058 if (!bc_url)
1059 {
1060 SVN_ERR(retrieve_baseline_info(youngest, &bc_url, conn,
1061 baseline_url, SVN_INVALID_REVNUM,
1062 scratch_pool, scratch_pool));
1063 SVN_ERR(svn_ra_serf__blncache_set(conn->session->blncache,
1064 baseline_url, *youngest,
1065 bc_url, scratch_pool));
1066 }
1067
1068 if (basecoll_url != NULL)
1069 *basecoll_url = apr_pstrdup(result_pool, bc_url);
1070
1071 return SVN_NO_ERROR;
1072 }
1073
1074
1075 svn_error_t *
svn_ra_serf__get_youngest_revnum(svn_revnum_t * youngest,svn_ra_serf__session_t * session,apr_pool_t * scratch_pool)1076 svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
1077 svn_ra_serf__session_t *session,
1078 apr_pool_t *scratch_pool)
1079 {
1080 const char *vcc_url;
1081
1082 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
1083 return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
1084 youngest, session->conns[0], scratch_pool));
1085
1086 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, NULL, scratch_pool));
1087
1088 return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
1089 session->conns[0], vcc_url,
1090 scratch_pool, scratch_pool));
1091 }
1092
1093
1094 /* Set *BC_URL to the baseline collection url for REVISION. If REVISION
1095 is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
1096
1097 *REVNUM_USED will be set to the revision used.
1098
1099 Uses the specified CONN, which is part of SESSION.
1100
1101 All allocations (results and temporary) are performed in POOL. */
1102 static svn_error_t *
get_baseline_info(const char ** bc_url,svn_revnum_t * revnum_used,svn_ra_serf__session_t * session,svn_ra_serf__connection_t * conn,svn_revnum_t revision,apr_pool_t * pool)1103 get_baseline_info(const char **bc_url,
1104 svn_revnum_t *revnum_used,
1105 svn_ra_serf__session_t *session,
1106 svn_ra_serf__connection_t *conn,
1107 svn_revnum_t revision,
1108 apr_pool_t *pool)
1109 {
1110 /* If we detected HTTP v2 support on the server, we can construct
1111 the baseline collection URL ourselves, and fetch the latest
1112 revision (if needed) with an OPTIONS request. */
1113 if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
1114 {
1115 if (SVN_IS_VALID_REVNUM(revision))
1116 {
1117 *revnum_used = revision;
1118 }
1119 else
1120 {
1121 SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
1122 revnum_used, conn, pool));
1123 if (! SVN_IS_VALID_REVNUM(*revnum_used))
1124 return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
1125 _("The OPTIONS response did not include "
1126 "the youngest revision"));
1127 }
1128
1129 *bc_url = apr_psprintf(pool, "%s/%ld",
1130 session->rev_root_stub, *revnum_used);
1131 }
1132
1133 /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt. */
1134 else
1135 {
1136 const char *vcc_url;
1137
1138 SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, conn, pool));
1139
1140 if (SVN_IS_VALID_REVNUM(revision))
1141 {
1142 /* First check baseline information cache. */
1143 SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
1144 session->blncache,
1145 revision, pool));
1146 if (!*bc_url)
1147 {
1148 SVN_ERR(retrieve_baseline_info(NULL, bc_url, conn,
1149 vcc_url, revision, pool, pool));
1150 SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
1151 revision, *bc_url, pool));
1152 }
1153
1154 *revnum_used = revision;
1155 }
1156 else
1157 {
1158 SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
1159 conn, vcc_url,
1160 pool, pool));
1161 }
1162 }
1163
1164 return SVN_NO_ERROR;
1165 }
1166
1167
1168 svn_error_t *
svn_ra_serf__get_stable_url(const char ** stable_url,svn_revnum_t * latest_revnum,svn_ra_serf__session_t * session,svn_ra_serf__connection_t * conn,const char * url,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1169 svn_ra_serf__get_stable_url(const char **stable_url,
1170 svn_revnum_t *latest_revnum,
1171 svn_ra_serf__session_t *session,
1172 svn_ra_serf__connection_t *conn,
1173 const char *url,
1174 svn_revnum_t revision,
1175 apr_pool_t *result_pool,
1176 apr_pool_t *scratch_pool)
1177 {
1178 const char *basecoll_url;
1179 const char *repos_relpath;
1180 svn_revnum_t revnum_used;
1181
1182 /* No URL? No sweat. We'll use the session URL. */
1183 if (! url)
1184 url = session->session_url.path;
1185
1186 /* If the caller didn't provide a specific connection for us to use,
1187 we'll use the default connection. */
1188 if (! conn)
1189 conn = session->conns[0];
1190
1191 SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
1192 session, conn, revision, scratch_pool));
1193 SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
1194 session, conn, scratch_pool));
1195
1196 *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
1197 result_pool);
1198 if (latest_revnum)
1199 *latest_revnum = revnum_used;
1200
1201 return SVN_NO_ERROR;
1202 }
1203
1204
1205 svn_error_t *
svn_ra_serf__get_resource_type(svn_node_kind_t * kind,apr_hash_t * props)1206 svn_ra_serf__get_resource_type(svn_node_kind_t *kind,
1207 apr_hash_t *props)
1208 {
1209 apr_hash_t *dav_props;
1210 const char *res_type;
1211
1212 dav_props = apr_hash_get(props, "DAV:", 4);
1213 res_type = svn_prop_get_value(dav_props, "resourcetype");
1214 if (!res_type)
1215 {
1216 /* How did this happen? */
1217 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
1218 _("The PROPFIND response did not include the "
1219 "requested resourcetype value"));
1220 }
1221
1222 if (strcmp(res_type, "collection") == 0)
1223 {
1224 *kind = svn_node_dir;
1225 }
1226 else
1227 {
1228 *kind = svn_node_file;
1229 }
1230
1231 return SVN_NO_ERROR;
1232 }
1233
1234
1235 svn_error_t *
svn_ra_serf__fetch_dav_prop(const char ** value,svn_ra_serf__connection_t * conn,const char * url,svn_revnum_t revision,const char * propname,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1236 svn_ra_serf__fetch_dav_prop(const char **value,
1237 svn_ra_serf__connection_t *conn,
1238 const char *url,
1239 svn_revnum_t revision,
1240 const char *propname,
1241 apr_pool_t *result_pool,
1242 apr_pool_t *scratch_pool)
1243 {
1244 apr_hash_t *props;
1245 apr_hash_t *dav_props;
1246
1247 SVN_ERR(svn_ra_serf__fetch_node_props(&props, conn, url, revision,
1248 checked_in_props,
1249 scratch_pool, scratch_pool));
1250 dav_props = apr_hash_get(props, "DAV:", 4);
1251 if (dav_props == NULL)
1252 return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
1253 _("The PROPFIND response did not include "
1254 "the requested 'DAV:' properties"));
1255
1256 /* We wouldn't get here if the resource was not found (404), so the
1257 property should be present.
1258
1259 Note: it is okay to call apr_pstrdup() with NULL. */
1260 *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
1261
1262 return SVN_NO_ERROR;
1263 }
1264