1/*
2 * stat.c :  file and directory stat and read functions
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#define APR_WANT_STRFUNC
27#include <apr_want.h>
28
29#include <serf.h>
30
31#include "svn_private_config.h"
32#include "svn_pools.h"
33#include "svn_xml.h"
34#include "../libsvn_ra/ra_loader.h"
35#include "svn_config.h"
36#include "svn_dirent_uri.h"
37#include "svn_hash.h"
38#include "svn_path.h"
39#include "svn_props.h"
40#include "svn_time.h"
41#include "svn_version.h"
42
43#include "private/svn_dav_protocol.h"
44#include "private/svn_dep_compat.h"
45#include "private/svn_fspath.h"
46
47#include "ra_serf.h"
48
49
50
51/* Implements svn_ra__vtable_t.check_path(). */
52svn_error_t *
53svn_ra_serf__check_path(svn_ra_session_t *ra_session,
54                        const char *relpath,
55                        svn_revnum_t revision,
56                        svn_node_kind_t *kind,
57                        apr_pool_t *scratch_pool)
58{
59  svn_ra_serf__session_t *session = ra_session->priv;
60  apr_hash_t *props;
61  svn_error_t *err;
62  const char *url;
63
64  url = session->session_url.path;
65
66  /* If we have a relative path, append it. */
67  if (relpath)
68    url = svn_path_url_add_component2(url, relpath, scratch_pool);
69
70  /* If we were given a specific revision, get a URL that refers to that
71     specific revision (rather than floating with HEAD).  */
72  if (SVN_IS_VALID_REVNUM(revision))
73    {
74      SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
75                                          session,
76                                          url, revision,
77                                          scratch_pool, scratch_pool));
78    }
79
80  /* URL is stable, so we use SVN_INVALID_REVNUM since it is now irrelevant.
81     Or we started with SVN_INVALID_REVNUM and URL may be floating.  */
82  err = svn_ra_serf__fetch_node_props(&props, session,
83                                      url, SVN_INVALID_REVNUM,
84                                      check_path_props,
85                                      scratch_pool, scratch_pool);
86
87  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
88    {
89      svn_error_clear(err);
90      *kind = svn_node_none;
91    }
92  else
93    {
94      apr_hash_t *dav_props;
95      const char *res_type;
96
97      /* Any other error, raise to caller. */
98      SVN_ERR(err);
99
100      dav_props = apr_hash_get(props, "DAV:", 4);
101      res_type = svn_prop_get_value(dav_props, "resourcetype");
102      if (!res_type)
103        {
104          /* How did this happen? */
105          return svn_error_create(SVN_ERR_RA_DAV_PROPS_NOT_FOUND, NULL,
106                                 _("The PROPFIND response did not include the "
107                                   "requested resourcetype value"));
108        }
109
110      if (strcmp(res_type, "collection") == 0)
111        *kind = svn_node_dir;
112      else
113        *kind = svn_node_file;
114    }
115
116  return SVN_NO_ERROR;
117}
118
119
120/* Baton for fill_dirent_propfunc() */
121struct fill_dirent_baton_t
122{
123  /* Update the fields in this entry.  */
124  svn_dirent_t *entry;
125
126  svn_tristate_t *supports_deadprop_count;
127
128  /* If allocations are necessary, then use this pool.  */
129  apr_pool_t *result_pool;
130};
131
132/* Implements svn_ra_serf__prop_func_t */
133static svn_error_t *
134fill_dirent_propfunc(void *baton,
135                     const char *path,
136                     const char *ns,
137                     const char *name,
138                     const svn_string_t *val,
139                     apr_pool_t *scratch_pool)
140{
141  struct fill_dirent_baton_t *fdb = baton;
142
143  if (strcmp(ns, "DAV:") == 0)
144    {
145      if (strcmp(name, SVN_DAV__VERSION_NAME) == 0)
146        {
147          apr_int64_t rev;
148          SVN_ERR(svn_cstring_atoi64(&rev, val->data));
149
150          fdb->entry->created_rev = (svn_revnum_t)rev;
151        }
152      else if (strcmp(name, "creator-displayname") == 0)
153        {
154          fdb->entry->last_author = apr_pstrdup(fdb->result_pool, val->data);
155        }
156      else if (strcmp(name, SVN_DAV__CREATIONDATE) == 0)
157        {
158          SVN_ERR(svn_time_from_cstring(&fdb->entry->time,
159                                        val->data,
160                                        fdb->result_pool));
161        }
162      else if (strcmp(name, "getcontentlength") == 0)
163        {
164          /* 'getcontentlength' property is empty for directories. */
165          if (val->len)
166            {
167              SVN_ERR(svn_cstring_atoi64(&fdb->entry->size, val->data));
168            }
169        }
170      else if (strcmp(name, "resourcetype") == 0)
171        {
172          if (strcmp(val->data, "collection") == 0)
173            {
174              fdb->entry->kind = svn_node_dir;
175            }
176          else
177            {
178              fdb->entry->kind = svn_node_file;
179            }
180        }
181    }
182  else if (strcmp(ns, SVN_DAV_PROP_NS_CUSTOM) == 0)
183    {
184      fdb->entry->has_props = TRUE;
185    }
186  else if (strcmp(ns, SVN_DAV_PROP_NS_SVN) == 0)
187    {
188      fdb->entry->has_props = TRUE;
189    }
190  else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0)
191    {
192      if(strcmp(name, "deadprop-count") == 0)
193        {
194          if (*val->data)
195            {
196              /* Note: 1.8.x and earlier servers send the count proper; 1.9.0
197               * and newer send "1" if there are properties and "0" otherwise.
198               */
199              apr_int64_t deadprop_count;
200              SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
201              fdb->entry->has_props = deadprop_count > 0;
202              if (fdb->supports_deadprop_count)
203                *fdb->supports_deadprop_count = svn_tristate_true;
204            }
205          else if (fdb->supports_deadprop_count)
206            *fdb->supports_deadprop_count = svn_tristate_false;
207        }
208    }
209
210  return SVN_NO_ERROR;
211}
212
213static const svn_ra_serf__dav_props_t *
214get_dirent_props(apr_uint32_t dirent_fields,
215                 svn_ra_serf__session_t *session,
216                 apr_pool_t *pool)
217{
218  svn_ra_serf__dav_props_t *prop;
219  apr_array_header_t *props = svn_ra_serf__get_dirent_props(dirent_fields,
220                                                            session, pool);
221
222  prop = apr_array_push(props);
223  prop->xmlns = NULL;
224  prop->name = NULL;
225
226  return (svn_ra_serf__dav_props_t *) props->elts;
227}
228
229/* Implements svn_ra__vtable_t.stat(). */
230svn_error_t *
231svn_ra_serf__stat(svn_ra_session_t *ra_session,
232                  const char *relpath,
233                  svn_revnum_t revision,
234                  svn_dirent_t **dirent,
235                  apr_pool_t *pool)
236{
237  svn_ra_serf__session_t *session = ra_session->priv;
238  svn_error_t *err;
239  struct fill_dirent_baton_t fdb;
240  svn_tristate_t deadprop_count = svn_tristate_unknown;
241  svn_ra_serf__handler_t *handler;
242  const char *url;
243
244  url = session->session_url.path;
245
246  /* If we have a relative path, append it. */
247  if (relpath)
248    url = svn_path_url_add_component2(url, relpath, pool);
249
250  /* If we were given a specific revision, get a URL that refers to that
251     specific revision (rather than floating with HEAD).  */
252  if (SVN_IS_VALID_REVNUM(revision))
253    {
254      SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
255                                          session,
256                                          url, revision,
257                                          pool, pool));
258    }
259
260  fdb.entry = svn_dirent_create(pool);
261  fdb.supports_deadprop_count = &deadprop_count;
262  fdb.result_pool = pool;
263
264  SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, url,
265                                               SVN_INVALID_REVNUM, "0",
266                                               get_dirent_props(SVN_DIRENT_ALL,
267                                                                session,
268                                                                pool),
269                                               fill_dirent_propfunc, &fdb, pool));
270
271  err = svn_ra_serf__context_run_one(handler, pool);
272
273  if (err)
274    {
275      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
276        {
277          svn_error_clear(err);
278          *dirent = NULL;
279          return SVN_NO_ERROR;
280        }
281      else
282        return svn_error_trace(err);
283    }
284
285  if (deadprop_count == svn_tristate_false
286      && session->supports_deadprop_count == svn_tristate_unknown
287      && !fdb.entry->has_props)
288    {
289      /* We have to requery as the server didn't give us the right
290         information */
291      session->supports_deadprop_count = svn_tristate_false;
292
293      /* Run the same handler again */
294      SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
295    }
296
297  if (deadprop_count != svn_tristate_unknown)
298    session->supports_deadprop_count = deadprop_count;
299
300  *dirent = fdb.entry;
301
302  return SVN_NO_ERROR;
303}
304
305/* Baton for get_dir_dirents_cb and get_dir_props_cb */
306struct get_dir_baton_t
307{
308  apr_pool_t *result_pool;
309  apr_hash_t *dirents;
310  apr_hash_t *ret_props;
311  svn_boolean_t is_directory;
312  svn_tristate_t supports_deadprop_count;
313  const char *path;
314};
315
316/* Implements svn_ra_serf__prop_func_t */
317static svn_error_t *
318get_dir_dirents_cb(void *baton,
319                   const char *path,
320                   const char *ns,
321                   const char *name,
322                   const svn_string_t *value,
323                   apr_pool_t *scratch_pool)
324{
325  struct get_dir_baton_t *db = baton;
326  const char *relpath;
327
328  relpath = svn_fspath__skip_ancestor(db->path, path);
329
330  if (relpath && relpath[0] != '\0')
331    {
332      struct fill_dirent_baton_t fdb;
333
334      relpath = svn_path_uri_decode(relpath, scratch_pool);
335      fdb.entry = svn_hash_gets(db->dirents, relpath);
336
337      if (!fdb.entry)
338        {
339          fdb.entry = svn_dirent_create(db->result_pool);
340          svn_hash_sets(db->dirents,
341                        apr_pstrdup(db->result_pool, relpath),
342                        fdb.entry);
343        }
344
345      fdb.result_pool = db->result_pool;
346      fdb.supports_deadprop_count = &db->supports_deadprop_count;
347      SVN_ERR(fill_dirent_propfunc(&fdb, path, ns, name, value, scratch_pool));
348    }
349  else if (relpath && !db->is_directory)
350    {
351      if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
352        {
353          if (strcmp(value->data, "collection") != 0)
354            {
355              /* Tell a lie to exit early */
356              return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
357                                      _("Can't get properties of non-directory"));
358            }
359          else
360            db->is_directory = TRUE;
361        }
362    }
363
364  return SVN_NO_ERROR;
365}
366
367/* Implements svn_ra_serf__prop_func */
368static svn_error_t *
369get_dir_props_cb(void *baton,
370                 const char *path,
371                 const char *ns,
372                 const char *name,
373                 const svn_string_t *value,
374                 apr_pool_t *scratch_pool)
375{
376  struct get_dir_baton_t *db = baton;
377  const char *propname;
378
379  propname = svn_ra_serf__svnname_from_wirename(ns, name, db->result_pool);
380  if (propname)
381    {
382      svn_hash_sets(db->ret_props, propname,
383                    svn_string_dup(value, db->result_pool));
384      return SVN_NO_ERROR;
385    }
386
387  if (!db->is_directory)
388    {
389      if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
390        {
391          if (strcmp(value->data, "collection") != 0)
392            {
393              /* Tell a lie to exit early */
394              return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
395                                      _("Can't get properties of non-directory"));
396            }
397          else
398            db->is_directory = TRUE;
399        }
400    }
401
402  return SVN_NO_ERROR;
403}
404
405/* Implements svn_ra__vtable_t.get_dir(). */
406svn_error_t *
407svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
408                     apr_hash_t **dirents,
409                     svn_revnum_t *fetched_rev,
410                     apr_hash_t **ret_props,
411                     const char *rel_path,
412                     svn_revnum_t revision,
413                     apr_uint32_t dirent_fields,
414                     apr_pool_t *result_pool)
415{
416  svn_ra_serf__session_t *session = ra_session->priv;
417  apr_pool_t *scratch_pool = svn_pool_create(result_pool);
418  svn_ra_serf__handler_t *dirent_handler = NULL;
419  svn_ra_serf__handler_t *props_handler = NULL;
420  const char *path;
421  struct get_dir_baton_t gdb;
422  svn_error_t *err = SVN_NO_ERROR;
423
424  gdb.result_pool = result_pool;
425  gdb.is_directory = FALSE;
426  gdb.supports_deadprop_count = svn_tristate_unknown;
427
428  path = session->session_url.path;
429
430  /* If we have a relative path, URI encode and append it. */
431  if (rel_path)
432    {
433      path = svn_path_url_add_component2(path, rel_path, scratch_pool);
434    }
435
436  /* If the user specified a peg revision other than HEAD, we have to fetch
437     the baseline collection url for that revision. If not, we can use the
438     public url. */
439  if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
440    {
441      SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
442                                          session,
443                                          path, revision,
444                                          scratch_pool, scratch_pool));
445      revision = SVN_INVALID_REVNUM;
446    }
447  /* REVISION is always SVN_INVALID_REVNUM  */
448  SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
449
450  gdb.path = path;
451
452  /* If we're asked for children, fetch them now. */
453  if (dirents)
454    {
455      /* Always request node kind to check that path is really a
456       * directory. */
457      if (!ret_props)
458        dirent_fields |= SVN_DIRENT_KIND;
459
460      gdb.dirents = apr_hash_make(result_pool);
461
462      SVN_ERR(svn_ra_serf__create_propfind_handler(
463                                          &dirent_handler, session,
464                                          path, SVN_INVALID_REVNUM, "1",
465                                          get_dirent_props(dirent_fields,
466                                                           session,
467                                                           scratch_pool),
468                                          get_dir_dirents_cb, &gdb,
469                                          scratch_pool));
470
471      svn_ra_serf__request_create(dirent_handler);
472    }
473  else
474    gdb.dirents = NULL;
475
476  if (ret_props)
477    {
478      gdb.ret_props = apr_hash_make(result_pool);
479      SVN_ERR(svn_ra_serf__create_propfind_handler(
480                                          &props_handler, session,
481                                          path, SVN_INVALID_REVNUM, "0",
482                                          all_props,
483                                          get_dir_props_cb, &gdb,
484                                          scratch_pool));
485
486      svn_ra_serf__request_create(props_handler);
487    }
488  else
489    gdb.ret_props = NULL;
490
491  if (dirent_handler)
492    {
493      err = svn_error_trace(
494              svn_ra_serf__context_run_wait(&dirent_handler->done,
495                                            session,
496                                            scratch_pool));
497
498      if (err)
499        {
500          svn_pool_clear(scratch_pool); /* Unregisters outstanding requests */
501          return err;
502        }
503
504      if (gdb.supports_deadprop_count == svn_tristate_false
505          && session->supports_deadprop_count == svn_tristate_unknown
506          && dirent_fields & SVN_DIRENT_HAS_PROPS)
507        {
508          /* We have to requery as the server didn't give us the right
509             information */
510          session->supports_deadprop_count = svn_tristate_false;
511
512          apr_hash_clear(gdb.dirents);
513
514          SVN_ERR(svn_ra_serf__create_propfind_handler(
515                                              &dirent_handler, session,
516                                              path, SVN_INVALID_REVNUM, "1",
517                                              get_dirent_props(dirent_fields,
518                                                               session,
519                                                               scratch_pool),
520                                              get_dir_dirents_cb, &gdb,
521                                              scratch_pool));
522
523          svn_ra_serf__request_create(dirent_handler);
524        }
525    }
526
527  if (props_handler)
528    {
529      err = svn_error_trace(
530              svn_ra_serf__context_run_wait(&props_handler->done,
531                                            session,
532                                            scratch_pool));
533    }
534
535  /* And dirent again for the case when we had to send the request again */
536  if (! err && dirent_handler)
537    {
538      err = svn_error_trace(
539              svn_ra_serf__context_run_wait(&dirent_handler->done,
540                                            session,
541                                            scratch_pool));
542    }
543
544  if (!err && gdb.supports_deadprop_count != svn_tristate_unknown)
545    session->supports_deadprop_count = gdb.supports_deadprop_count;
546
547  svn_pool_destroy(scratch_pool); /* Unregisters outstanding requests */
548
549  SVN_ERR(err);
550
551  if (!gdb.is_directory)
552    return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
553                            _("Can't get entries of non-directory"));
554
555  if (ret_props)
556    *ret_props = gdb.ret_props;
557
558  if (dirents)
559    *dirents = gdb.dirents;
560
561  return SVN_NO_ERROR;
562}
563