1251881Speter/*
2251881Speter * property.c : property routines for ra_serf
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#include <serf.h>
27251881Speter
28251881Speter#include "svn_hash.h"
29251881Speter#include "svn_path.h"
30251881Speter#include "svn_base64.h"
31251881Speter#include "svn_xml.h"
32251881Speter#include "svn_props.h"
33251881Speter#include "svn_dirent_uri.h"
34251881Speter
35251881Speter#include "private/svn_dav_protocol.h"
36251881Speter#include "private/svn_fspath.h"
37251881Speter#include "private/svn_string_private.h"
38251881Speter#include "svn_private_config.h"
39251881Speter
40251881Speter#include "ra_serf.h"
41251881Speter
42251881Speter
43251881Speter/* Our current parsing state we're in for the PROPFIND response. */
44251881Spetertypedef enum prop_state_e {
45289180Speter  INITIAL = XML_STATE_INITIAL,
46251881Speter  MULTISTATUS,
47251881Speter  RESPONSE,
48251881Speter  HREF,
49251881Speter  PROPSTAT,
50251881Speter  STATUS,
51251881Speter  PROP,
52251881Speter  PROPVAL,
53251881Speter  COLLECTION,
54251881Speter  HREF_VALUE
55251881Speter} prop_state_e;
56251881Speter
57251881Speter
58251881Speter/*
59251881Speter * This structure represents a pending PROPFIND response.
60251881Speter */
61251881Spetertypedef struct propfind_context_t {
62251881Speter  svn_ra_serf__handler_t *handler;
63251881Speter
64251881Speter  /* the requested path */
65251881Speter  const char *path;
66251881Speter
67289180Speter  /* the requested version (in string form) */
68251881Speter  const char *label;
69251881Speter
70251881Speter  /* the request depth */
71251881Speter  const char *depth;
72251881Speter
73251881Speter  /* the list of requested properties */
74251881Speter  const svn_ra_serf__dav_props_t *find_props;
75251881Speter
76289180Speter  svn_ra_serf__prop_func_t prop_func;
77289180Speter  void *prop_func_baton;
78251881Speter
79251881Speter  /* hash table containing all the properties associated with the
80251881Speter   * "current" <propstat> tag.  These will get copied into RET_PROPS
81251881Speter   * if the status code similarly associated indicates that they are
82251881Speter   * "good"; otherwise, they'll get discarded.
83251881Speter   */
84251881Speter  apr_hash_t *ps_props;
85251881Speter} propfind_context_t;
86251881Speter
87251881Speter
88251881Speter#define D_ "DAV:"
89251881Speter#define S_ SVN_XML_NAMESPACE
90251881Speterstatic const svn_ra_serf__xml_transition_t propfind_ttable[] = {
91251881Speter  { INITIAL, D_, "multistatus", MULTISTATUS,
92251881Speter    FALSE, { NULL }, TRUE },
93251881Speter
94251881Speter  { MULTISTATUS, D_, "response", RESPONSE,
95251881Speter    FALSE, { NULL }, FALSE },
96251881Speter
97251881Speter  { RESPONSE, D_, "href", HREF,
98251881Speter    TRUE, { NULL }, TRUE },
99251881Speter
100251881Speter  { RESPONSE, D_, "propstat", PROPSTAT,
101251881Speter    FALSE, { NULL }, TRUE },
102251881Speter
103251881Speter  { PROPSTAT, D_, "status", STATUS,
104251881Speter    TRUE, { NULL }, TRUE },
105251881Speter
106251881Speter  { PROPSTAT, D_, "prop", PROP,
107251881Speter    FALSE, { NULL }, FALSE },
108251881Speter
109251881Speter  { PROP, "*", "*", PROPVAL,
110251881Speter    TRUE, { "?V:encoding", NULL }, TRUE },
111251881Speter
112251881Speter  { PROPVAL, D_, "collection", COLLECTION,
113251881Speter    FALSE, { NULL }, TRUE },
114251881Speter
115251881Speter  { PROPVAL, D_, "href", HREF_VALUE,
116251881Speter    TRUE, { NULL }, TRUE },
117251881Speter
118251881Speter  { 0 }
119251881Speter};
120251881Speter
121289180Speterstatic const int propfind_expected_status[] = {
122289180Speter  207,
123289180Speter  0
124289180Speter};
125251881Speter
126251881Speter/* Return the HTTP status code contained in STATUS_LINE, or 0 if
127251881Speter   there's a problem parsing it. */
128289180Speterstatic apr_int64_t parse_status_code(const char *status_line)
129251881Speter{
130251881Speter  /* STATUS_LINE should be of form: "HTTP/1.1 200 OK" */
131251881Speter  if (status_line[0] == 'H' &&
132251881Speter      status_line[1] == 'T' &&
133251881Speter      status_line[2] == 'T' &&
134251881Speter      status_line[3] == 'P' &&
135251881Speter      status_line[4] == '/' &&
136251881Speter      (status_line[5] >= '0' && status_line[5] <= '9') &&
137251881Speter      status_line[6] == '.' &&
138251881Speter      (status_line[7] >= '0' && status_line[7] <= '9') &&
139251881Speter      status_line[8] == ' ')
140251881Speter    {
141251881Speter      char *reason;
142251881Speter
143251881Speter      return apr_strtoi64(status_line + 8, &reason, 10);
144251881Speter    }
145251881Speter  return 0;
146251881Speter}
147251881Speter
148251881Speter/* Conforms to svn_ra_serf__xml_opened_t  */
149251881Speterstatic svn_error_t *
150251881Speterpropfind_opened(svn_ra_serf__xml_estate_t *xes,
151251881Speter                void *baton,
152251881Speter                int entered_state,
153251881Speter                const svn_ra_serf__dav_props_t *tag,
154251881Speter                apr_pool_t *scratch_pool)
155251881Speter{
156251881Speter  propfind_context_t *ctx = baton;
157251881Speter
158251881Speter  if (entered_state == PROPVAL)
159251881Speter    {
160289180Speter        svn_ra_serf__xml_note(xes, PROPVAL, "ns", tag->xmlns);
161251881Speter      svn_ra_serf__xml_note(xes, PROPVAL, "name", tag->name);
162251881Speter    }
163251881Speter  else if (entered_state == PROPSTAT)
164251881Speter    {
165289180Speter      ctx->ps_props = apr_hash_make(svn_ra_serf__xml_state_pool(xes));
166251881Speter    }
167251881Speter
168251881Speter  return SVN_NO_ERROR;
169251881Speter}
170251881Speter
171289180Speter/* Set PROPS for NS:NAME VAL. Helper for propfind_closed */
172289180Speterstatic void
173289180Speterset_ns_prop(apr_hash_t *ns_props,
174289180Speter            const char *ns, const char *name,
175289180Speter            const svn_string_t *val, apr_pool_t *result_pool)
176289180Speter{
177289180Speter  apr_hash_t *props = svn_hash_gets(ns_props, ns);
178251881Speter
179289180Speter  if (!props)
180289180Speter    {
181289180Speter      props = apr_hash_make(result_pool);
182289180Speter      ns = apr_pstrdup(result_pool, ns);
183289180Speter      svn_hash_sets(ns_props, ns, props);
184289180Speter    }
185289180Speter
186289180Speter  if (val)
187289180Speter    {
188289180Speter      name = apr_pstrdup(result_pool, name);
189289180Speter      val = svn_string_dup(val, result_pool);
190289180Speter    }
191289180Speter
192289180Speter  svn_hash_sets(props, name, val);
193289180Speter}
194289180Speter
195251881Speter/* Conforms to svn_ra_serf__xml_closed_t  */
196251881Speterstatic svn_error_t *
197251881Speterpropfind_closed(svn_ra_serf__xml_estate_t *xes,
198251881Speter                void *baton,
199251881Speter                int leaving_state,
200251881Speter                const svn_string_t *cdata,
201251881Speter                apr_hash_t *attrs,
202251881Speter                apr_pool_t *scratch_pool)
203251881Speter{
204251881Speter  propfind_context_t *ctx = baton;
205251881Speter
206251881Speter  if (leaving_state == MULTISTATUS)
207251881Speter    {
208251881Speter      /* We've gathered all the data from the reponse. Add this item
209251881Speter         onto the "done list". External callers will then know this
210251881Speter         request has been completed (tho stray response bytes may still
211251881Speter         arrive).  */
212251881Speter    }
213251881Speter  else if (leaving_state == HREF)
214251881Speter    {
215251881Speter      const char *path;
216251881Speter
217251881Speter      if (strcmp(ctx->depth, "1") == 0)
218251881Speter        path = svn_urlpath__canonicalize(cdata->data, scratch_pool);
219251881Speter      else
220251881Speter        path = ctx->path;
221251881Speter
222251881Speter      svn_ra_serf__xml_note(xes, RESPONSE, "path", path);
223251881Speter
224289180Speter      SVN_ERR(ctx->prop_func(ctx->prop_func_baton,
225289180Speter                             path,
226289180Speter                             D_, "href",
227289180Speter                             cdata, scratch_pool));
228251881Speter    }
229251881Speter  else if (leaving_state == COLLECTION)
230251881Speter    {
231251881Speter      svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", "collection");
232251881Speter    }
233251881Speter  else if (leaving_state == HREF_VALUE)
234251881Speter    {
235251881Speter      svn_ra_serf__xml_note(xes, PROPVAL, "altvalue", cdata->data);
236251881Speter    }
237251881Speter  else if (leaving_state == STATUS)
238251881Speter    {
239251881Speter      /* Parse the status field, and remember if this is a property
240251881Speter         that we wish to ignore.  (Typically, if it's not a 200, the
241251881Speter         status will be 404 to indicate that a property we
242251881Speter         specifically requested from the server doesn't exist.)  */
243289180Speter      apr_int64_t status = parse_status_code(cdata->data);
244251881Speter      if (status != 200)
245251881Speter        svn_ra_serf__xml_note(xes, PROPSTAT, "ignore-prop", "*");
246251881Speter    }
247251881Speter  else if (leaving_state == PROPVAL)
248251881Speter    {
249289180Speter      const char *encoding;
250251881Speter      const svn_string_t *val_str;
251251881Speter      const char *ns;
252251881Speter      const char *name;
253251881Speter      const char *altvalue;
254251881Speter
255289180Speter      if ((altvalue = svn_hash_gets(attrs, "altvalue")) != NULL)
256251881Speter        {
257289180Speter          val_str = svn_string_create(altvalue, scratch_pool);
258289180Speter        }
259289180Speter      else if ((encoding = svn_hash_gets(attrs, "V:encoding")) != NULL)
260289180Speter        {
261251881Speter          if (strcmp(encoding, "base64") != 0)
262251881Speter            return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
263251881Speter                                     NULL,
264251881Speter                                     _("Got unrecognized encoding '%s'"),
265251881Speter                                     encoding);
266251881Speter
267251881Speter          /* Decode into the right pool.  */
268289180Speter          val_str = svn_base64_decode_string(cdata, scratch_pool);
269251881Speter        }
270251881Speter      else
271251881Speter        {
272251881Speter          /* Copy into the right pool.  */
273289180Speter          val_str = cdata;
274251881Speter        }
275251881Speter
276289180Speter      /* The current path sits on the RESPONSE state.
277251881Speter
278251881Speter         Now, it would be nice if we could, at this point, know that
279251881Speter         the status code for this property indicated a problem -- then
280251881Speter         we could simply bail out here and ignore the property.
281251881Speter         Sadly, though, we might get the status code *after* we get
282251881Speter         the property value.  So we'll carry on with our processing
283251881Speter         here, setting the property and value as expected.  Once we
284251881Speter         know for sure the status code associate with the property,
285251881Speter         we'll decide its fate.  */
286251881Speter
287251881Speter      ns = svn_hash_gets(attrs, "ns");
288289180Speter      name = svn_hash_gets(attrs, "name");
289251881Speter
290289180Speter      set_ns_prop(ctx->ps_props, ns, name, val_str,
291289180Speter                  apr_hash_pool_get(ctx->ps_props));
292251881Speter    }
293251881Speter  else
294251881Speter    {
295251881Speter      apr_hash_t *gathered;
296251881Speter
297251881Speter      SVN_ERR_ASSERT(leaving_state == PROPSTAT);
298251881Speter
299289180Speter      gathered = svn_ra_serf__xml_gather_since(xes, RESPONSE);
300251881Speter
301251881Speter      /* If we've squirreled away a note that says we want to ignore
302251881Speter         these properties, we'll do so.  Otherwise, we need to copy
303251881Speter         them from the temporary hash into the ctx->ret_props hash. */
304251881Speter      if (! svn_hash_gets(gathered, "ignore-prop"))
305251881Speter        {
306289180Speter          apr_hash_index_t *hi_ns;
307289180Speter          const char *path;
308289180Speter          apr_pool_t *iterpool = svn_pool_create(scratch_pool);
309251881Speter
310251881Speter
311289180Speter          path = svn_hash_gets(gathered, "path");
312289180Speter          if (!path)
313289180Speter            path = ctx->path;
314251881Speter
315289180Speter          for (hi_ns = apr_hash_first(scratch_pool, ctx->ps_props);
316289180Speter               hi_ns;
317289180Speter               hi_ns = apr_hash_next(hi_ns))
318289180Speter            {
319289180Speter              const char *ns = apr_hash_this_key(hi_ns);
320289180Speter              apr_hash_t *props = apr_hash_this_val(hi_ns);
321289180Speter              apr_hash_index_t *hi_prop;
322251881Speter
323289180Speter              svn_pool_clear(iterpool);
324251881Speter
325289180Speter              for (hi_prop = apr_hash_first(iterpool, props);
326289180Speter                   hi_prop;
327289180Speter                   hi_prop = apr_hash_next(hi_prop))
328289180Speter                {
329289180Speter                  const char *name = apr_hash_this_key(hi_prop);
330289180Speter                  const svn_string_t *value = apr_hash_this_val(hi_prop);
331251881Speter
332289180Speter                  SVN_ERR(ctx->prop_func(ctx->prop_func_baton, path,
333289180Speter                                         ns, name, value, iterpool));
334289180Speter                }
335251881Speter            }
336289180Speter
337289180Speter          svn_pool_destroy(iterpool);
338251881Speter        }
339251881Speter
340289180Speter      ctx->ps_props = NULL; /* Allocated in PROPSTAT state pool */
341251881Speter    }
342251881Speter
343289180Speter  return SVN_NO_ERROR;
344251881Speter}
345251881Speter
346251881Speter
347251881Speter
348251881Speterstatic svn_error_t *
349251881Spetersetup_propfind_headers(serf_bucket_t *headers,
350289180Speter                       void *setup_baton,
351289180Speter                       apr_pool_t *pool /* request pool */,
352289180Speter                       apr_pool_t *scratch_pool)
353251881Speter{
354251881Speter  propfind_context_t *ctx = setup_baton;
355251881Speter
356251881Speter  serf_bucket_headers_setn(headers, "Depth", ctx->depth);
357251881Speter  if (ctx->label)
358251881Speter    {
359251881Speter      serf_bucket_headers_setn(headers, "Label", ctx->label);
360251881Speter    }
361251881Speter
362251881Speter  return SVN_NO_ERROR;
363251881Speter}
364251881Speter
365251881Speter#define PROPFIND_HEADER "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\">"
366251881Speter#define PROPFIND_TRAILER "</propfind>"
367251881Speter
368289180Speter/* Implements svn_ra_serf__request_body_delegate_t */
369251881Speterstatic svn_error_t *
370251881Spetercreate_propfind_body(serf_bucket_t **bkt,
371251881Speter                     void *setup_baton,
372251881Speter                     serf_bucket_alloc_t *alloc,
373289180Speter                     apr_pool_t *pool /* request pool */,
374289180Speter                     apr_pool_t *scratch_pool)
375251881Speter{
376251881Speter  propfind_context_t *ctx = setup_baton;
377251881Speter
378251881Speter  serf_bucket_t *body_bkt, *tmp;
379251881Speter  const svn_ra_serf__dav_props_t *prop;
380251881Speter  svn_boolean_t requested_allprop = FALSE;
381251881Speter
382251881Speter  body_bkt = serf_bucket_aggregate_create(alloc);
383251881Speter
384251881Speter  prop = ctx->find_props;
385289180Speter  while (prop && prop->xmlns)
386251881Speter    {
387251881Speter      /* special case the allprop case. */
388251881Speter      if (strcmp(prop->name, "allprop") == 0)
389251881Speter        {
390251881Speter          requested_allprop = TRUE;
391251881Speter        }
392251881Speter
393362181Sdim      prop++;
394362181Sdim    }
395362181Sdim
396362181Sdim  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
397362181Sdim                                      sizeof(PROPFIND_HEADER)-1,
398362181Sdim                                      alloc);
399362181Sdim  serf_bucket_aggregate_append(body_bkt, tmp);
400362181Sdim
401362181Sdim  /* If we're not doing an allprop, add <prop> tags. */
402362181Sdim  if (!requested_allprop)
403362181Sdim    {
404362181Sdim      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
405362181Sdim                                          sizeof("<prop>")-1,
406362181Sdim                                          alloc);
407362181Sdim      serf_bucket_aggregate_append(body_bkt, tmp);
408362181Sdim    }
409362181Sdim
410362181Sdim  prop = ctx->find_props;
411362181Sdim  while (prop && prop->xmlns)
412362181Sdim    {
413251881Speter      /* <*propname* xmlns="*propns*" /> */
414251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
415251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
416251881Speter
417251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
418251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
419251881Speter
420251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
421251881Speter                                          sizeof(" xmlns=\"")-1,
422251881Speter                                          alloc);
423251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
424251881Speter
425289180Speter      tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc);
426251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
427251881Speter
428251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
429251881Speter                                          alloc);
430251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
431251881Speter
432251881Speter      prop++;
433251881Speter    }
434251881Speter
435251881Speter  if (!requested_allprop)
436251881Speter    {
437251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
438251881Speter                                          sizeof("</prop>")-1,
439251881Speter                                          alloc);
440251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
441251881Speter    }
442251881Speter
443251881Speter  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
444251881Speter                                      sizeof(PROPFIND_TRAILER)-1,
445251881Speter                                      alloc);
446251881Speter  serf_bucket_aggregate_append(body_bkt, tmp);
447251881Speter
448251881Speter  *bkt = body_bkt;
449251881Speter  return SVN_NO_ERROR;
450251881Speter}
451251881Speter
452251881Speter
453251881Spetersvn_error_t *
454289180Spetersvn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler,
455289180Speter                                     svn_ra_serf__session_t *sess,
456289180Speter                                     const char *path,
457289180Speter                                     svn_revnum_t rev,
458289180Speter                                     const char *depth,
459289180Speter                                     const svn_ra_serf__dav_props_t *find_props,
460289180Speter                                     svn_ra_serf__prop_func_t prop_func,
461289180Speter                                     void *prop_func_baton,
462289180Speter                                     apr_pool_t *pool)
463251881Speter{
464251881Speter  propfind_context_t *new_prop_ctx;
465251881Speter  svn_ra_serf__handler_t *handler;
466251881Speter  svn_ra_serf__xml_context_t *xmlctx;
467251881Speter
468251881Speter  new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
469251881Speter
470251881Speter  new_prop_ctx->path = path;
471251881Speter  new_prop_ctx->find_props = find_props;
472289180Speter  new_prop_ctx->prop_func = prop_func;
473289180Speter  new_prop_ctx->prop_func_baton = prop_func_baton;
474251881Speter  new_prop_ctx->depth = depth;
475251881Speter
476251881Speter  if (SVN_IS_VALID_REVNUM(rev))
477251881Speter    {
478251881Speter      new_prop_ctx->label = apr_ltoa(pool, rev);
479251881Speter    }
480251881Speter  else
481251881Speter    {
482251881Speter      new_prop_ctx->label = NULL;
483251881Speter    }
484251881Speter
485251881Speter  xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
486251881Speter                                           propfind_opened,
487251881Speter                                           propfind_closed,
488251881Speter                                           NULL,
489251881Speter                                           new_prop_ctx,
490251881Speter                                           pool);
491289180Speter  handler = svn_ra_serf__create_expat_handler(sess, xmlctx,
492289180Speter                                              propfind_expected_status,
493289180Speter                                              pool);
494251881Speter
495251881Speter  handler->method = "PROPFIND";
496251881Speter  handler->path = path;
497251881Speter  handler->body_delegate = create_propfind_body;
498251881Speter  handler->body_type = "text/xml";
499251881Speter  handler->body_delegate_baton = new_prop_ctx;
500251881Speter  handler->header_delegate = setup_propfind_headers;
501251881Speter  handler->header_delegate_baton = new_prop_ctx;
502251881Speter
503289180Speter  handler->no_dav_headers = TRUE;
504251881Speter
505251881Speter  new_prop_ctx->handler = handler;
506251881Speter
507251881Speter  *propfind_handler = handler;
508251881Speter
509251881Speter  return SVN_NO_ERROR;
510251881Speter}
511251881Speter
512251881Spetersvn_error_t *
513289180Spetersvn_ra_serf__deliver_svn_props(void *baton,
514289180Speter                               const char *path,
515289180Speter                               const char *ns,
516289180Speter                               const char *name,
517289180Speter                               const svn_string_t *value,
518289180Speter                               apr_pool_t *scratch_pool)
519251881Speter{
520289180Speter  apr_hash_t *props = baton;
521289180Speter  apr_pool_t *result_pool = apr_hash_pool_get(props);
522289180Speter  const char *prop_name;
523251881Speter
524289180Speter  prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
525289180Speter  if (prop_name == NULL)
526289180Speter    return SVN_NO_ERROR;
527251881Speter
528289180Speter  svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool));
529251881Speter
530289180Speter  return SVN_NO_ERROR;
531251881Speter}
532251881Speter
533251881Speter/*
534289180Speter * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties
535289180Speter * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash
536289180Speter *    (const char * -> svn_string_t *) to the values.
537251881Speter */
538289180Speterstatic svn_error_t *
539289180Speterdeliver_node_props(void *baton,
540289180Speter                  const char *path,
541289180Speter                  const char *ns,
542289180Speter                  const char *name,
543289180Speter                  const svn_string_t *value,
544289180Speter                  apr_pool_t *scratch_pool)
545251881Speter{
546289180Speter  apr_hash_t *nss = baton;
547289180Speter  apr_hash_t *props;
548289180Speter  apr_pool_t *result_pool = apr_hash_pool_get(nss);
549251881Speter
550289180Speter  props = svn_hash_gets(nss, ns);
551251881Speter
552289180Speter  if (!props)
553289180Speter    {
554289180Speter      props = apr_hash_make(result_pool);
555251881Speter
556289180Speter      ns = apr_pstrdup(result_pool, ns);
557289180Speter      svn_hash_sets(nss, ns, props);
558289180Speter    }
559289180Speter
560289180Speter  name = apr_pstrdup(result_pool, name);
561289180Speter  svn_hash_sets(props, name, svn_string_dup(value, result_pool));
562289180Speter
563251881Speter  return SVN_NO_ERROR;
564251881Speter}
565251881Speter
566251881Spetersvn_error_t *
567251881Spetersvn_ra_serf__fetch_node_props(apr_hash_t **results,
568289180Speter                              svn_ra_serf__session_t *session,
569251881Speter                              const char *url,
570251881Speter                              svn_revnum_t revision,
571251881Speter                              const svn_ra_serf__dav_props_t *which_props,
572251881Speter                              apr_pool_t *result_pool,
573251881Speter                              apr_pool_t *scratch_pool)
574251881Speter{
575289180Speter  apr_hash_t *props;
576289180Speter  svn_ra_serf__handler_t *handler;
577251881Speter
578289180Speter  props = apr_hash_make(result_pool);
579251881Speter
580289180Speter  SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
581289180Speter                                               url, revision, "0", which_props,
582289180Speter                                               deliver_node_props,
583289180Speter                                               props, scratch_pool));
584251881Speter
585289180Speter  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
586251881Speter
587289180Speter  *results = props;
588251881Speter  return SVN_NO_ERROR;
589251881Speter}
590251881Speter
591251881Speterconst char *
592251881Spetersvn_ra_serf__svnname_from_wirename(const char *ns,
593251881Speter                                   const char *name,
594251881Speter                                   apr_pool_t *result_pool)
595251881Speter{
596251881Speter  if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
597251881Speter    return apr_pstrdup(result_pool, name);
598251881Speter
599251881Speter  if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
600289180Speter    return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
601251881Speter
602251881Speter  if (strcmp(ns, SVN_PROP_PREFIX) == 0)
603289180Speter    return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
604251881Speter
605251881Speter  if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
606251881Speter    return SVN_PROP_ENTRY_COMMITTED_REV;
607251881Speter
608251881Speter  if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
609251881Speter    return SVN_PROP_ENTRY_COMMITTED_DATE;
610251881Speter
611251881Speter  if (strcmp(name, "creator-displayname") == 0)
612251881Speter    return SVN_PROP_ENTRY_LAST_AUTHOR;
613251881Speter
614251881Speter  if (strcmp(name, "repository-uuid") == 0)
615251881Speter    return SVN_PROP_ENTRY_UUID;
616251881Speter
617251881Speter  if (strcmp(name, "lock-token") == 0)
618251881Speter    return SVN_PROP_ENTRY_LOCK_TOKEN;
619251881Speter
620251881Speter  if (strcmp(name, "checked-in") == 0)
621251881Speter    return SVN_RA_SERF__WC_CHECKED_IN_URL;
622251881Speter
623251881Speter  if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
624251881Speter    {
625251881Speter      /* Here DAV: properties not yet converted to svn: properties should be
626251881Speter         ignored. */
627251881Speter      return NULL;
628251881Speter    }
629251881Speter
630251881Speter  /* An unknown namespace, must be a custom property. */
631289180Speter  return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL);
632251881Speter}
633251881Speter
634251881Speter/*
635251881Speter * Contact the server (using CONN) to calculate baseline
636251881Speter * information for BASELINE_URL at REVISION (which may be
637251881Speter * SVN_INVALID_REVNUM to query the HEAD revision).
638251881Speter *
639251881Speter * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
640251881Speter * retrieved from the server as part of this process (which should
641251881Speter * match REVISION when REVISION is valid).  Set *BASECOLL_URL_P to the
642251881Speter * baseline collection URL.
643251881Speter */
644251881Speterstatic svn_error_t *
645251881Speterretrieve_baseline_info(svn_revnum_t *actual_revision,
646251881Speter                       const char **basecoll_url_p,
647289180Speter                       svn_ra_serf__session_t *session,
648251881Speter                       const char *baseline_url,
649251881Speter                       svn_revnum_t revision,
650251881Speter                       apr_pool_t *result_pool,
651251881Speter                       apr_pool_t *scratch_pool)
652251881Speter{
653251881Speter  apr_hash_t *props;
654251881Speter  apr_hash_t *dav_props;
655251881Speter  const char *basecoll_url;
656251881Speter
657289180Speter  SVN_ERR(svn_ra_serf__fetch_node_props(&props, session,
658251881Speter                                        baseline_url, revision,
659251881Speter                                        baseline_props,
660251881Speter                                        scratch_pool, scratch_pool));
661251881Speter  dav_props = apr_hash_get(props, "DAV:", 4);
662251881Speter  /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL.  */
663251881Speter
664251881Speter  basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
665251881Speter  if (!basecoll_url)
666251881Speter    {
667251881Speter      return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
668251881Speter                              _("The PROPFIND response did not include "
669251881Speter                                "the requested baseline-collection value"));
670251881Speter    }
671251881Speter  *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
672251881Speter
673251881Speter  if (actual_revision)
674251881Speter    {
675251881Speter      const char *version_name;
676251881Speter
677251881Speter      version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
678289180Speter      if (version_name)
679289180Speter        {
680289180Speter          apr_int64_t rev;
681289180Speter
682289180Speter          SVN_ERR(svn_cstring_atoi64(&rev, version_name));
683289180Speter          *actual_revision = (svn_revnum_t)rev;
684289180Speter        }
685289180Speter
686289180Speter      if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision))
687251881Speter        return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
688251881Speter                                _("The PROPFIND response did not include "
689251881Speter                                  "the requested version-name value"));
690251881Speter    }
691251881Speter
692251881Speter  return SVN_NO_ERROR;
693251881Speter}
694251881Speter
695251881Speter
696251881Speter/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
697251881Speter   revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
698251881Speter   collection URL is also returned.
699251881Speter
700251881Speter   Do the work over CONN.
701251881Speter
702251881Speter   *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
703251881Speter   temporary allocations will be made in SCRATCH_POOL.  */
704251881Speterstatic svn_error_t *
705251881Speterv1_get_youngest_revnum(svn_revnum_t *youngest,
706251881Speter                       const char **basecoll_url,
707289180Speter                       svn_ra_serf__session_t *session,
708251881Speter                       const char *vcc_url,
709251881Speter                       apr_pool_t *result_pool,
710251881Speter                       apr_pool_t *scratch_pool)
711251881Speter{
712251881Speter  const char *baseline_url;
713251881Speter  const char *bc_url;
714251881Speter
715251881Speter  /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
716251881Speter     revision) will return the latest Baseline resource's URL.  */
717289180Speter  SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url,
718251881Speter                                      SVN_INVALID_REVNUM,
719251881Speter                                      "checked-in",
720251881Speter                                      scratch_pool, scratch_pool));
721251881Speter  if (!baseline_url)
722251881Speter    {
723251881Speter      return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
724251881Speter                              _("The OPTIONS response did not include "
725251881Speter                                "the requested checked-in value"));
726251881Speter    }
727251881Speter  baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
728251881Speter
729251881Speter  /* From the Baseline resource, we can fetch the DAV:baseline-collection
730251881Speter     and DAV:version-name properties. The latter is the revision number,
731251881Speter     which is formally the name used in Label: headers.  */
732251881Speter
733251881Speter  /* First check baseline information cache. */
734251881Speter  SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
735251881Speter                                                  youngest,
736289180Speter                                                  session->blncache,
737251881Speter                                                  baseline_url,
738251881Speter                                                  scratch_pool));
739251881Speter  if (!bc_url)
740251881Speter    {
741289180Speter      SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session,
742251881Speter                                     baseline_url, SVN_INVALID_REVNUM,
743251881Speter                                     scratch_pool, scratch_pool));
744289180Speter      SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
745251881Speter                                        baseline_url, *youngest,
746251881Speter                                        bc_url, scratch_pool));
747251881Speter    }
748251881Speter
749251881Speter  if (basecoll_url != NULL)
750251881Speter    *basecoll_url = apr_pstrdup(result_pool, bc_url);
751251881Speter
752251881Speter  return SVN_NO_ERROR;
753251881Speter}
754251881Speter
755251881Speter
756251881Spetersvn_error_t *
757251881Spetersvn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
758251881Speter                                 svn_ra_serf__session_t *session,
759251881Speter                                 apr_pool_t *scratch_pool)
760251881Speter{
761251881Speter  const char *vcc_url;
762251881Speter
763251881Speter  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
764251881Speter    return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
765289180Speter                             youngest, session, scratch_pool));
766251881Speter
767289180Speter  SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
768251881Speter
769251881Speter  return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
770289180Speter                                                session, vcc_url,
771251881Speter                                                scratch_pool, scratch_pool));
772251881Speter}
773251881Speter
774251881Speter
775251881Speter/* Set *BC_URL to the baseline collection url for REVISION. If REVISION
776251881Speter   is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
777251881Speter
778251881Speter   *REVNUM_USED will be set to the revision used.
779251881Speter
780251881Speter   Uses the specified CONN, which is part of SESSION.
781251881Speter
782251881Speter   All allocations (results and temporary) are performed in POOL.  */
783251881Speterstatic svn_error_t *
784251881Speterget_baseline_info(const char **bc_url,
785251881Speter                  svn_revnum_t *revnum_used,
786251881Speter                  svn_ra_serf__session_t *session,
787251881Speter                  svn_revnum_t revision,
788289180Speter                  apr_pool_t *result_pool,
789289180Speter                  apr_pool_t *scratch_pool)
790251881Speter{
791251881Speter  /* If we detected HTTP v2 support on the server, we can construct
792251881Speter     the baseline collection URL ourselves, and fetch the latest
793251881Speter     revision (if needed) with an OPTIONS request.  */
794251881Speter  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
795251881Speter    {
796251881Speter      if (SVN_IS_VALID_REVNUM(revision))
797251881Speter        {
798251881Speter          *revnum_used = revision;
799251881Speter        }
800251881Speter      else
801251881Speter        {
802251881Speter          SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
803289180Speter                    revnum_used, session, scratch_pool));
804251881Speter        }
805251881Speter
806289180Speter      *bc_url = apr_psprintf(result_pool, "%s/%ld",
807251881Speter                             session->rev_root_stub, *revnum_used);
808251881Speter    }
809251881Speter
810251881Speter  /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt.  */
811251881Speter  else
812251881Speter    {
813251881Speter      const char *vcc_url;
814251881Speter
815289180Speter      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
816251881Speter
817251881Speter      if (SVN_IS_VALID_REVNUM(revision))
818251881Speter        {
819251881Speter          /* First check baseline information cache. */
820251881Speter          SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
821251881Speter                                                   session->blncache,
822289180Speter                                                   revision, result_pool));
823251881Speter          if (!*bc_url)
824251881Speter            {
825289180Speter              SVN_ERR(retrieve_baseline_info(NULL, bc_url, session,
826289180Speter                                             vcc_url, revision,
827289180Speter                                             result_pool, scratch_pool));
828251881Speter              SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
829289180Speter                                                revision, *bc_url,
830289180Speter                                                scratch_pool));
831251881Speter            }
832251881Speter
833251881Speter          *revnum_used = revision;
834251881Speter        }
835251881Speter      else
836251881Speter        {
837251881Speter          SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
838289180Speter                                         session, vcc_url,
839289180Speter                                         result_pool, scratch_pool));
840251881Speter        }
841251881Speter    }
842251881Speter
843251881Speter  return SVN_NO_ERROR;
844251881Speter}
845251881Speter
846251881Speter
847251881Spetersvn_error_t *
848251881Spetersvn_ra_serf__get_stable_url(const char **stable_url,
849251881Speter                            svn_revnum_t *latest_revnum,
850251881Speter                            svn_ra_serf__session_t *session,
851251881Speter                            const char *url,
852251881Speter                            svn_revnum_t revision,
853251881Speter                            apr_pool_t *result_pool,
854251881Speter                            apr_pool_t *scratch_pool)
855251881Speter{
856251881Speter  const char *basecoll_url;
857251881Speter  const char *repos_relpath;
858251881Speter  svn_revnum_t revnum_used;
859251881Speter
860251881Speter  /* No URL? No sweat. We'll use the session URL.  */
861251881Speter  if (! url)
862251881Speter    url = session->session_url.path;
863251881Speter
864251881Speter  SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
865289180Speter                            session, revision, scratch_pool, scratch_pool));
866251881Speter  SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
867289180Speter                                         session, scratch_pool));
868251881Speter
869251881Speter  *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
870251881Speter                                            result_pool);
871251881Speter  if (latest_revnum)
872251881Speter    *latest_revnum = revnum_used;
873251881Speter
874251881Speter  return SVN_NO_ERROR;
875251881Speter}
876251881Speter
877251881Speter
878251881Spetersvn_error_t *
879251881Spetersvn_ra_serf__fetch_dav_prop(const char **value,
880289180Speter                            svn_ra_serf__session_t *session,
881251881Speter                            const char *url,
882251881Speter                            svn_revnum_t revision,
883251881Speter                            const char *propname,
884251881Speter                            apr_pool_t *result_pool,
885251881Speter                            apr_pool_t *scratch_pool)
886251881Speter{
887251881Speter  apr_hash_t *props;
888251881Speter  apr_hash_t *dav_props;
889251881Speter
890289180Speter  SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision,
891251881Speter                                        checked_in_props,
892251881Speter                                        scratch_pool, scratch_pool));
893251881Speter  dav_props = apr_hash_get(props, "DAV:", 4);
894251881Speter  if (dav_props == NULL)
895251881Speter    return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
896251881Speter                            _("The PROPFIND response did not include "
897251881Speter                              "the requested 'DAV:' properties"));
898251881Speter
899251881Speter  /* We wouldn't get here if the resource was not found (404), so the
900251881Speter     property should be present.
901251881Speter
902251881Speter     Note: it is okay to call apr_pstrdup() with NULL.  */
903251881Speter  *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
904251881Speter
905251881Speter  return SVN_NO_ERROR;
906251881Speter}
907289180Speter
908289180Speter/* Removes all non regular properties from PROPS */
909289180Spetervoid
910289180Spetersvn_ra_serf__keep_only_regular_props(apr_hash_t *props,
911289180Speter                                     apr_pool_t *scratch_pool)
912289180Speter{
913289180Speter  apr_hash_index_t *hi;
914289180Speter
915289180Speter  for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
916289180Speter    {
917289180Speter      const char *propname = apr_hash_this_key(hi);
918289180Speter
919289180Speter      if (svn_property_kind2(propname) != svn_prop_regular_kind)
920289180Speter        svn_hash_sets(props, propname, NULL);
921289180Speter    }
922289180Speter}
923