source: valtobtest/subversion-1.6.2/subversion/mod_dav_svn/util.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 15.4 KB
Line 
1/*
2 * util.c: some handy utility functions
3 *
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet.  All rights reserved.
6 *
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution.  The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
12 *
13 * This software consists of voluntary contributions made by many
14 * individuals.  For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
17 */
18
19#include <apr_xml.h>
20#include <apr_errno.h>
21#include <apr_uri.h>
22#include <apr_buckets.h>
23
24#include <mod_dav.h>
25
26#include "svn_error.h"
27#include "svn_fs.h"
28#include "svn_dav.h"
29#include "svn_base64.h"
30
31#include "dav_svn.h"
32
33
34dav_error *
35dav_svn__new_error_tag(apr_pool_t *pool,
36                       int status,
37                       int error_id,
38                       const char *desc,
39                       const char *namespace,
40                       const char *tagname)
41{
42  /* dav_new_error_tag will record errno but Subversion makes no attempt
43     to ensure that it is valid.  We reset it to avoid putting incorrect
44     information into the error log, at the expense of possibly removing
45     valid information. */
46  errno = 0;
47
48  return dav_new_error_tag(pool, status, error_id, desc, namespace, tagname);
49}
50
51
52/* Build up a chain of DAV errors that correspond to the underlying SVN
53   errors that caused this problem. */
54static dav_error *
55build_error_chain(apr_pool_t *pool, svn_error_t *err, int status)
56{
57  char *msg = err->message ? apr_pstrdup(pool, err->message) : NULL;
58
59  dav_error *derr = dav_svn__new_error_tag(pool, status, err->apr_err, msg,
60                                           SVN_DAV_ERROR_NAMESPACE,
61                                           SVN_DAV_ERROR_TAG);
62
63  if (err->child)
64    derr->prev = build_error_chain(pool, err->child, status);
65
66  return derr;
67}
68
69
70dav_error *
71dav_svn__convert_err(svn_error_t *serr,
72                     int status,
73                     const char *message,
74                     apr_pool_t *pool)
75{
76    dav_error *derr;
77
78    /* ### someday mod_dav_svn will send back 'rich' error tags, much
79       finer grained than plain old svn_error_t's.  But for now, all
80       svn_error_t's are marshalled to the client via the single
81       generic <svn:error/> tag nestled within a <D:error> block. */
82
83    /* Examine the Subverion error code, and select the most
84       appropriate HTTP status code.  If no more appropriate HTTP
85       status code maps to the Subversion error code, use the one
86       suggested status provided by the caller. */
87    switch (serr->apr_err)
88      {
89      case SVN_ERR_FS_NOT_FOUND:
90        status = HTTP_NOT_FOUND;
91        break;
92      case SVN_ERR_UNSUPPORTED_FEATURE:
93        status = HTTP_NOT_IMPLEMENTED;
94        break;
95      case SVN_ERR_FS_PATH_ALREADY_LOCKED:
96        status = HTTP_LOCKED;
97        break;
98        /* add other mappings here */
99      }
100
101    derr = build_error_chain(pool, serr, status);
102    if (message != NULL
103        && serr->apr_err != SVN_ERR_REPOS_HOOK_FAILURE)
104      /* Don't hide hook failures; we might hide the error text */
105      derr = dav_push_error(pool, status, serr->apr_err, message, derr);
106
107    /* Now, destroy the Subversion error. */
108    svn_error_clear(serr);
109
110    return derr;
111}
112
113
114/* Set *REVISION to the youngest revision in which an interesting
115   history item (a modification, or a copy) occurred for PATH under
116   ROOT.  Use POOL for scratchwork. */
117static svn_error_t *
118get_last_history_rev(svn_revnum_t *revision,
119                     svn_fs_root_t *root,
120                     const char *path,
121                     apr_pool_t *pool)
122{
123  svn_fs_history_t *history;
124  const char *ignored;
125
126  /* Get an initial HISTORY baton. */
127  SVN_ERR(svn_fs_node_history(&history, root, path, pool));
128
129  /* Now get the first *real* point of interesting history. */
130  SVN_ERR(svn_fs_history_prev(&history, history, FALSE, pool));
131
132  /* Fetch the location information for this history step. */
133  return svn_fs_history_location(&ignored, revision, history, pool);
134}
135
136
137svn_revnum_t
138dav_svn__get_safe_cr(svn_fs_root_t *root, const char *path, apr_pool_t *pool)
139{
140  svn_revnum_t revision = svn_fs_revision_root_revision(root);
141  svn_revnum_t history_rev;
142  svn_fs_root_t *other_root;
143  svn_fs_t *fs = svn_fs_root_fs(root);
144  const svn_fs_id_t *id, *other_id;
145  svn_error_t *err;
146
147  if ((err = svn_fs_node_id(&id, root, path, pool)))
148    {
149      svn_error_clear(err);
150      return revision;   /* couldn't get id of root/path */
151    }
152
153  if ((err = get_last_history_rev(&history_rev, root, path, pool)))
154    {
155      svn_error_clear(err);
156      return revision;   /* couldn't find last history rev */
157    }
158
159  if ((err = svn_fs_revision_root(&other_root, fs, history_rev, pool)))
160    {
161      svn_error_clear(err);
162      return revision;   /* couldn't open the history rev */
163    }
164
165  if ((err = svn_fs_node_id(&other_id, other_root, path, pool)))
166    {
167      svn_error_clear(err);
168      return revision;   /* couldn't get id of other_root/path */
169    }
170
171  if (svn_fs_compare_ids(id, other_id) == 0)
172    return history_rev;  /* the history rev is safe!  the same node
173                            exists at the same path in both revisions. */
174
175  /* default */
176  return revision;
177}
178
179
180const char *
181dav_svn__build_uri(const dav_svn_repos *repos,
182                   enum dav_svn__build_what what,
183                   svn_revnum_t revision,
184                   const char *path,
185                   int add_href,
186                   apr_pool_t *pool)
187{
188  const char *root_path = repos->root_path;
189  const char *special_uri = repos->special_uri;
190  const char *path_uri = path ? svn_path_uri_encode(path, pool) : NULL;
191  const char *href1 = add_href ? "<D:href>" : "";
192  const char *href2 = add_href ? "</D:href>" : "";
193
194  /* The first character of root_path is guaranteed to be "/".  If
195     there's no component beyond that, then just use "", so that
196     appending another "/" later does not result in "//". */
197  if (root_path[1] == '\0')
198    root_path = "";
199
200  switch (what)
201    {
202    case DAV_SVN__BUILD_URI_ACT_COLLECTION:
203      return apr_psprintf(pool, "%s%s/%s/act/%s",
204                          href1, root_path, special_uri, href2);
205
206    case DAV_SVN__BUILD_URI_BASELINE:
207      return apr_psprintf(pool, "%s%s/%s/bln/%ld%s",
208                          href1, root_path, special_uri, revision, href2);
209
210    case DAV_SVN__BUILD_URI_BC:
211      return apr_psprintf(pool, "%s%s/%s/bc/%ld/%s",
212                          href1, root_path, special_uri, revision, href2);
213
214    case DAV_SVN__BUILD_URI_PUBLIC:
215      return apr_psprintf(pool, "%s%s%s%s",
216                          href1, root_path, path_uri, href2);
217
218    case DAV_SVN__BUILD_URI_VERSION:
219      return apr_psprintf(pool, "%s%s/%s/ver/%ld%s%s",
220                          href1, root_path, special_uri,
221                          revision, path_uri, href2);
222
223    case DAV_SVN__BUILD_URI_VCC:
224      return apr_psprintf(pool, "%s%s/%s/vcc/" DAV_SVN__DEFAULT_VCC_NAME "%s",
225                          href1, root_path, special_uri, href2);
226
227    default:
228      /* programmer error somewhere */
229      SVN_ERR_MALFUNCTION_NO_RETURN();
230    }
231
232  /* NOTREACHED */
233}
234
235
236svn_error_t *
237dav_svn__simple_parse_uri(dav_svn__uri_info *info,
238                          const dav_resource *relative,
239                          const char *uri,
240                          apr_pool_t *pool)
241{
242  apr_uri_t comp;
243  const char *path;
244  apr_size_t len1;
245  apr_size_t len2;
246  const char *slash;
247  const char *created_rev_str;
248
249  /* parse the input URI, in case it is more than just a path */
250  if (apr_uri_parse(pool, uri, &comp) != APR_SUCCESS)
251    goto malformed_uri;
252
253  /* ### ignore all URI parts but the path (for now) */
254
255  /* clean up the URI */
256  if (comp.path == NULL)
257    path = "/";
258  else
259    {
260      ap_getparents(comp.path);
261      ap_no2slash(comp.path);
262      path = comp.path;
263    }
264
265  /*
266   * Does the URI path specify the same repository? It does not if one of:
267   *
268   * 1) input is shorter than the path to our repository
269   * 2) input is longer, but there is no separator
270   *    [ http://host/repos vs http://host/repository ]
271   * 3) the two paths do not match
272   */
273  len1 = strlen(path);
274  len2 = strlen(relative->info->repos->root_path);
275  if (len2 == 1 && relative->info->repos->root_path[0] == '/')
276    len2 = 0;
277
278  if (len1 < len2
279      || (len1 > len2 && path[len2] != '/')
280      || memcmp(path, relative->info->repos->root_path, len2) != 0)
281    {
282      return svn_error_create(SVN_ERR_APMOD_MALFORMED_URI, NULL,
283                              "Unusable URI: it does not refer to this "
284                              "repository");
285    }
286
287  /* prep the return value */
288  memset(info, 0, sizeof(*info));
289  info->rev = SVN_INVALID_REVNUM;
290
291  path += len2; /* now points to "/" or "\0" */
292  len1 -= len2;
293
294  if (len1 <= 1)
295    {
296      info->repos_path = "/";
297      return NULL;
298    }
299
300  /* skip over the leading "/" */
301  ++path;
302  --len1;
303
304  /* is this a special URI? */
305  len2 = strlen(relative->info->repos->special_uri);
306  if (len1 < len2
307      || (len1 > len2 && path[len2] != '/')
308      || memcmp(path, relative->info->repos->special_uri, len2) != 0)
309    {
310      /* this is an ordinary "public" URI, so back up to include the
311         leading '/' and just return... no need to parse further. */
312      info->repos_path = svn_path_uri_decode(path - 1, pool);
313      return NULL;
314    }
315
316  path += len2; /* now points to "/" or "\0" just past the special URI */
317  len1 -= len2;
318
319  /* ### we don't handle the root of the special area yet */
320  if (len1 <= 1)
321    goto unhandled_form;
322
323  /* Find the next component, and ensure something is there. */
324  slash = ap_strchr_c(path + 1, '/');
325  if (slash == NULL || slash[1] == '\0')
326    goto unhandled_form;
327  len2 = slash - path;
328
329  /* Figure out what we have here */
330  if (len2 == 4 && memcmp(path, "/act/", 5) == 0)
331    {
332      /* an activity */
333      info->activity_id = path + 5;
334    }
335  else if (len2 == 4 && memcmp(path, "/ver/", 5) == 0)
336    {
337      /* a version resource */
338      path += 5;
339      len1 -= 5;
340      slash = ap_strchr_c(path, '/');
341      if (slash == NULL)
342        {
343          created_rev_str = apr_pstrndup(pool, path, len1);
344          info->rev = SVN_STR_TO_REV(created_rev_str);
345          info->repos_path = "/";
346        }
347      else
348        {
349          created_rev_str = apr_pstrndup(pool, path, slash - path);
350          info->rev = SVN_STR_TO_REV(created_rev_str);
351          info->repos_path = svn_path_uri_decode(slash, pool);
352        }
353      if (info->rev == SVN_INVALID_REVNUM)
354        goto malformed_uri;
355    }
356  else
357    goto unhandled_form;
358
359  return NULL;
360
361 malformed_uri:
362    return svn_error_create(SVN_ERR_APMOD_MALFORMED_URI, NULL,
363                            "The specified URI could not be parsed");
364
365 unhandled_form:
366  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
367                          "Unsupported URI form");
368}
369
370
371/* ### move this into apr_xml */
372int
373dav_svn__find_ns(apr_array_header_t *namespaces, const char *uri)
374{
375  int i;
376
377  for (i = 0; i < namespaces->nelts; ++i)
378    if (strcmp(APR_XML_GET_URI_ITEM(namespaces, i), uri) == 0)
379      return i;
380  return -1;
381}
382
383
384svn_error_t *
385dav_svn__send_xml(apr_bucket_brigade *bb,
386                  ap_filter_t *output,
387                  const char *fmt,
388                  ...)
389{
390  apr_status_t apr_err;
391  va_list ap;
392
393  va_start(ap, fmt);
394  apr_err = apr_brigade_vprintf(bb, ap_filter_flush, output, fmt, ap);
395  va_end(ap);
396  if (apr_err)
397    return svn_error_create(apr_err, 0, NULL);
398  /* ### check for an aborted connection, since the brigade functions
399     don't appear to be return useful errors when the connection is
400     dropped. */
401  if (output->c->aborted)
402    return svn_error_create(SVN_ERR_APMOD_CONNECTION_ABORTED, 0, NULL);
403  return SVN_NO_ERROR;
404}
405
406
407dav_error *
408dav_svn__test_canonical(const char *path, apr_pool_t *pool)
409{
410  if (svn_path_is_canonical(path, pool))
411    return NULL;
412
413  /* Otherwise, generate a generic HTTP_BAD_REQUEST error. */
414  return dav_svn__new_error_tag
415    (pool, HTTP_BAD_REQUEST, 0,
416     apr_psprintf(pool,
417                  "Path '%s' is not canonicalized; "
418                  "there is a problem with the client.", path),
419     SVN_DAV_ERROR_NAMESPACE, SVN_DAV_ERROR_TAG);
420}
421
422
423dav_error *
424dav_svn__sanitize_error(svn_error_t *serr,
425                        const char *new_msg,
426                        int http_status,
427                        request_rec *r)
428{
429  svn_error_t *safe_err = serr;
430  if (new_msg != NULL)
431    {
432      /* Sanitization is necessary.  Create a new, safe error and
433           log the original error. */
434        safe_err = svn_error_create(serr->apr_err, NULL, new_msg);
435        ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r,
436                      "%s", serr->message);
437        svn_error_clear(serr);
438      }
439    return dav_svn__convert_err(safe_err, http_status,
440                                apr_psprintf(r->pool, "%s", safe_err->message),
441                                r->pool);
442}
443
444
445struct brigade_write_baton
446{
447  apr_bucket_brigade *bb;
448  ap_filter_t *output;
449};
450
451
452/* This implements 'svn_write_fn_t'. */
453static svn_error_t *
454brigade_write_fn(void *baton, const char *data, apr_size_t *len)
455{
456  struct brigade_write_baton *wb = baton;
457  apr_status_t apr_err;
458
459  apr_err = apr_brigade_write(wb->bb, ap_filter_flush, wb->output, data, *len);
460
461  if (apr_err != APR_SUCCESS)
462    return svn_error_wrap_apr(apr_err, "Error writing base64 data");
463
464  return SVN_NO_ERROR;
465}
466
467
468svn_stream_t *
469dav_svn__make_base64_output_stream(apr_bucket_brigade *bb,
470                                   ap_filter_t *output,
471                                   apr_pool_t *pool)
472{
473  struct brigade_write_baton *wb = apr_palloc(pool, sizeof(*wb));
474  svn_stream_t *stream = svn_stream_create(wb, pool);
475
476  wb->bb = bb;
477  wb->output = output;
478  svn_stream_set_write(stream, brigade_write_fn);
479
480  return svn_base64_encode(stream, pool);
481}
482
483void
484dav_svn__operational_log(struct dav_resource_private *info, const char *line)
485{
486  apr_table_set(info->r->subprocess_env, "SVN-ACTION", line);
487  apr_table_set(info->r->subprocess_env, "SVN-REPOS",
488                svn_path_uri_encode(info->repos->fs_path, info->r->pool));
489  apr_table_set(info->r->subprocess_env, "SVN-REPOS-NAME",
490                svn_path_uri_encode(info->repos->repo_basename, info->r->pool));
491}
492
493
494dav_error *
495dav_svn__final_flush_or_error(request_rec *r,
496                              apr_bucket_brigade *bb,
497                              ap_filter_t *output,
498                              dav_error *preferred_err,
499                              apr_pool_t *pool)
500{
501  dav_error *derr = preferred_err;
502  svn_boolean_t do_flush;
503
504  do_flush = r->sent_bodyct > 0;
505  if (! do_flush)
506    {
507      /* Ask about the length of the bucket brigade, ignoring errors. */
508      apr_off_t len;
509      (void)apr_brigade_length(bb, FALSE, &len);
510      do_flush = (len != 0);
511    }
512
513  /* If there's something in the bucket brigade to flush, or we've
514     already started sending data down the wire, flush what we've
515     got.  We only keep any error retrieved from the flush if weren't
516     provided a more-important DERR, though. */
517  if (do_flush)
518    {
519      apr_status_t apr_err = ap_fflush(output, bb);
520      if (apr_err && (! derr))
521        derr = dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR, 0,
522                             "Error flushing brigade.");
523    }
524  return derr;
525}
Note: See TracBrowser for help on using the repository browser.