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 {
45299742Sdim  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
67299742Sdim  /* 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
76299742Sdim  svn_ra_serf__prop_func_t prop_func;
77299742Sdim  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
121299742Sdimstatic const int propfind_expected_status[] = {
122299742Sdim  207,
123299742Sdim  0
124299742Sdim};
125251881Speter
126251881Speter/* Return the HTTP status code contained in STATUS_LINE, or 0 if
127251881Speter   there's a problem parsing it. */
128299742Sdimstatic 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    {
160299742Sdim        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    {
165299742Sdim      ctx->ps_props = apr_hash_make(svn_ra_serf__xml_state_pool(xes));
166251881Speter    }
167251881Speter
168251881Speter  return SVN_NO_ERROR;
169251881Speter}
170251881Speter
171299742Sdim/* Set PROPS for NS:NAME VAL. Helper for propfind_closed */
172299742Sdimstatic void
173299742Sdimset_ns_prop(apr_hash_t *ns_props,
174299742Sdim            const char *ns, const char *name,
175299742Sdim            const svn_string_t *val, apr_pool_t *result_pool)
176299742Sdim{
177299742Sdim  apr_hash_t *props = svn_hash_gets(ns_props, ns);
178251881Speter
179299742Sdim  if (!props)
180299742Sdim    {
181299742Sdim      props = apr_hash_make(result_pool);
182299742Sdim      ns = apr_pstrdup(result_pool, ns);
183299742Sdim      svn_hash_sets(ns_props, ns, props);
184299742Sdim    }
185299742Sdim
186299742Sdim  if (val)
187299742Sdim    {
188299742Sdim      name = apr_pstrdup(result_pool, name);
189299742Sdim      val = svn_string_dup(val, result_pool);
190299742Sdim    }
191299742Sdim
192299742Sdim  svn_hash_sets(props, name, val);
193299742Sdim}
194299742Sdim
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
224299742Sdim      SVN_ERR(ctx->prop_func(ctx->prop_func_baton,
225299742Sdim                             path,
226299742Sdim                             D_, "href",
227299742Sdim                             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.)  */
243299742Sdim      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    {
249299742Sdim      const char *encoding;
250251881Speter      const svn_string_t *val_str;
251251881Speter      const char *ns;
252251881Speter      const char *name;
253251881Speter      const char *altvalue;
254251881Speter
255299742Sdim      if ((altvalue = svn_hash_gets(attrs, "altvalue")) != NULL)
256251881Speter        {
257299742Sdim          val_str = svn_string_create(altvalue, scratch_pool);
258299742Sdim        }
259299742Sdim      else if ((encoding = svn_hash_gets(attrs, "V:encoding")) != NULL)
260299742Sdim        {
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.  */
268299742Sdim          val_str = svn_base64_decode_string(cdata, scratch_pool);
269251881Speter        }
270251881Speter      else
271251881Speter        {
272251881Speter          /* Copy into the right pool.  */
273299742Sdim          val_str = cdata;
274251881Speter        }
275251881Speter
276299742Sdim      /* 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");
288299742Sdim      name = svn_hash_gets(attrs, "name");
289251881Speter
290299742Sdim      set_ns_prop(ctx->ps_props, ns, name, val_str,
291299742Sdim                  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
299299742Sdim      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        {
306299742Sdim          apr_hash_index_t *hi_ns;
307299742Sdim          const char *path;
308299742Sdim          apr_pool_t *iterpool = svn_pool_create(scratch_pool);
309251881Speter
310251881Speter
311299742Sdim          path = svn_hash_gets(gathered, "path");
312299742Sdim          if (!path)
313299742Sdim            path = ctx->path;
314251881Speter
315299742Sdim          for (hi_ns = apr_hash_first(scratch_pool, ctx->ps_props);
316299742Sdim               hi_ns;
317299742Sdim               hi_ns = apr_hash_next(hi_ns))
318299742Sdim            {
319299742Sdim              const char *ns = apr_hash_this_key(hi_ns);
320299742Sdim              apr_hash_t *props = apr_hash_this_val(hi_ns);
321299742Sdim              apr_hash_index_t *hi_prop;
322251881Speter
323299742Sdim              svn_pool_clear(iterpool);
324251881Speter
325299742Sdim              for (hi_prop = apr_hash_first(iterpool, props);
326299742Sdim                   hi_prop;
327299742Sdim                   hi_prop = apr_hash_next(hi_prop))
328299742Sdim                {
329299742Sdim                  const char *name = apr_hash_this_key(hi_prop);
330299742Sdim                  const svn_string_t *value = apr_hash_this_val(hi_prop);
331251881Speter
332299742Sdim                  SVN_ERR(ctx->prop_func(ctx->prop_func_baton, path,
333299742Sdim                                         ns, name, value, iterpool));
334299742Sdim                }
335251881Speter            }
336299742Sdim
337299742Sdim          svn_pool_destroy(iterpool);
338251881Speter        }
339251881Speter
340299742Sdim      ctx->ps_props = NULL; /* Allocated in PROPSTAT state pool */
341251881Speter    }
342251881Speter
343299742Sdim  return SVN_NO_ERROR;
344251881Speter}
345251881Speter
346251881Speter
347251881Speter
348251881Speterstatic svn_error_t *
349251881Spetersetup_propfind_headers(serf_bucket_t *headers,
350299742Sdim                       void *setup_baton,
351299742Sdim                       apr_pool_t *pool /* request pool */,
352299742Sdim                       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
368299742Sdim/* 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,
373299742Sdim                     apr_pool_t *pool /* request pool */,
374299742Sdim                     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;
385299742Sdim  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
393251881Speter      /* <*propname* xmlns="*propns*" /> */
394251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc);
395251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
396251881Speter
397251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc);
398251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
399251881Speter
400251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"",
401251881Speter                                          sizeof(" xmlns=\"")-1,
402251881Speter                                          alloc);
403251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
404251881Speter
405299742Sdim      tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc);
406251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
407251881Speter
408251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1,
409251881Speter                                          alloc);
410251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
411251881Speter
412251881Speter      prop++;
413251881Speter    }
414251881Speter
415251881Speter  /* If we're not doing an allprop, add <prop> tags. */
416251881Speter  if (!requested_allprop)
417251881Speter    {
418251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>",
419251881Speter                                          sizeof("<prop>")-1,
420251881Speter                                          alloc);
421251881Speter      serf_bucket_aggregate_prepend(body_bkt, tmp);
422251881Speter    }
423251881Speter
424251881Speter  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER,
425251881Speter                                      sizeof(PROPFIND_HEADER)-1,
426251881Speter                                      alloc);
427251881Speter
428251881Speter  serf_bucket_aggregate_prepend(body_bkt, tmp);
429251881Speter
430251881Speter  if (!requested_allprop)
431251881Speter    {
432251881Speter      tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>",
433251881Speter                                          sizeof("</prop>")-1,
434251881Speter                                          alloc);
435251881Speter      serf_bucket_aggregate_append(body_bkt, tmp);
436251881Speter    }
437251881Speter
438251881Speter  tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER,
439251881Speter                                      sizeof(PROPFIND_TRAILER)-1,
440251881Speter                                      alloc);
441251881Speter  serf_bucket_aggregate_append(body_bkt, tmp);
442251881Speter
443251881Speter  *bkt = body_bkt;
444251881Speter  return SVN_NO_ERROR;
445251881Speter}
446251881Speter
447251881Speter
448251881Spetersvn_error_t *
449299742Sdimsvn_ra_serf__create_propfind_handler(svn_ra_serf__handler_t **propfind_handler,
450299742Sdim                                     svn_ra_serf__session_t *sess,
451299742Sdim                                     const char *path,
452299742Sdim                                     svn_revnum_t rev,
453299742Sdim                                     const char *depth,
454299742Sdim                                     const svn_ra_serf__dav_props_t *find_props,
455299742Sdim                                     svn_ra_serf__prop_func_t prop_func,
456299742Sdim                                     void *prop_func_baton,
457299742Sdim                                     apr_pool_t *pool)
458251881Speter{
459251881Speter  propfind_context_t *new_prop_ctx;
460251881Speter  svn_ra_serf__handler_t *handler;
461251881Speter  svn_ra_serf__xml_context_t *xmlctx;
462251881Speter
463251881Speter  new_prop_ctx = apr_pcalloc(pool, sizeof(*new_prop_ctx));
464251881Speter
465251881Speter  new_prop_ctx->path = path;
466251881Speter  new_prop_ctx->find_props = find_props;
467299742Sdim  new_prop_ctx->prop_func = prop_func;
468299742Sdim  new_prop_ctx->prop_func_baton = prop_func_baton;
469251881Speter  new_prop_ctx->depth = depth;
470251881Speter
471251881Speter  if (SVN_IS_VALID_REVNUM(rev))
472251881Speter    {
473251881Speter      new_prop_ctx->label = apr_ltoa(pool, rev);
474251881Speter    }
475251881Speter  else
476251881Speter    {
477251881Speter      new_prop_ctx->label = NULL;
478251881Speter    }
479251881Speter
480251881Speter  xmlctx = svn_ra_serf__xml_context_create(propfind_ttable,
481251881Speter                                           propfind_opened,
482251881Speter                                           propfind_closed,
483251881Speter                                           NULL,
484251881Speter                                           new_prop_ctx,
485251881Speter                                           pool);
486299742Sdim  handler = svn_ra_serf__create_expat_handler(sess, xmlctx,
487299742Sdim                                              propfind_expected_status,
488299742Sdim                                              pool);
489251881Speter
490251881Speter  handler->method = "PROPFIND";
491251881Speter  handler->path = path;
492251881Speter  handler->body_delegate = create_propfind_body;
493251881Speter  handler->body_type = "text/xml";
494251881Speter  handler->body_delegate_baton = new_prop_ctx;
495251881Speter  handler->header_delegate = setup_propfind_headers;
496251881Speter  handler->header_delegate_baton = new_prop_ctx;
497251881Speter
498299742Sdim  handler->no_dav_headers = TRUE;
499251881Speter
500251881Speter  new_prop_ctx->handler = handler;
501251881Speter
502251881Speter  *propfind_handler = handler;
503251881Speter
504251881Speter  return SVN_NO_ERROR;
505251881Speter}
506251881Speter
507251881Spetersvn_error_t *
508299742Sdimsvn_ra_serf__deliver_svn_props(void *baton,
509299742Sdim                               const char *path,
510299742Sdim                               const char *ns,
511299742Sdim                               const char *name,
512299742Sdim                               const svn_string_t *value,
513299742Sdim                               apr_pool_t *scratch_pool)
514251881Speter{
515299742Sdim  apr_hash_t *props = baton;
516299742Sdim  apr_pool_t *result_pool = apr_hash_pool_get(props);
517299742Sdim  const char *prop_name;
518251881Speter
519299742Sdim  prop_name = svn_ra_serf__svnname_from_wirename(ns, name, result_pool);
520299742Sdim  if (prop_name == NULL)
521299742Sdim    return SVN_NO_ERROR;
522251881Speter
523299742Sdim  svn_hash_sets(props, prop_name, svn_string_dup(value, result_pool));
524251881Speter
525299742Sdim  return SVN_NO_ERROR;
526251881Speter}
527251881Speter
528251881Speter/*
529299742Sdim * Implementation of svn_ra_serf__prop_func_t that delivers all DAV properties
530299742Sdim * in (const char * -> apr_hash_t *) on Namespace pointing to a second hash
531299742Sdim *    (const char * -> svn_string_t *) to the values.
532251881Speter */
533299742Sdimstatic svn_error_t *
534299742Sdimdeliver_node_props(void *baton,
535299742Sdim                  const char *path,
536299742Sdim                  const char *ns,
537299742Sdim                  const char *name,
538299742Sdim                  const svn_string_t *value,
539299742Sdim                  apr_pool_t *scratch_pool)
540251881Speter{
541299742Sdim  apr_hash_t *nss = baton;
542299742Sdim  apr_hash_t *props;
543299742Sdim  apr_pool_t *result_pool = apr_hash_pool_get(nss);
544251881Speter
545299742Sdim  props = svn_hash_gets(nss, ns);
546251881Speter
547299742Sdim  if (!props)
548299742Sdim    {
549299742Sdim      props = apr_hash_make(result_pool);
550251881Speter
551299742Sdim      ns = apr_pstrdup(result_pool, ns);
552299742Sdim      svn_hash_sets(nss, ns, props);
553299742Sdim    }
554299742Sdim
555299742Sdim  name = apr_pstrdup(result_pool, name);
556299742Sdim  svn_hash_sets(props, name, svn_string_dup(value, result_pool));
557299742Sdim
558251881Speter  return SVN_NO_ERROR;
559251881Speter}
560251881Speter
561251881Spetersvn_error_t *
562251881Spetersvn_ra_serf__fetch_node_props(apr_hash_t **results,
563299742Sdim                              svn_ra_serf__session_t *session,
564251881Speter                              const char *url,
565251881Speter                              svn_revnum_t revision,
566251881Speter                              const svn_ra_serf__dav_props_t *which_props,
567251881Speter                              apr_pool_t *result_pool,
568251881Speter                              apr_pool_t *scratch_pool)
569251881Speter{
570299742Sdim  apr_hash_t *props;
571299742Sdim  svn_ra_serf__handler_t *handler;
572251881Speter
573299742Sdim  props = apr_hash_make(result_pool);
574251881Speter
575299742Sdim  SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session,
576299742Sdim                                               url, revision, "0", which_props,
577299742Sdim                                               deliver_node_props,
578299742Sdim                                               props, scratch_pool));
579251881Speter
580299742Sdim  SVN_ERR(svn_ra_serf__context_run_one(handler, scratch_pool));
581251881Speter
582299742Sdim  *results = props;
583251881Speter  return SVN_NO_ERROR;
584251881Speter}
585251881Speter
586251881Speterconst char *
587251881Spetersvn_ra_serf__svnname_from_wirename(const char *ns,
588251881Speter                                   const char *name,
589251881Speter                                   apr_pool_t *result_pool)
590251881Speter{
591251881Speter  if (*ns == '\0' || strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
592251881Speter    return apr_pstrdup(result_pool, name);
593251881Speter
594251881Speter  if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
595299742Sdim    return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
596251881Speter
597251881Speter  if (strcmp(ns, SVN_PROP_PREFIX) == 0)
598299742Sdim    return apr_pstrcat(result_pool, SVN_PROP_PREFIX, name, SVN_VA_NULL);
599251881Speter
600251881Speter  if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
601251881Speter    return SVN_PROP_ENTRY_COMMITTED_REV;
602251881Speter
603251881Speter  if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
604251881Speter    return SVN_PROP_ENTRY_COMMITTED_DATE;
605251881Speter
606251881Speter  if (strcmp(name, "creator-displayname") == 0)
607251881Speter    return SVN_PROP_ENTRY_LAST_AUTHOR;
608251881Speter
609251881Speter  if (strcmp(name, "repository-uuid") == 0)
610251881Speter    return SVN_PROP_ENTRY_UUID;
611251881Speter
612251881Speter  if (strcmp(name, "lock-token") == 0)
613251881Speter    return SVN_PROP_ENTRY_LOCK_TOKEN;
614251881Speter
615251881Speter  if (strcmp(name, "checked-in") == 0)
616251881Speter    return SVN_RA_SERF__WC_CHECKED_IN_URL;
617251881Speter
618251881Speter  if (strcmp(ns, "DAV:") == 0 || strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
619251881Speter    {
620251881Speter      /* Here DAV: properties not yet converted to svn: properties should be
621251881Speter         ignored. */
622251881Speter      return NULL;
623251881Speter    }
624251881Speter
625251881Speter  /* An unknown namespace, must be a custom property. */
626299742Sdim  return apr_pstrcat(result_pool, ns, name, SVN_VA_NULL);
627251881Speter}
628251881Speter
629251881Speter/*
630251881Speter * Contact the server (using CONN) to calculate baseline
631251881Speter * information for BASELINE_URL at REVISION (which may be
632251881Speter * SVN_INVALID_REVNUM to query the HEAD revision).
633251881Speter *
634251881Speter * If ACTUAL_REVISION is non-NULL, set *ACTUAL_REVISION to revision
635251881Speter * retrieved from the server as part of this process (which should
636251881Speter * match REVISION when REVISION is valid).  Set *BASECOLL_URL_P to the
637251881Speter * baseline collection URL.
638251881Speter */
639251881Speterstatic svn_error_t *
640251881Speterretrieve_baseline_info(svn_revnum_t *actual_revision,
641251881Speter                       const char **basecoll_url_p,
642299742Sdim                       svn_ra_serf__session_t *session,
643251881Speter                       const char *baseline_url,
644251881Speter                       svn_revnum_t revision,
645251881Speter                       apr_pool_t *result_pool,
646251881Speter                       apr_pool_t *scratch_pool)
647251881Speter{
648251881Speter  apr_hash_t *props;
649251881Speter  apr_hash_t *dav_props;
650251881Speter  const char *basecoll_url;
651251881Speter
652299742Sdim  SVN_ERR(svn_ra_serf__fetch_node_props(&props, session,
653251881Speter                                        baseline_url, revision,
654251881Speter                                        baseline_props,
655251881Speter                                        scratch_pool, scratch_pool));
656251881Speter  dav_props = apr_hash_get(props, "DAV:", 4);
657251881Speter  /* If DAV_PROPS is NULL, then svn_prop_get_value() will return NULL.  */
658251881Speter
659251881Speter  basecoll_url = svn_prop_get_value(dav_props, "baseline-collection");
660251881Speter  if (!basecoll_url)
661251881Speter    {
662251881Speter      return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
663251881Speter                              _("The PROPFIND response did not include "
664251881Speter                                "the requested baseline-collection value"));
665251881Speter    }
666251881Speter  *basecoll_url_p = svn_urlpath__canonicalize(basecoll_url, result_pool);
667251881Speter
668251881Speter  if (actual_revision)
669251881Speter    {
670251881Speter      const char *version_name;
671251881Speter
672251881Speter      version_name = svn_prop_get_value(dav_props, SVN_DAV__VERSION_NAME);
673299742Sdim      if (version_name)
674299742Sdim        {
675299742Sdim          apr_int64_t rev;
676299742Sdim
677299742Sdim          SVN_ERR(svn_cstring_atoi64(&rev, version_name));
678299742Sdim          *actual_revision = (svn_revnum_t)rev;
679299742Sdim        }
680299742Sdim
681299742Sdim      if (!version_name || !SVN_IS_VALID_REVNUM(*actual_revision))
682251881Speter        return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
683251881Speter                                _("The PROPFIND response did not include "
684251881Speter                                  "the requested version-name value"));
685251881Speter    }
686251881Speter
687251881Speter  return SVN_NO_ERROR;
688251881Speter}
689251881Speter
690251881Speter
691251881Speter/* For HTTPv1 servers, do a PROPFIND dance on the VCC to fetch the youngest
692251881Speter   revnum. If BASECOLL_URL is non-NULL, then the corresponding baseline
693251881Speter   collection URL is also returned.
694251881Speter
695251881Speter   Do the work over CONN.
696251881Speter
697251881Speter   *BASECOLL_URL (if requested) will be allocated in RESULT_POOL. All
698251881Speter   temporary allocations will be made in SCRATCH_POOL.  */
699251881Speterstatic svn_error_t *
700251881Speterv1_get_youngest_revnum(svn_revnum_t *youngest,
701251881Speter                       const char **basecoll_url,
702299742Sdim                       svn_ra_serf__session_t *session,
703251881Speter                       const char *vcc_url,
704251881Speter                       apr_pool_t *result_pool,
705251881Speter                       apr_pool_t *scratch_pool)
706251881Speter{
707251881Speter  const char *baseline_url;
708251881Speter  const char *bc_url;
709251881Speter
710251881Speter  /* Fetching DAV:checked-in from the VCC (with no Label: to specify a
711251881Speter     revision) will return the latest Baseline resource's URL.  */
712299742Sdim  SVN_ERR(svn_ra_serf__fetch_dav_prop(&baseline_url, session, vcc_url,
713251881Speter                                      SVN_INVALID_REVNUM,
714251881Speter                                      "checked-in",
715251881Speter                                      scratch_pool, scratch_pool));
716251881Speter  if (!baseline_url)
717251881Speter    {
718251881Speter      return svn_error_create(SVN_ERR_RA_DAV_OPTIONS_REQ_FAILED, NULL,
719251881Speter                              _("The OPTIONS response did not include "
720251881Speter                                "the requested checked-in value"));
721251881Speter    }
722251881Speter  baseline_url = svn_urlpath__canonicalize(baseline_url, scratch_pool);
723251881Speter
724251881Speter  /* From the Baseline resource, we can fetch the DAV:baseline-collection
725251881Speter     and DAV:version-name properties. The latter is the revision number,
726251881Speter     which is formally the name used in Label: headers.  */
727251881Speter
728251881Speter  /* First check baseline information cache. */
729251881Speter  SVN_ERR(svn_ra_serf__blncache_get_baseline_info(&bc_url,
730251881Speter                                                  youngest,
731299742Sdim                                                  session->blncache,
732251881Speter                                                  baseline_url,
733251881Speter                                                  scratch_pool));
734251881Speter  if (!bc_url)
735251881Speter    {
736299742Sdim      SVN_ERR(retrieve_baseline_info(youngest, &bc_url, session,
737251881Speter                                     baseline_url, SVN_INVALID_REVNUM,
738251881Speter                                     scratch_pool, scratch_pool));
739299742Sdim      SVN_ERR(svn_ra_serf__blncache_set(session->blncache,
740251881Speter                                        baseline_url, *youngest,
741251881Speter                                        bc_url, scratch_pool));
742251881Speter    }
743251881Speter
744251881Speter  if (basecoll_url != NULL)
745251881Speter    *basecoll_url = apr_pstrdup(result_pool, bc_url);
746251881Speter
747251881Speter  return SVN_NO_ERROR;
748251881Speter}
749251881Speter
750251881Speter
751251881Spetersvn_error_t *
752251881Spetersvn_ra_serf__get_youngest_revnum(svn_revnum_t *youngest,
753251881Speter                                 svn_ra_serf__session_t *session,
754251881Speter                                 apr_pool_t *scratch_pool)
755251881Speter{
756251881Speter  const char *vcc_url;
757251881Speter
758251881Speter  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
759251881Speter    return svn_error_trace(svn_ra_serf__v2_get_youngest_revnum(
760299742Sdim                             youngest, session, scratch_pool));
761251881Speter
762299742Sdim  SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
763251881Speter
764251881Speter  return svn_error_trace(v1_get_youngest_revnum(youngest, NULL,
765299742Sdim                                                session, vcc_url,
766251881Speter                                                scratch_pool, scratch_pool));
767251881Speter}
768251881Speter
769251881Speter
770251881Speter/* Set *BC_URL to the baseline collection url for REVISION. If REVISION
771251881Speter   is SVN_INVALID_REVNUM, then the youngest revnum ("HEAD") is used.
772251881Speter
773251881Speter   *REVNUM_USED will be set to the revision used.
774251881Speter
775251881Speter   Uses the specified CONN, which is part of SESSION.
776251881Speter
777251881Speter   All allocations (results and temporary) are performed in POOL.  */
778251881Speterstatic svn_error_t *
779251881Speterget_baseline_info(const char **bc_url,
780251881Speter                  svn_revnum_t *revnum_used,
781251881Speter                  svn_ra_serf__session_t *session,
782251881Speter                  svn_revnum_t revision,
783299742Sdim                  apr_pool_t *result_pool,
784299742Sdim                  apr_pool_t *scratch_pool)
785251881Speter{
786251881Speter  /* If we detected HTTP v2 support on the server, we can construct
787251881Speter     the baseline collection URL ourselves, and fetch the latest
788251881Speter     revision (if needed) with an OPTIONS request.  */
789251881Speter  if (SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session))
790251881Speter    {
791251881Speter      if (SVN_IS_VALID_REVNUM(revision))
792251881Speter        {
793251881Speter          *revnum_used = revision;
794251881Speter        }
795251881Speter      else
796251881Speter        {
797251881Speter          SVN_ERR(svn_ra_serf__v2_get_youngest_revnum(
798299742Sdim                    revnum_used, session, scratch_pool));
799251881Speter        }
800251881Speter
801299742Sdim      *bc_url = apr_psprintf(result_pool, "%s/%ld",
802251881Speter                             session->rev_root_stub, *revnum_used);
803251881Speter    }
804251881Speter
805251881Speter  /* Otherwise, we fall back to the old VCC_URL PROPFIND hunt.  */
806251881Speter  else
807251881Speter    {
808251881Speter      const char *vcc_url;
809251881Speter
810299742Sdim      SVN_ERR(svn_ra_serf__discover_vcc(&vcc_url, session, scratch_pool));
811251881Speter
812251881Speter      if (SVN_IS_VALID_REVNUM(revision))
813251881Speter        {
814251881Speter          /* First check baseline information cache. */
815251881Speter          SVN_ERR(svn_ra_serf__blncache_get_bc_url(bc_url,
816251881Speter                                                   session->blncache,
817299742Sdim                                                   revision, result_pool));
818251881Speter          if (!*bc_url)
819251881Speter            {
820299742Sdim              SVN_ERR(retrieve_baseline_info(NULL, bc_url, session,
821299742Sdim                                             vcc_url, revision,
822299742Sdim                                             result_pool, scratch_pool));
823251881Speter              SVN_ERR(svn_ra_serf__blncache_set(session->blncache, NULL,
824299742Sdim                                                revision, *bc_url,
825299742Sdim                                                scratch_pool));
826251881Speter            }
827251881Speter
828251881Speter          *revnum_used = revision;
829251881Speter        }
830251881Speter      else
831251881Speter        {
832251881Speter          SVN_ERR(v1_get_youngest_revnum(revnum_used, bc_url,
833299742Sdim                                         session, vcc_url,
834299742Sdim                                         result_pool, scratch_pool));
835251881Speter        }
836251881Speter    }
837251881Speter
838251881Speter  return SVN_NO_ERROR;
839251881Speter}
840251881Speter
841251881Speter
842251881Spetersvn_error_t *
843251881Spetersvn_ra_serf__get_stable_url(const char **stable_url,
844251881Speter                            svn_revnum_t *latest_revnum,
845251881Speter                            svn_ra_serf__session_t *session,
846251881Speter                            const char *url,
847251881Speter                            svn_revnum_t revision,
848251881Speter                            apr_pool_t *result_pool,
849251881Speter                            apr_pool_t *scratch_pool)
850251881Speter{
851251881Speter  const char *basecoll_url;
852251881Speter  const char *repos_relpath;
853251881Speter  svn_revnum_t revnum_used;
854251881Speter
855251881Speter  /* No URL? No sweat. We'll use the session URL.  */
856251881Speter  if (! url)
857251881Speter    url = session->session_url.path;
858251881Speter
859251881Speter  SVN_ERR(get_baseline_info(&basecoll_url, &revnum_used,
860299742Sdim                            session, revision, scratch_pool, scratch_pool));
861251881Speter  SVN_ERR(svn_ra_serf__get_relative_path(&repos_relpath, url,
862299742Sdim                                         session, scratch_pool));
863251881Speter
864251881Speter  *stable_url = svn_path_url_add_component2(basecoll_url, repos_relpath,
865251881Speter                                            result_pool);
866251881Speter  if (latest_revnum)
867251881Speter    *latest_revnum = revnum_used;
868251881Speter
869251881Speter  return SVN_NO_ERROR;
870251881Speter}
871251881Speter
872251881Speter
873251881Spetersvn_error_t *
874251881Spetersvn_ra_serf__fetch_dav_prop(const char **value,
875299742Sdim                            svn_ra_serf__session_t *session,
876251881Speter                            const char *url,
877251881Speter                            svn_revnum_t revision,
878251881Speter                            const char *propname,
879251881Speter                            apr_pool_t *result_pool,
880251881Speter                            apr_pool_t *scratch_pool)
881251881Speter{
882251881Speter  apr_hash_t *props;
883251881Speter  apr_hash_t *dav_props;
884251881Speter
885299742Sdim  SVN_ERR(svn_ra_serf__fetch_node_props(&props, session, url, revision,
886251881Speter                                        checked_in_props,
887251881Speter                                        scratch_pool, scratch_pool));
888251881Speter  dav_props = apr_hash_get(props, "DAV:", 4);
889251881Speter  if (dav_props == NULL)
890251881Speter    return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
891251881Speter                            _("The PROPFIND response did not include "
892251881Speter                              "the requested 'DAV:' properties"));
893251881Speter
894251881Speter  /* We wouldn't get here if the resource was not found (404), so the
895251881Speter     property should be present.
896251881Speter
897251881Speter     Note: it is okay to call apr_pstrdup() with NULL.  */
898251881Speter  *value = apr_pstrdup(result_pool, svn_prop_get_value(dav_props, propname));
899251881Speter
900251881Speter  return SVN_NO_ERROR;
901251881Speter}
902299742Sdim
903299742Sdim/* Removes all non regular properties from PROPS */
904299742Sdimvoid
905299742Sdimsvn_ra_serf__keep_only_regular_props(apr_hash_t *props,
906299742Sdim                                     apr_pool_t *scratch_pool)
907299742Sdim{
908299742Sdim  apr_hash_index_t *hi;
909299742Sdim
910299742Sdim  for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
911299742Sdim    {
912299742Sdim      const char *propname = apr_hash_this_key(hi);
913299742Sdim
914299742Sdim      if (svn_property_kind2(propname) != svn_prop_regular_kind)
915299742Sdim        svn_hash_sets(props, propname, NULL);
916299742Sdim    }
917299742Sdim}
918