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 = 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. */
parse_status_code(const char * status_line)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 *
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)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
set_ns_prop(apr_hash_t * ns_props,const char * ns,const char * name,const svn_string_t * val,apr_pool_t * result_pool)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 *
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)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 *
setup_propfind_headers(serf_bucket_t * headers,void * setup_baton,apr_pool_t * pool,apr_pool_t * scratch_pool)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 *
create_propfind_body(serf_bucket_t ** bkt,void * setup_baton,serf_bucket_alloc_t * alloc,apr_pool_t * pool,apr_pool_t * scratch_pool)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       prop++;
394     }
395 
396   tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
397                                       sizeof(PROPFIND_HEADER)-1,
398                                       alloc);
399   serf_bucket_aggregate_append(body_bkt, tmp);
400 
401   /* If we're not doing an allprop, add <prop> tags. */
402   if (!requested_allprop)
403     {
404       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
405                                           sizeof("<prop>")-1,
406                                           alloc);
407       serf_bucket_aggregate_append(body_bkt, tmp);
408     }
409 
410   prop = ctx->find_props;
411   while (prop && prop->xmlns)
412     {
413       /* <*propname* xmlns="*propns*" /> */
414       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
415       serf_bucket_aggregate_append(body_bkt, tmp);
416 
417       tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
418       serf_bucket_aggregate_append(body_bkt, tmp);
419 
420       tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
421                                           sizeof(" xmlns=\"")-1,
422                                           alloc);
423       serf_bucket_aggregate_append(body_bkt, tmp);
424 
425       tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc);
426       serf_bucket_aggregate_append(body_bkt, tmp);
427 
428       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
429                                           alloc);
430       serf_bucket_aggregate_append(body_bkt, tmp);
431 
432       prop++;
433     }
434 
435   if (!requested_allprop)
436     {
437       tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
438                                           sizeof("</prop>")-1,
439                                           alloc);
440       serf_bucket_aggregate_append(body_bkt, tmp);
441     }
442 
443   tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
444                                       sizeof(PROPFIND_TRAILER)-1,
445                                       alloc);
446   serf_bucket_aggregate_append(body_bkt, tmp);
447 
448   *bkt = body_bkt;
449   return SVN_NO_ERROR;
450 }
451 
452 
453 svn_error_t *
svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t ** propfind_handler,svn_ra_serf__session_t * sess,const char * path,svn_revnum_t rev,const char * depth,const svn_ra_serf__dav_props_t * find_props,svn_ra_serf__prop_func_t prop_func,void * prop_func_baton,apr_pool_t * pool)454 svn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler,
455                                      svn_ra_serf__session_t *sess,
456                                      const char *path,
457                                      svn_revnum_t rev,
458                                      const char *depth,
459                                      const svn_ra_serf__dav_props_t *find_props,
460                                      svn_ra_serf__prop_func_t prop_func,
461                                      void *prop_func_baton,
462                                      apr_pool_t *pool)
463 {
464   propfind_context_t *new_prop_ctx;
465   svn_ra_serf__handler_t *handler;
466   svn_ra_serf__xml_context_t *xmlctx;
467 
468   new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
469 
470   new_prop_ctx->path = path;
471   new_prop_ctx->find_props = find_props;
472   new_prop_ctx->prop_func = prop_func;
473   new_prop_ctx->prop_func_baton = prop_func_baton;
474   new_prop_ctx->depth = depth;
475 
476   if (SVN_IS_VALID_REVNUM(rev))
477     {
478       new_prop_ctx->label = apr_ltoa(pool, rev);
479     }
480   else
481     {
482       new_prop_ctx->label = NULL;
483     }
484 
485   xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
486                                            propfind_opened,
487                                            propfind_closed,
488                                            NULL,
489                                            new_prop_ctx,
490                                            pool);
491   handler = svn_ra_serf__create_expat_handler(sess, xmlctx,
492                                               propfind_expected_status,
493                                               pool);
494 
495   handler->method = "PROPFIND";
496   handler->path = path;
497   handler->body_delegate = create_propfind_body;
498   handler->body_type = "text/xml";
499   handler->body_delegate_baton = new_prop_ctx;
500   handler->header_delegate = setup_propfind_headers;
501   handler->header_delegate_baton = new_prop_ctx;
502 
503   handler->no_dav_headers = TRUE;
504 
505   new_prop_ctx->handler = handler;
506 
507   *propfind_handler = handler;
508 
509   return SVN_NO_ERROR;
510 }
511 
512 svn_error_t *
svn_ra_serf__deliver_svn_props(void * baton,const char * path,const char * ns,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)513 svn_ra_serf__deliver_svn_props(void *baton,
514                                const char *path,
515                                const char *ns,
516                                const char *name,
517                                const svn_string_t *value,
518                                apr_pool_t *scratch_pool)
519 {
520   apr_hash_t *props = baton;
521   apr_pool_t *result_pool = apr_hash_pool_get(props);
522   const char *prop_name;
523 
524   prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
525   if (prop_name == NULL)
526     return SVN_NO_ERROR;
527 
528   svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool));
529 
530   return SVN_NO_ERROR;
531 }
532 
533 /*
534  * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties
535  * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash
536  *    (const char * -> svn_string_t *) to the values.
537  */
538 static svn_error_t *
deliver_node_props(void * baton,const char * path,const char * ns,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)539 deliver_node_props(void *baton,
540                   const char *path,
541                   const char *ns,
542                   const char *name,
543                   const svn_string_t *value,
544                   apr_pool_t *scratch_pool)
545 {
546   apr_hash_t *nss = baton;
547   apr_hash_t *props;
548   apr_pool_t *result_pool = apr_hash_pool_get(nss);
549 
550   props = svn_hash_gets(nss, ns);
551 
552   if (!props)
553     {
554       props = apr_hash_make(result_pool);
555 
556       ns = apr_pstrdup(result_pool, ns);
557       svn_hash_sets(nss, ns, props);
558     }
559 
560   name = apr_pstrdup(result_pool, name);
561   svn_hash_sets(props, name, svn_string_dup(value, result_pool));
562 
563   return SVN_NO_ERROR;
564 }
565 
566 svn_error_t *
svn_ra_serf__fetch_node_props(apr_hash_t ** results,svn_ra_serf__session_t * session,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)567 svn_ra_serf__fetch_node_props(apr_hash_t **results,
568                               svn_ra_serf__session_t *session,
569                               const char *url,
570                               svn_revnum_t revision,
571                               const svn_ra_serf__dav_props_t *which_props,
572                               apr_pool_t *result_pool,
573                               apr_pool_t *scratch_pool)
574 {
575   apr_hash_t *props;
576   svn_ra_serf__handler_t *handler;
577 
578   props = apr_hash_make(result_pool);
579 
580   SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
581                                                url, revision, "0", which_props,
582                                                deliver_node_props,
583                                                props, scratch_pool));
584 
585   SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
586 
587   *results = props;
588   return SVN_NO_ERROR;
589 }
590 
591 const char *
svn_ra_serf__svnname_from_wirename(const char * ns,const char * name,apr_pool_t * result_pool)592 svn_ra_serf__svnname_from_wirename(const char *ns,
593                                    const char *name,
594                                    apr_pool_t *result_pool)
595 {
596   if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
597     return apr_pstrdup(result_pool, name);
598 
599   if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
600     return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
601 
602   if (strcmp(ns, SVN_PROP_PREFIX) == 0)
603     return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
604 
605   if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
606     return SVN_PROP_ENTRY_COMMITTED_REV;
607 
608   if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
609     return SVN_PROP_ENTRY_COMMITTED_DATE;
610 
611   if (strcmp(name, "creator-displayname") == 0)
612     return SVN_PROP_ENTRY_LAST_AUTHOR;
613 
614   if (strcmp(name, "repository-uuid") == 0)
615     return SVN_PROP_ENTRY_UUID;
616 
617   if (strcmp(name, "lock-token") == 0)
618     return SVN_PROP_ENTRY_LOCK_TOKEN;
619 
620   if (strcmp(name, "checked-in") == 0)
621     return SVN_RA_SERF__WC_CHECKED_IN_URL;
622 
623   if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
624     {
625       /* Here DAV: properties not yet converted to svn: properties should be
626          ignored. */
627       return NULL;
628     }
629 
630   /* An unknown namespace, must be a custom property. */
631   return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL);
632 }
633 
634 /*
635  * Contact the server (using CONN) to calculate baseline
636  * information for BASELINE_URL at REVISION (which may be
637  * SVN_INVALID_REVNUM to query the HEAD revision).
638  *
639  * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
640  * retrieved from the server as part of this process (which should
641  * match REVISION when REVISION is valid).  Set *BASECOLL_URL_P to the
642  * baseline collection URL.
643  */
644 static svn_error_t *
retrieve_baseline_info(svn_revnum_t * actual_revision,const char ** basecoll_url_p,svn_ra_serf__session_t * session,const char * baseline_url,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)645 retrieve_baseline_info(svn_revnum_t *actual_revision,
646                        const char **basecoll_url_p,
647                        svn_ra_serf__session_t *session,
648                        const char *baseline_url,
649                        svn_revnum_t revision,
650                        apr_pool_t *result_pool,
651                        apr_pool_t *scratch_pool)
652 {
653   apr_hash_t *props;
654   apr_hash_t *dav_props;
655   const char *basecoll_url;
656 
657   SVN_ERR(svn_ra_serf__fetch_node_props(&props, session,
658                                         baseline_url, revision,
659                                         baseline_props,
660                                         scratch_pool, scratch_pool));
661   dav_props = apr_hash_get(props, "DAV:", 4);
662   /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL.  */
663 
664   basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
665   if (!basecoll_url)
666     {
667       return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
668                               _("The PROPFIND response did not include "
669                                 "the requested baseline-collection value"));
670     }
671   *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
672 
673   if (actual_revision)
674     {
675       const char *version_name;
676 
677       version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
678       if (version_name)
679         {
680           apr_int64_t rev;
681 
682           SVN_ERR(svn_cstring_atoi64(&rev, version_name));
683           *actual_revision = (svn_revnum_t)rev;
684         }
685 
686       if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision))
687         return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
688                                 _("The PROPFIND response did not include "
689                                   "the requested version-name value"));
690     }
691 
692   return SVN_NO_ERROR;
693 }
694 
695 
696 /* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
697    revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
698    collection URL is also returned.
699 
700    Do the work over CONN.
701 
702    *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
703    temporary allocations will be made in SCRATCH_POOL.  */
704 static svn_error_t *
v1_get_youngest_revnum(svn_revnum_t * youngest,const char ** basecoll_url,svn_ra_serf__session_t * session,const char * vcc_url,apr_pool_t * result_pool,apr_pool_t * scratch_pool)705 v1_get_youngest_revnum(svn_revnum_t *youngest,
706                        const char **basecoll_url,
707                        svn_ra_serf__session_t *session,
708                        const char *vcc_url,
709                        apr_pool_t *result_pool,
710                        apr_pool_t *scratch_pool)
711 {
712   const char *baseline_url;
713   const char *bc_url;
714 
715   /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
716      revision) will return the latest Baseline resource's URL.  */
717   SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url,
718                                       SVN_INVALID_REVNUM,
719                                       "checked-in",
720                                       scratch_pool, scratch_pool));
721   if (!baseline_url)
722     {
723       return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
724                               _("The OPTIONS response did not include "
725                                 "the requested checked-in value"));
726     }
727   baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
728 
729   /* From the Baseline resource, we can fetch the DAV:baseline-collection
730      and DAV:version-name properties. The latter is the revision number,
731      which is formally the name used in Label: headers.  */
732 
733   /* First check baseline information cache. */
734   SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
735                                                   youngest,
736                                                   session->blncache,
737                                                   baseline_url,
738                                                   scratch_pool));
739   if (!bc_url)
740     {
741       SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session,
742                                      baseline_url, SVN_INVALID_REVNUM,
743                                      scratch_pool, scratch_pool));
744       SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
745                                         baseline_url, *youngest,
746                                         bc_url, scratch_pool));
747     }
748 
749   if (basecoll_url != NULL)
750     *basecoll_url = apr_pstrdup(result_pool, bc_url);
751 
752   return SVN_NO_ERROR;
753 }
754 
755 
756 svn_error_t *
svn_ra_serf__get_youngest_revnum(svn_revnum_t * youngest,svn_ra_serf__session_t * session,apr_pool_t * scratch_pool)757 svn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
758                                  svn_ra_serf__session_t *session,
759                                  apr_pool_t *scratch_pool)
760 {
761   const char *vcc_url;
762 
763   if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
764     return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
765                              youngest, session, scratch_pool));
766 
767   SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
768 
769   return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
770                                                 session, vcc_url,
771                                                 scratch_pool, scratch_pool));
772 }
773 
774 
775 /* Set *BC_URL to the baseline collection url for REVISION. If REVISION
776    is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
777 
778    *REVNUM_USED will be set to the revision used.
779 
780    Uses the specified CONN, which is part of SESSION.
781 
782    All allocations (results and temporary) are performed in POOL.  */
783 static svn_error_t *
get_baseline_info(const char ** bc_url,svn_revnum_t * revnum_used,svn_ra_serf__session_t * session,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)784 get_baseline_info(const char **bc_url,
785                   svn_revnum_t *revnum_used,
786                   svn_ra_serf__session_t *session,
787                   svn_revnum_t revision,
788                   apr_pool_t *result_pool,
789                   apr_pool_t *scratch_pool)
790 {
791   /* If we detected HTTP v2 support on the server, we can construct
792      the baseline collection URL ourselves, and fetch the latest
793      revision (if needed) with an OPTIONS request.  */
794   if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
795     {
796       if (SVN_IS_VALID_REVNUM(revision))
797         {
798           *revnum_used = revision;
799         }
800       else
801         {
802           SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
803                     revnum_used, session, scratch_pool));
804         }
805 
806       *bc_url = apr_psprintf(result_pool, "%s/%ld",
807                              session->rev_root_stub, *revnum_used);
808     }
809 
810   /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt.  */
811   else
812     {
813       const char *vcc_url;
814 
815       SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
816 
817       if (SVN_IS_VALID_REVNUM(revision))
818         {
819           /* First check baseline information cache. */
820           SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
821                                                    session->blncache,
822                                                    revision, result_pool));
823           if (!*bc_url)
824             {
825               SVN_ERR(retrieve_baseline_info(NULL, bc_url, session,
826                                              vcc_url, revision,
827                                              result_pool, scratch_pool));
828               SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
829                                                 revision, *bc_url,
830                                                 scratch_pool));
831             }
832 
833           *revnum_used = revision;
834         }
835       else
836         {
837           SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
838                                          session, vcc_url,
839                                          result_pool, scratch_pool));
840         }
841     }
842 
843   return SVN_NO_ERROR;
844 }
845 
846 
847 svn_error_t *
svn_ra_serf__get_stable_url(const char ** stable_url,svn_revnum_t * latest_revnum,svn_ra_serf__session_t * session,const char * url,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)848 svn_ra_serf__get_stable_url(const char **stable_url,
849                             svn_revnum_t *latest_revnum,
850                             svn_ra_serf__session_t *session,
851                             const char *url,
852                             svn_revnum_t revision,
853                             apr_pool_t *result_pool,
854                             apr_pool_t *scratch_pool)
855 {
856   const char *basecoll_url;
857   const char *repos_relpath;
858   svn_revnum_t revnum_used;
859 
860   /* No URL? No sweat. We'll use the session URL.  */
861   if (! url)
862     url = session->session_url.path;
863 
864   SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
865                             session, revision, scratch_pool, scratch_pool));
866   SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
867                                          session, scratch_pool));
868 
869   *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
870                                             result_pool);
871   if (latest_revnum)
872     *latest_revnum = revnum_used;
873 
874   return SVN_NO_ERROR;
875 }
876 
877 
878 svn_error_t *
svn_ra_serf__fetch_dav_prop(const char ** value,svn_ra_serf__session_t * session,const char * url,svn_revnum_t revision,const char * propname,apr_pool_t * result_pool,apr_pool_t * scratch_pool)879 svn_ra_serf__fetch_dav_prop(const char **value,
880                             svn_ra_serf__session_t *session,
881                             const char *url,
882                             svn_revnum_t revision,
883                             const char *propname,
884                             apr_pool_t *result_pool,
885                             apr_pool_t *scratch_pool)
886 {
887   apr_hash_t *props;
888   apr_hash_t *dav_props;
889 
890   SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision,
891                                         checked_in_props,
892                                         scratch_pool, scratch_pool));
893   dav_props = apr_hash_get(props, "DAV:", 4);
894   if (dav_props == NULL)
895     return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
896                             _("The PROPFIND response did not include "
897                               "the requested 'DAV:' properties"));
898 
899   /* We wouldn't get here if the resource was not found (404), so the
900      property should be present.
901 
902      Note: it is okay to call apr_pstrdup() with NULL.  */
903   *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
904 
905   return SVN_NO_ERROR;
906 }
907 
908 /* Removes all non regular properties from PROPS */
909 void
svn_ra_serf__keep_only_regular_props(apr_hash_t * props,apr_pool_t * scratch_pool)910 svn_ra_serf__keep_only_regular_props(apr_hash_t *props,
911                                      apr_pool_t *scratch_pool)
912 {
913   apr_hash_index_t *hi;
914 
915   for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
916     {
917       const char *propname = apr_hash_this_key(hi);
918 
919       if (svn_property_kind2(propname) != svn_prop_regular_kind)
920         svn_hash_sets(props, propname, NULL);
921     }
922 }
923