]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - subversion/libsvn_ra_serf/stat.c
Import Subversion-1.10.0
[FreeBSD/FreeBSD.git] / subversion / libsvn_ra_serf / stat.c
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 \f
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 \f
50
51 /* Implements svn_ra__vtable_t.check_path(). */
52 svn_error_t *
53 svn_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() */
121 struct 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 */
133 static svn_error_t *
134 fill_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
213 static const svn_ra_serf__dav_props_t *
214 get_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(). */
230 svn_error_t *
231 svn_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 */
306 struct 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 */
317 static svn_error_t *
318 get_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 */
368 static svn_error_t *
369 get_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(). */
406 svn_error_t *
407 svn_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 }