]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/property.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_serf / property.c
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 \f
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 \f
43 /* Our current parsing state we're in for the PROPFIND response. */
44 typedef enum prop_state_e {
45   INITIAL = XML_STATE_INITIAL,
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   svn_ra_serf__handler_t *handler;
63
64   /* the requested path */
65   const char *path;
66
67   /* the requested version (in string form) */
68   const char *label;
69
70   /* the request depth */
71   const char *depth;
72
73   /* the list of requested properties */
74   const svn_ra_serf__dav_props_t *find_props;
75
76   svn_ra_serf__prop_func_t prop_func;
77   void *prop_func_baton;
78
79   /* hash table containing all the properties associated with the
80    * "current" <propstat> tag.  These will get copied into RET_PROPS
81    * if the status code similarly associated indicates that they are
82    * "good"; otherwise, they'll get discarded.
83    */
84   apr_hash_t *ps_props;
85 } propfind_context_t;
86
87
88 #define D_ "DAV:"
89 #define S_ SVN_XML_NAMESPACE
90 static const svn_ra_serf__xml_transition_t propfind_ttable[] = {
91   { INITIAL, D_, "multistatus", MULTISTATUS,
92     FALSE, { NULL }, TRUE },
93
94   { MULTISTATUS, D_, "response", RESPONSE,
95     FALSE, { NULL }, FALSE },
96
97   { RESPONSE, D_, "href", HREF,
98     TRUE, { NULL }, TRUE },
99
100   { RESPONSE, D_, "propstat", PROPSTAT,
101     FALSE, { NULL }, TRUE },
102
103   { PROPSTAT, D_, "status", STATUS,
104     TRUE, { NULL }, TRUE },
105
106   { PROPSTAT, D_, "prop", PROP,
107     FALSE, { NULL }, FALSE },
108
109   { PROP, "*", "*", PROPVAL,
110     TRUE, { "?V:encoding", NULL }, TRUE },
111
112   { PROPVAL, D_, "collection", COLLECTION,
113     FALSE, { NULL }, TRUE },
114
115   { PROPVAL, D_, "href", HREF_VALUE,
116     TRUE, { NULL }, TRUE },
117
118   { 0 }
119 };
120
121 static const int propfind_expected_status[] = {
122   207,
123   0
124 };
125
126 /* Return the HTTP status code contained in STATUS_LINE, or 0 if
127    there's a problem parsing it. */
128 static apr_int64_t parse_status_code(const char *status_line)
129 {
130   /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */
131   if (status_line[0] == 'H' &&
132       status_line[1] == 'T' &&
133       status_line[2] == 'T' &&
134       status_line[3] == 'P' &&
135       status_line[4] == '/' &&
136       (status_line[5] >= '0' && status_line[5] <= '9') &&
137       status_line[6] == '.' &&
138       (status_line[7] >= '0' && status_line[7] <= '9') &&
139       status_line[8] == ' ')
140     {
141       char *reason;
142
143       return apr_strtoi64(status_line + 8, &reason, 10);
144     }
145   return 0;
146 }
147
148 /* Conforms to svn_ra_serf__xml_opened_t  */
149 static svn_error_t *
150 propfind_opened(svn_ra_serf__xml_estate_t *xes,
151                 void *baton,
152                 int entered_state,
153                 const svn_ra_serf__dav_props_t *tag,
154                 apr_pool_t *scratch_pool)
155 {
156   propfind_context_t *ctx = baton;
157
158   if (entered_state == PROPVAL)
159     {
160         svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->xmlns);
161       svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
162     }
163   else if (entered_state == PROPSTAT)
164     {
165       ctx->ps_props = apr_hash_make(svn_ra_serf__xml_state_pool(xes));
166     }
167
168   return SVN_NO_ERROR;
169 }
170
171 /* Set PROPS for NS:NAME VAL. Helper for propfind_closed */
172 static void
173 set_ns_prop(apr_hash_t *ns_props,
174             const char *ns, const char *name,
175             const svn_string_t *val, apr_pool_t *result_pool)
176 {
177   apr_hash_t *props = svn_hash_gets(ns_props, ns);
178
179   if (!props)
180     {
181       props = apr_hash_make(result_pool);
182       ns = apr_pstrdup(result_pool, ns);
183       svn_hash_sets(ns_props, ns, props);
184     }
185
186   if (val)
187     {
188       name = apr_pstrdup(result_pool, name);
189       val = svn_string_dup(val, result_pool);
190     }
191
192   svn_hash_sets(props, name, val);
193 }
194
195 /* Conforms to svn_ra_serf__xml_closed_t  */
196 static svn_error_t *
197 propfind_closed(svn_ra_serf__xml_estate_t *xes,
198                 void *baton,
199                 int leaving_state,
200                 const svn_string_t *cdata,
201                 apr_hash_t *attrs,
202                 apr_pool_t *scratch_pool)
203 {
204   propfind_context_t *ctx = baton;
205
206   if (leaving_state == MULTISTATUS)
207     {
208       /* We've gathered all the data from the reponse. Add this item
209          onto the "done list". External callers will then know this
210          request has been completed (tho stray response bytes may still
211          arrive).  */
212     }
213   else if (leaving_state == HREF)
214     {
215       const char *path;
216
217       if (strcmp(ctx->depth, "1") == 0)
218         path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
219       else
220         path = ctx->path;
221
222       svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
223
224       SVN_ERR(ctx->prop_func(ctx->prop_func_baton,
225                              path,
226                              D_, "href",
227                              cdata, scratch_pool));
228     }
229   else if (leaving_state == COLLECTION)
230     {
231       svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
232     }
233   else if (leaving_state == HREF_VALUE)
234     {
235       svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
236     }
237   else if (leaving_state == STATUS)
238     {
239       /* Parse the status field, and remember if this is a property
240          that we wish to ignore.  (Typically, if it's not a 200, the
241          status will be 404 to indicate that a property we
242          specifically requested from the server doesn't exist.)  */
243       apr_int64_t status = parse_status_code(cdata->data);
244       if (status != 200)
245         svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
246     }
247   else if (leaving_state == PROPVAL)
248     {
249       const char *encoding;
250       const svn_string_t *val_str;
251       const char *ns;
252       const char *name;
253       const char *altvalue;
254
255       if ((altvalue = svn_hash_gets(attrs, "altvalue")) != NULL)
256         {
257           val_str = svn_string_create(altvalue, scratch_pool);
258         }
259       else if ((encoding = svn_hash_gets(attrs, "V:encoding")) != NULL)
260         {
261           if (strcmp(encoding, "base64") != 0)
262             return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
263                                      NULL,
264                                      _("Got unrecognized encoding '%s'"),
265                                      encoding);
266
267           /* Decode into the right pool.  */
268           val_str = svn_base64_decode_string(cdata, scratch_pool);
269         }
270       else
271         {
272           /* Copy into the right pool.  */
273           val_str = cdata;
274         }
275
276       /* The current path sits on the RESPONSE state.
277
278          Now, it would be nice if we could, at this point, know that
279          the status code for this property indicated a problem -- then
280          we could simply bail out here and ignore the property.
281          Sadly, though, we might get the status code *after* we get
282          the property value.  So we'll carry on with our processing
283          here, setting the property and value as expected.  Once we
284          know for sure the status code associate with the property,
285          we'll decide its fate.  */
286
287       ns = svn_hash_gets(attrs, "ns");
288       name = svn_hash_gets(attrs, "name");
289
290       set_ns_prop(ctx->ps_props, ns, name, val_str,
291                   apr_hash_pool_get(ctx->ps_props));
292     }
293   else
294     {
295       apr_hash_t *gathered;
296
297       SVN_ERR_ASSERT(leaving_state == PROPSTAT);
298
299       gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE);
300
301       /* If we've squirreled away a note that says we want to ignore
302          these properties, we'll do so.  Otherwise, we need to copy
303          them from the temporary hash into the ctx->ret_props hash. */
304       if (! svn_hash_gets(gathered, "ignore-prop"))
305         {
306           apr_hash_index_t *hi_ns;
307           const char *path;
308           apr_pool_t *iterpool = svn_pool_create(scratch_pool);
309
310
311           path = svn_hash_gets(gathered, "path");
312           if (!path)
313             path = ctx->path;
314
315           for (hi_ns = apr_hash_first(scratch_pool, ctx->ps_props);
316                hi_ns;
317                hi_ns = apr_hash_next(hi_ns))
318             {
319               const char *ns = apr_hash_this_key(hi_ns);
320               apr_hash_t *props = apr_hash_this_val(hi_ns);
321               apr_hash_index_t *hi_prop;
322
323               svn_pool_clear(iterpool);
324
325               for (hi_prop = apr_hash_first(iterpool, props);
326                    hi_prop;
327                    hi_prop = apr_hash_next(hi_prop))
328                 {
329                   const char *name = apr_hash_this_key(hi_prop);
330                   const svn_string_t *value = apr_hash_this_val(hi_prop);
331
332                   SVN_ERR(ctx->prop_func(ctx->prop_func_baton, path,
333                                          ns, name, value, iterpool));
334                 }
335             }
336
337           svn_pool_destroy(iterpool);
338         }
339
340       ctx->ps_props = NULL; /* Allocated in PROPSTAT state pool */
341     }
342
343   return SVN_NO_ERROR;
344 }
345
346
347
348 static svn_error_t *
349 setup_propfind_headers(serf_bucket_t *headers,
350                        void *setup_baton,
351                        apr_pool_t *pool /* request pool */,
352                        apr_pool_t *scratch_pool)
353 {
354   propfind_context_t *ctx = setup_baton;
355
356   serf_bucket_headers_setn(headers, "Depth", ctx->depth);
357   if (ctx->label)
358     {
359       serf_bucket_headers_setn(headers, "Label", ctx->label);
360     }
361
362   return SVN_NO_ERROR;
363 }
364
365 #define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">"
366 #define PROPFIND_TRAILER "</propfind>"
367
368 /* Implements svn_ra_serf__request_body_delegate_t */
369 static svn_error_t *
370 create_propfind_body(serf_bucket_t **bkt,
371                      void *setup_baton,
372                      serf_bucket_alloc_t *alloc,
373                      apr_pool_t *pool /* request pool */,
374                      apr_pool_t *scratch_pool)
375 {
376   propfind_context_t *ctx = setup_baton;
377
378   serf_bucket_t *body_bkt, *tmp;
379   const svn_ra_serf__dav_props_t *prop;
380   svn_boolean_t requested_allprop = FALSE;
381
382   body_bkt = serf_bucket_aggregate_create(alloc);
383
384   prop = ctx->find_props;
385   while (prop && prop->xmlns)
386     {
387       /* special case the allprop case. */
388       if (strcmp(prop->name, "allprop") == 0)
389         {
390           requested_allprop = TRUE;
391         }
392
393       /* <*propname* xmlns="*propns*" /> */
394       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
395       serf_bucket_aggregate_append(body_bkt, tmp);
396
397       tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
398       serf_bucket_aggregate_append(body_bkt, tmp);
399
400       tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
401                                           sizeof(" xmlns=\"")-1,
402                                           alloc);
403       serf_bucket_aggregate_append(body_bkt, tmp);
404
405       tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc);
406       serf_bucket_aggregate_append(body_bkt, tmp);
407
408       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
409                                           alloc);
410       serf_bucket_aggregate_append(body_bkt, tmp);
411
412       prop++;
413     }
414
415   /* If we're not doing an allprop, add <prop> tags. */
416   if (!requested_allprop)
417     {
418       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
419                                           sizeof("<prop>")-1,
420                                           alloc);
421       serf_bucket_aggregate_prepend(body_bkt, tmp);
422     }
423
424   tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
425                                       sizeof(PROPFIND_HEADER)-1,
426                                       alloc);
427
428   serf_bucket_aggregate_prepend(body_bkt, tmp);
429
430   if (!requested_allprop)
431     {
432       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
433                                           sizeof("</prop>")-1,
434                                           alloc);
435       serf_bucket_aggregate_append(body_bkt, tmp);
436     }
437
438   tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
439                                       sizeof(PROPFIND_TRAILER)-1,
440                                       alloc);
441   serf_bucket_aggregate_append(body_bkt, tmp);
442
443   *bkt = body_bkt;
444   return SVN_NO_ERROR;
445 }
446
447
448 svn_error_t *
449 svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler,
450                                      svn_ra_serf__session_t *sess,
451                                      const char *path,
452                                      svn_revnum_t rev,
453                                      const char *depth,
454                                      const svn_ra_serf__dav_props_t *find_props,
455                                      svn_ra_serf__prop_func_t prop_func,
456                                      void *prop_func_baton,
457                                      apr_pool_t *pool)
458 {
459   propfind_context_t *new_prop_ctx;
460   svn_ra_serf__handler_t *handler;
461   svn_ra_serf__xml_context_t *xmlctx;
462
463   new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
464
465   new_prop_ctx->path = path;
466   new_prop_ctx->find_props = find_props;
467   new_prop_ctx->prop_func = prop_func;
468   new_prop_ctx->prop_func_baton = prop_func_baton;
469   new_prop_ctx->depth = depth;
470
471   if (SVN_IS_VALID_REVNUM(rev))
472     {
473       new_prop_ctx->label = apr_ltoa(pool, rev);
474     }
475   else
476     {
477       new_prop_ctx->label = NULL;
478     }
479
480   xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
481                                            propfind_opened,
482                                            propfind_closed,
483                                            NULL,
484                                            new_prop_ctx,
485                                            pool);
486   handler = svn_ra_serf__create_expat_handler(sess, xmlctx,
487                                               propfind_expected_status,
488                                               pool);
489
490   handler->method = "PROPFIND";
491   handler->path = path;
492   handler->body_delegate = create_propfind_body;
493   handler->body_type = "text/xml";
494   handler->body_delegate_baton = new_prop_ctx;
495   handler->header_delegate = setup_propfind_headers;
496   handler->header_delegate_baton = new_prop_ctx;
497
498   handler->no_dav_headers = TRUE;
499
500   new_prop_ctx->handler = handler;
501
502   *propfind_handler = handler;
503
504   return SVN_NO_ERROR;
505 }
506
507 svn_error_t *
508 svn_ra_serf__deliver_svn_props(void *baton,
509                                const char *path,
510                                const char *ns,
511                                const char *name,
512                                const svn_string_t *value,
513                                apr_pool_t *scratch_pool)
514 {
515   apr_hash_t *props = baton;
516   apr_pool_t *result_pool = apr_hash_pool_get(props);
517   const char *prop_name;
518
519   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
520   if (prop_name == NULL)
521     return SVN_NO_ERROR;
522
523   svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool));
524
525   return SVN_NO_ERROR;
526 }
527
528 /*
529  * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties
530  * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash
531  *    (const char * -> svn_string_t *) to the values.
532  */
533 static svn_error_t *
534 deliver_node_props(void *baton,
535                   const char *path,
536                   const char *ns,
537                   const char *name,
538                   const svn_string_t *value,
539                   apr_pool_t *scratch_pool)
540 {
541   apr_hash_t *nss = baton;
542   apr_hash_t *props;
543   apr_pool_t *result_pool = apr_hash_pool_get(nss);
544
545   props = svn_hash_gets(nss, ns);
546
547   if (!props)
548     {
549       props = apr_hash_make(result_pool);
550
551       ns = apr_pstrdup(result_pool, ns);
552       svn_hash_sets(nss, ns, props);
553     }
554
555   name = apr_pstrdup(result_pool, name);
556   svn_hash_sets(props, name, svn_string_dup(value, result_pool));
557
558   return SVN_NO_ERROR;
559 }
560
561 svn_error_t *
562 svn_ra_serf__fetch_node_props(apr_hash_t **results,
563                               svn_ra_serf__session_t *session,
564                               const char *url,
565                               svn_revnum_t revision,
566                               const svn_ra_serf__dav_props_t *which_props,
567                               apr_pool_t *result_pool,
568                               apr_pool_t *scratch_pool)
569 {
570   apr_hash_t *props;
571   svn_ra_serf__handler_t *handler;
572
573   props = apr_hash_make(result_pool);
574
575   SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
576                                                url, revision, "0", which_props,
577                                                deliver_node_props,
578                                                props, scratch_pool));
579
580   SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
581
582   *results = props;
583   return SVN_NO_ERROR;
584 }
585
586 const char *
587 svn_ra_serf__svnname_from_wirename(const char *ns,
588                                    const char *name,
589                                    apr_pool_t *result_pool)
590 {
591   if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
592     return apr_pstrdup(result_pool, name);
593
594   if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
595     return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
596
597   if (strcmp(ns, SVN_PROP_PREFIX) == 0)
598     return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
599
600   if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
601     return SVN_PROP_ENTRY_COMMITTED_REV;
602
603   if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
604     return SVN_PROP_ENTRY_COMMITTED_DATE;
605
606   if (strcmp(name, "creator-displayname") == 0)
607     return SVN_PROP_ENTRY_LAST_AUTHOR;
608
609   if (strcmp(name, "repository-uuid") == 0)
610     return SVN_PROP_ENTRY_UUID;
611
612   if (strcmp(name, "lock-token") == 0)
613     return SVN_PROP_ENTRY_LOCK_TOKEN;
614
615   if (strcmp(name, "checked-in") == 0)
616     return SVN_RA_SERF__WC_CHECKED_IN_URL;
617
618   if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
619     {
620       /* Here DAV: properties not yet converted to svn: properties should be
621          ignored. */
622       return NULL;
623     }
624
625   /* An unknown namespace, must be a custom property. */
626   return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL);
627 }
628
629 /*
630  * Contact the server (using CONN) to calculate baseline
631  * information for BASELINE_URL at REVISION (which may be
632  * SVN_INVALID_REVNUM to query the HEAD revision).
633  *
634  * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
635  * retrieved from the server as part of this process (which should
636  * match REVISION when REVISION is valid).  Set *BASECOLL_URL_P to the
637  * baseline collection URL.
638  */
639 static svn_error_t *
640 retrieve_baseline_info(svn_revnum_t *actual_revision,
641                        const char **basecoll_url_p,
642                        svn_ra_serf__session_t *session,
643                        const char *baseline_url,
644                        svn_revnum_t revision,
645                        apr_pool_t *result_pool,
646                        apr_pool_t *scratch_pool)
647 {
648   apr_hash_t *props;
649   apr_hash_t *dav_props;
650   const char *basecoll_url;
651
652   SVN_ERR(svn_ra_serf__fetch_node_props(&props, session,
653                                         baseline_url, revision,
654                                         baseline_props,
655                                         scratch_pool, scratch_pool));
656   dav_props = apr_hash_get(props, "DAV:", 4);
657   /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL.  */
658
659   basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
660   if (!basecoll_url)
661     {
662       return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
663                               _("The PROPFIND response did not include "
664                                 "the requested baseline-collection value"));
665     }
666   *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
667
668   if (actual_revision)
669     {
670       const char *version_name;
671
672       version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
673       if (version_name)
674         {
675           apr_int64_t rev;
676
677           SVN_ERR(svn_cstring_atoi64(&rev, version_name));
678           *actual_revision = (svn_revnum_t)rev;
679         }
680
681       if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision))
682         return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
683                                 _("The PROPFIND response did not include "
684                                   "the requested version-name value"));
685     }
686
687   return SVN_NO_ERROR;
688 }
689
690
691 /* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
692    revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
693    collection URL is also returned.
694
695    Do the work over CONN.
696
697    *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
698    temporary allocations will be made in SCRATCH_POOL.  */
699 static svn_error_t *
700 v1_get_youngest_revnum(svn_revnum_t *youngest,
701                        const char **basecoll_url,
702                        svn_ra_serf__session_t *session,
703                        const char *vcc_url,
704                        apr_pool_t *result_pool,
705                        apr_pool_t *scratch_pool)
706 {
707   const char *baseline_url;
708   const char *bc_url;
709
710   /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
711      revision) will return the latest Baseline resource's URL.  */
712   SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url,
713                                       SVN_INVALID_REVNUM,
714                                       "checked-in",
715                                       scratch_pool, scratch_pool));
716   if (!baseline_url)
717     {
718       return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
719                               _("The OPTIONS response did not include "
720                                 "the requested checked-in value"));
721     }
722   baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
723
724   /* From the Baseline resource, we can fetch the DAV:baseline-collection
725      and DAV:version-name properties. The latter is the revision number,
726      which is formally the name used in Label: headers.  */
727
728   /* First check baseline information cache. */
729   SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
730                                                   youngest,
731                                                   session->blncache,
732                                                   baseline_url,
733                                                   scratch_pool));
734   if (!bc_url)
735     {
736       SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session,
737                                      baseline_url, SVN_INVALID_REVNUM,
738                                      scratch_pool, scratch_pool));
739       SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
740                                         baseline_url, *youngest,
741                                         bc_url, scratch_pool));
742     }
743
744   if (basecoll_url != NULL)
745     *basecoll_url = apr_pstrdup(result_pool, bc_url);
746
747   return SVN_NO_ERROR;
748 }
749
750
751 svn_error_t *
752 svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
753                                  svn_ra_serf__session_t *session,
754                                  apr_pool_t *scratch_pool)
755 {
756   const char *vcc_url;
757
758   if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
759     return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
760                              youngest, session, scratch_pool));
761
762   SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
763
764   return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
765                                                 session, vcc_url,
766                                                 scratch_pool, scratch_pool));
767 }
768
769
770 /* Set *BC_URL to the baseline collection url for REVISION. If REVISION
771    is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
772
773    *REVNUM_USED will be set to the revision used.
774
775    Uses the specified CONN, which is part of SESSION.
776
777    All allocations (results and temporary) are performed in POOL.  */
778 static svn_error_t *
779 get_baseline_info(const char **bc_url,
780                   svn_revnum_t *revnum_used,
781                   svn_ra_serf__session_t *session,
782                   svn_revnum_t revision,
783                   apr_pool_t *result_pool,
784                   apr_pool_t *scratch_pool)
785 {
786   /* If we detected HTTP v2 support on the server, we can construct
787      the baseline collection URL ourselves, and fetch the latest
788      revision (if needed) with an OPTIONS request.  */
789   if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
790     {
791       if (SVN_IS_VALID_REVNUM(revision))
792         {
793           *revnum_used = revision;
794         }
795       else
796         {
797           SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
798                     revnum_used, session, scratch_pool));
799         }
800
801       *bc_url = apr_psprintf(result_pool, "%s/%ld",
802                              session->rev_root_stub, *revnum_used);
803     }
804
805   /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt.  */
806   else
807     {
808       const char *vcc_url;
809
810       SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
811
812       if (SVN_IS_VALID_REVNUM(revision))
813         {
814           /* First check baseline information cache. */
815           SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
816                                                    session->blncache,
817                                                    revision, result_pool));
818           if (!*bc_url)
819             {
820               SVN_ERR(retrieve_baseline_info(NULL, bc_url, session,
821                                              vcc_url, revision,
822                                              result_pool, scratch_pool));
823               SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
824                                                 revision, *bc_url,
825                                                 scratch_pool));
826             }
827
828           *revnum_used = revision;
829         }
830       else
831         {
832           SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
833                                          session, vcc_url,
834                                          result_pool, scratch_pool));
835         }
836     }
837
838   return SVN_NO_ERROR;
839 }
840
841
842 svn_error_t *
843 svn_ra_serf__get_stable_url(const char **stable_url,
844                             svn_revnum_t *latest_revnum,
845                             svn_ra_serf__session_t *session,
846                             const char *url,
847                             svn_revnum_t revision,
848                             apr_pool_t *result_pool,
849                             apr_pool_t *scratch_pool)
850 {
851   const char *basecoll_url;
852   const char *repos_relpath;
853   svn_revnum_t revnum_used;
854
855   /* No URL? No sweat. We'll use the session URL.  */
856   if (! url)
857     url = session->session_url.path;
858
859   SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
860                             session, revision, scratch_pool, scratch_pool));
861   SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
862                                          session, scratch_pool));
863
864   *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
865                                             result_pool);
866   if (latest_revnum)
867     *latest_revnum = revnum_used;
868
869   return SVN_NO_ERROR;
870 }
871
872
873 svn_error_t *
874 svn_ra_serf__fetch_dav_prop(const char **value,
875                             svn_ra_serf__session_t *session,
876                             const char *url,
877                             svn_revnum_t revision,
878                             const char *propname,
879                             apr_pool_t *result_pool,
880                             apr_pool_t *scratch_pool)
881 {
882   apr_hash_t *props;
883   apr_hash_t *dav_props;
884
885   SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision,
886                                         checked_in_props,
887                                         scratch_pool, scratch_pool));
888   dav_props = apr_hash_get(props, "DAV:", 4);
889   if (dav_props == NULL)
890     return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
891                             _("The PROPFIND response did not include "
892                               "the requested 'DAV:' properties"));
893
894   /* We wouldn't get here if the resource was not found (404), so the
895      property should be present.
896
897      Note: it is okay to call apr_pstrdup() with NULL.  */
898   *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
899
900   return SVN_NO_ERROR;
901 }
902
903 /* Removes all non regular properties from PROPS */
904 void
905 svn_ra_serf__keep_only_regular_props(apr_hash_t *props,
906                                      apr_pool_t *scratch_pool)
907 {
908   apr_hash_index_t *hi;
909
910   for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
911     {
912       const char *propname = apr_hash_this_key(hi);
913
914       if (svn_property_kind2(propname) != svn_prop_regular_kind)
915         svn_hash_sets(props, propname, NULL);
916     }
917 }