]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/stat.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / 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               apr_int64_t deadprop_count;
197               SVN_ERR(svn_cstring_atoi64(&deadprop_count, val->data));
198               fdb->entry->has_props = deadprop_count > 0;
199               if (fdb->supports_deadprop_count)
200                 *fdb->supports_deadprop_count = svn_tristate_true;
201             }
202           else if (fdb->supports_deadprop_count)
203             *fdb->supports_deadprop_count = svn_tristate_false;
204         }
205     }
206
207   return SVN_NO_ERROR;
208 }
209
210 static const svn_ra_serf__dav_props_t *
211 get_dirent_props(apr_uint32_t dirent_fields,
212                  svn_ra_serf__session_t *session,
213                  apr_pool_t *pool)
214 {
215   svn_ra_serf__dav_props_t *prop;
216   apr_array_header_t *props = apr_array_make
217     (pool, 7, sizeof(svn_ra_serf__dav_props_t));
218
219   if (session->supports_deadprop_count != svn_tristate_false
220       || ! (dirent_fields & SVN_DIRENT_HAS_PROPS))
221     {
222       if (dirent_fields & SVN_DIRENT_KIND)
223         {
224           prop = apr_array_push(props);
225           prop->xmlns = "DAV:";
226           prop->name = "resourcetype";
227         }
228
229       if (dirent_fields & SVN_DIRENT_SIZE)
230         {
231           prop = apr_array_push(props);
232           prop->xmlns = "DAV:";
233           prop->name = "getcontentlength";
234         }
235
236       if (dirent_fields & SVN_DIRENT_HAS_PROPS)
237         {
238           prop = apr_array_push(props);
239           prop->xmlns = SVN_DAV_PROP_NS_DAV;
240           prop->name = "deadprop-count";
241         }
242
243       if (dirent_fields & SVN_DIRENT_CREATED_REV)
244         {
245           svn_ra_serf__dav_props_t *p = apr_array_push(props);
246           p->xmlns = "DAV:";
247           p->name = SVN_DAV__VERSION_NAME;
248         }
249
250       if (dirent_fields & SVN_DIRENT_TIME)
251         {
252           prop = apr_array_push(props);
253           prop->xmlns = "DAV:";
254           prop->name = SVN_DAV__CREATIONDATE;
255         }
256
257       if (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
258         {
259           prop = apr_array_push(props);
260           prop->xmlns = "DAV:";
261           prop->name = "creator-displayname";
262         }
263     }
264   else
265     {
266       /* We found an old subversion server that can't handle
267          the deadprop-count property in the way we expect.
268
269          The neon behavior is to retrieve all properties in this case */
270       prop = apr_array_push(props);
271       prop->xmlns = "DAV:";
272       prop->name = "allprop";
273     }
274
275   prop = apr_array_push(props);
276   prop->xmlns = NULL;
277   prop->name = NULL;
278
279   return (svn_ra_serf__dav_props_t *) props->elts;
280 }
281
282 /* Implements svn_ra__vtable_t.stat(). */
283 svn_error_t *
284 svn_ra_serf__stat(svn_ra_session_t *ra_session,
285                   const char *relpath,
286                   svn_revnum_t revision,
287                   svn_dirent_t **dirent,
288                   apr_pool_t *pool)
289 {
290   svn_ra_serf__session_t *session = ra_session->priv;
291   svn_error_t *err;
292   struct fill_dirent_baton_t fdb;
293   svn_tristate_t deadprop_count = svn_tristate_unknown;
294   svn_ra_serf__handler_t *handler;
295   const char *url;
296
297   url = session->session_url.path;
298
299   /* If we have a relative path, append it. */
300   if (relpath)
301     url = svn_path_url_add_component2(url, relpath, pool);
302
303   /* If we were given a specific revision, get a URL that refers to that
304      specific revision (rather than floating with HEAD).  */
305   if (SVN_IS_VALID_REVNUM(revision))
306     {
307       SVN_ERR(svn_ra_serf__get_stable_url(&url, NULL /* latest_revnum */,
308                                           session,
309                                           url, revision,
310                                           pool, pool));
311     }
312
313   fdb.entry = svn_dirent_create(pool);
314   fdb.supports_deadprop_count = &deadprop_count;
315   fdb.result_pool = pool;
316
317   SVN_ERR(svn_ra_serf__create_propfind_handler(&handler, session, url,
318                                                SVN_INVALID_REVNUM, "0",
319                                                get_dirent_props(SVN_DIRENT_ALL,
320                                                                 session,
321                                                                 pool),
322                                                fill_dirent_propfunc, &fdb, pool));
323
324   err = svn_ra_serf__context_run_one(handler, pool);
325
326   if (err)
327     {
328       if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
329         {
330           svn_error_clear(err);
331           *dirent = NULL;
332           return SVN_NO_ERROR;
333         }
334       else
335         return svn_error_trace(err);
336     }
337
338   if (deadprop_count == svn_tristate_false
339       && session->supports_deadprop_count == svn_tristate_unknown
340       && !fdb.entry->has_props)
341     {
342       /* We have to requery as the server didn't give us the right
343          information */
344       session->supports_deadprop_count = svn_tristate_false;
345
346       /* Run the same handler again */
347       SVN_ERR(svn_ra_serf__context_run_one(handler, pool));
348     }
349
350   if (deadprop_count != svn_tristate_unknown)
351     session->supports_deadprop_count = deadprop_count;
352
353   *dirent = fdb.entry;
354
355   return SVN_NO_ERROR;
356 }
357
358 /* Baton for get_dir_dirents_cb and get_dir_props_cb */
359 struct get_dir_baton_t
360 {
361   apr_pool_t *result_pool;
362   apr_hash_t *dirents;
363   apr_hash_t *ret_props;
364   svn_boolean_t is_directory;
365   svn_tristate_t supports_deadprop_count;
366   const char *path;
367 };
368
369 /* Implements svn_ra_serf__prop_func_t */
370 static svn_error_t *
371 get_dir_dirents_cb(void *baton,
372                    const char *path,
373                    const char *ns,
374                    const char *name,
375                    const svn_string_t *value,
376                    apr_pool_t *scratch_pool)
377 {
378   struct get_dir_baton_t *db = baton;
379   const char *relpath;
380
381   relpath = svn_fspath__skip_ancestor(db->path, path);
382
383   if (relpath && relpath[0] != '\0')
384     {
385       struct fill_dirent_baton_t fdb;
386
387       relpath = svn_path_uri_decode(relpath, scratch_pool);
388       fdb.entry = svn_hash_gets(db->dirents, relpath);
389
390       if (!fdb.entry)
391         {
392           fdb.entry = svn_dirent_create(db->result_pool);
393           svn_hash_sets(db->dirents,
394                         apr_pstrdup(db->result_pool, relpath),
395                         fdb.entry);
396         }
397
398       fdb.result_pool = db->result_pool;
399       fdb.supports_deadprop_count = &db->supports_deadprop_count;
400       SVN_ERR(fill_dirent_propfunc(&fdb, path, ns, name, value, scratch_pool));
401     }
402   else if (relpath && !db->is_directory)
403     {
404       if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
405         {
406           if (strcmp(value->data, "collection") != 0)
407             {
408               /* Tell a lie to exit early */
409               return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
410                                       _("Can't get properties of non-directory"));
411             }
412           else
413             db->is_directory = TRUE;
414         }
415     }
416
417   return SVN_NO_ERROR;
418 }
419
420 /* Implements svn_ra_serf__prop_func */
421 static svn_error_t *
422 get_dir_props_cb(void *baton,
423                  const char *path,
424                  const char *ns,
425                  const char *name,
426                  const svn_string_t *value,
427                  apr_pool_t *scratch_pool)
428 {
429   struct get_dir_baton_t *db = baton;
430   const char *propname;
431
432   propname = svn_ra_serf__svnname_from_wirename(ns, name, db->result_pool);
433   if (propname)
434     {
435       svn_hash_sets(db->ret_props, propname,
436                     svn_string_dup(value, db->result_pool));
437       return SVN_NO_ERROR;
438     }
439
440   if (!db->is_directory)
441     {
442       if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0)
443         {
444           if (strcmp(value->data, "collection") != 0)
445             {
446               /* Tell a lie to exit early */
447               return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
448                                       _("Can't get properties of non-directory"));
449             }
450           else
451             db->is_directory = TRUE;
452         }
453     }
454
455   return SVN_NO_ERROR;
456 }
457
458 /* Implements svn_ra__vtable_t.get_dir(). */
459 svn_error_t *
460 svn_ra_serf__get_dir(svn_ra_session_t *ra_session,
461                      apr_hash_t **dirents,
462                      svn_revnum_t *fetched_rev,
463                      apr_hash_t **ret_props,
464                      const char *rel_path,
465                      svn_revnum_t revision,
466                      apr_uint32_t dirent_fields,
467                      apr_pool_t *result_pool)
468 {
469   svn_ra_serf__session_t *session = ra_session->priv;
470   apr_pool_t *scratch_pool = svn_pool_create(result_pool);
471   svn_ra_serf__handler_t *dirent_handler = NULL;
472   svn_ra_serf__handler_t *props_handler = NULL;
473   const char *path;
474   struct get_dir_baton_t gdb;
475   svn_error_t *err = SVN_NO_ERROR;
476
477   gdb.result_pool = result_pool;
478   gdb.is_directory = FALSE;
479   gdb.supports_deadprop_count = svn_tristate_unknown;
480
481   path = session->session_url.path;
482
483   /* If we have a relative path, URI encode and append it. */
484   if (rel_path)
485     {
486       path = svn_path_url_add_component2(path, rel_path, scratch_pool);
487     }
488
489   /* If the user specified a peg revision other than HEAD, we have to fetch
490      the baseline collection url for that revision. If not, we can use the
491      public url. */
492   if (SVN_IS_VALID_REVNUM(revision) || fetched_rev)
493     {
494       SVN_ERR(svn_ra_serf__get_stable_url(&path, fetched_rev,
495                                           session,
496                                           path, revision,
497                                           scratch_pool, scratch_pool));
498       revision = SVN_INVALID_REVNUM;
499     }
500   /* REVISION is always SVN_INVALID_REVNUM  */
501   SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision));
502
503   gdb.path = path;
504
505   /* If we're asked for children, fetch them now. */
506   if (dirents)
507     {
508       /* Always request node kind to check that path is really a
509        * directory. */
510       if (!ret_props)
511         dirent_fields |= SVN_DIRENT_KIND;
512
513       gdb.dirents = apr_hash_make(result_pool);
514
515       SVN_ERR(svn_ra_serf__create_propfind_handler(
516                                           &dirent_handler, session,
517                                           path, SVN_INVALID_REVNUM, "1",
518                                           get_dirent_props(dirent_fields,
519                                                            session,
520                                                            scratch_pool),
521                                           get_dir_dirents_cb, &gdb,
522                                           scratch_pool));
523
524       svn_ra_serf__request_create(dirent_handler);
525     }
526   else
527     gdb.dirents = NULL;
528
529   if (ret_props)
530     {
531       gdb.ret_props = apr_hash_make(result_pool);
532       SVN_ERR(svn_ra_serf__create_propfind_handler(
533                                           &props_handler, session,
534                                           path, SVN_INVALID_REVNUM, "0",
535                                           all_props,
536                                           get_dir_props_cb, &gdb,
537                                           scratch_pool));
538
539       svn_ra_serf__request_create(props_handler);
540     }
541   else
542     gdb.ret_props = NULL;
543
544   if (dirent_handler)
545     {
546       err = svn_error_trace(
547               svn_ra_serf__context_run_wait(&dirent_handler->done,
548                                             session,
549                                             scratch_pool));
550
551       if (err)
552         {
553           svn_pool_clear(scratch_pool); /* Unregisters outstanding requests */
554           return err;
555         }
556
557       if (gdb.supports_deadprop_count == svn_tristate_false
558           && session->supports_deadprop_count == svn_tristate_unknown
559           && dirent_fields & SVN_DIRENT_HAS_PROPS)
560         {
561           /* We have to requery as the server didn't give us the right
562              information */
563           session->supports_deadprop_count = svn_tristate_false;
564
565           apr_hash_clear(gdb.dirents);
566
567           SVN_ERR(svn_ra_serf__create_propfind_handler(
568                                               &dirent_handler, session,
569                                               path, SVN_INVALID_REVNUM, "1",
570                                               get_dirent_props(dirent_fields,
571                                                                session,
572                                                                scratch_pool),
573                                               get_dir_dirents_cb, &gdb,
574                                               scratch_pool));
575
576           svn_ra_serf__request_create(dirent_handler);
577         }
578     }
579
580   if (props_handler)
581     {
582       err = svn_error_trace(
583               svn_ra_serf__context_run_wait(&props_handler->done,
584                                             session,
585                                             scratch_pool));
586     }
587
588   /* And dirent again for the case when we had to send the request again */
589   if (! err && dirent_handler)
590     {
591       err = svn_error_trace(
592               svn_ra_serf__context_run_wait(&dirent_handler->done,
593                                             session,
594                                             scratch_pool));
595     }
596
597   if (!err && gdb.supports_deadprop_count != svn_tristate_unknown)
598     session->supports_deadprop_count = gdb.supports_deadprop_count;
599
600   svn_pool_destroy(scratch_pool); /* Unregisters outstanding requests */
601
602   SVN_ERR(err);
603
604   if (!gdb.is_directory)
605     return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, NULL,
606                             _("Can't get entries of non-directory"));
607
608   if (ret_props)
609     *ret_props = gdb.ret_props;
610
611   if (dirents)
612     *dirents = gdb.dirents;
613
614   return SVN_NO_ERROR;
615 }