Пример #1
0
/*
** Find a particular lock on a resource (specified by its locktoken).
**
** *lock will be set to NULL if the lock is not found.
**
** Note that the provider can optimize the unmarshalling -- only one
** lock (or none) must be constructed and returned.
**
** If partial_ok is true (non-zero), then an indirect lock can be
** partially filled in. Otherwise, another lookup is done and the
** lock structure will be filled out as a DAV_LOCKREC_INDIRECT.
*/
static dav_error *
find_lock(dav_lockdb *lockdb,
          const dav_resource *resource,
          const dav_locktoken *locktoken,
          int partial_ok,
          dav_lock **lock)
{
  dav_lockdb_private *info = lockdb->info;
  svn_error_t *serr;
  svn_lock_t *slock;
  dav_lock *dlock = NULL;

  /* If the resource's fs path is unreadable, we don't want to say
     anything about locks attached to it.*/
  if (! dav_svn__allow_read_resource(resource, SVN_INVALID_REVNUM,
                                     resource->pool))
    return dav_svn__new_error(resource->pool, HTTP_FORBIDDEN,
                              DAV_ERR_LOCK_SAVE_LOCK,
                              "Path is not accessible.");

  serr = svn_fs_get_lock(&slock,
                         resource->info->repos->fs,
                         resource->info->repos_path,
                         resource->pool);
  if (serr)
    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                "Failed to look up lock by path.",
                                resource->pool);

  if (slock != NULL)
    {
      /* Sanity check. */
      if (strcmp(locktoken->uuid_str, slock->token) != 0)
        return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST,
                                  DAV_ERR_LOCK_SAVE_LOCK,
                                  "Incoming token doesn't match existing "
                                  "lock.");

      svn_lock_to_dav_lock(&dlock, slock, FALSE,
                           resource->exists, resource->pool);

      /* Let svn clients know the creationdate of the slock. */
      apr_table_setn(info->r->headers_out, SVN_DAV_CREATIONDATE_HEADER,
                     svn_time_to_cstring(slock->creation_date,
                                         resource->pool));

      /* Let svn clients know the 'owner' of the slock. */
      apr_table_setn(info->r->headers_out, SVN_DAV_LOCK_OWNER_HEADER,
                     slock->owner);
    }

  *lock = dlock;
  return 0;
}
Пример #2
0
/*
** Quick test to see if the resource has *any* locks on it.
**
** This is typically used to determine if a non-existent resource
** has a lock and is (therefore) a locknull resource.
**
** WARNING: this function may return TRUE even when timed-out locks
**          exist (i.e. it may not perform timeout checks).
*/
static dav_error *
has_locks(dav_lockdb *lockdb, const dav_resource *resource, int *locks_present)
{
  dav_lockdb_private *info = lockdb->info;
  svn_error_t *serr;
  svn_lock_t *slock;

  /* Sanity check:  if the resource has no associated path in the fs,
     then there's nothing to do.  */
  if (! resource->info->repos_path)
    {
      *locks_present = 0;
      return 0;
    }

  /* The Big Lie: if the client ran 'svn lock', then we have
     to pretend that there's no existing lock.  Otherwise mod_dav will
     throw '403 Locked' without even attempting to create a new
     lock.  For the --force case, this is required and for the non-force case,
     we allow the filesystem to produce a better error for svn clients.
  */
  if (info->r->method_number == M_LOCK
      && resource->info->repos->is_svn_client)
    {
      *locks_present = 0;
      return 0;
    }

  /* If the resource's fs path is unreadable, we don't want to say
     anything about locks attached to it.*/
  if (! dav_svn__allow_read_resource(resource, SVN_INVALID_REVNUM,
                                     resource->pool))
    return dav_svn__new_error(resource->pool, HTTP_FORBIDDEN,
                              DAV_ERR_LOCK_SAVE_LOCK,
                              "Path is not accessible.");

  serr = svn_fs_get_lock(&slock,
                         resource->info->repos->fs,
                         resource->info->repos_path,
                         resource->pool);
  if (serr)
    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                "Failed to check path for a lock.",
                                resource->pool);

  *locks_present = slock ? 1 : 0;
  return 0;
}
/* Set *PROPVAL to the value for the revision property PROPNAME on
   COMMITTED_REV, in the repository identified by RESOURCE, if
   RESOURCE's path is readable.  If it is not readable, set *PROPVAL
   to NULL and return SVN_NO_ERROR.  Use POOL for temporary
   allocations and the allocation of *PROPVAL.

   Note that this function does not check the readability of the
   revision property, but the readability of a path.  The true
   readability of a revision property is determined by investigating
   the readability of all changed paths in the revision.  For certain
   revision properties (e.g. svn:author and svn:date) to be readable,
   it is enough if at least one changed path is readable.  When we
   already have a changed path, we can skip the check for the other
   changed paths in the revision and save a lot of work.  This means
   that we will make a mistake when our path is unreadable and another
   changed path is readable, but we will at least only hide too much
   and not leak any protected properties.

   WARNING: This method of only checking the readability of a path is
   only valid to get revision properties for which it is enough if at
   least one changed path is readable.  Using this function to get
   revision properties for which all changed paths must be readable
   might leak protected information because we will only test the
   readability of a single changed path.
*/
static svn_error_t *
get_path_revprop(svn_string_t **propval,
                 const dav_resource *resource,
                 svn_revnum_t committed_rev,
                 const char *propname,
                 apr_pool_t *pool)
{
  *propval = NULL;

  if (! dav_svn__allow_read_resource(resource, committed_rev, pool))
    return SVN_NO_ERROR;

  /* Get the property of the created revision. The authz is already
     performed, so we don't need to do it here too. */
  return svn_repos_fs_revision_prop(propval,
                                    resource->info->repos->repos,
                                    committed_rev,
                                    propname,
                                    NULL, NULL, pool);
}
Пример #4
0
/*
** Refresh all locks, found on the specified resource, which has a
** locktoken in the provided list.
**
** If the lock is indirect, then the direct lock is referenced and
** refreshed.
**
** Each lock that is updated is returned in the <locks> argument.
** Note that the locks will be fully resolved.
*/
static dav_error *
refresh_locks(dav_lockdb *lockdb,
              const dav_resource *resource,
              const dav_locktoken_list *ltl,
              time_t new_time,
              dav_lock **locks)
{
  /* We're not looping over a list of locks, since we only support one
     lock per resource. */
  dav_locktoken *token = ltl->locktoken;
  svn_error_t *serr;
  svn_lock_t *slock;
  dav_lock *dlock;

  /* If the resource's fs path is unreadable, we don't want to say
     anything about locks attached to it.*/
  if (! dav_svn__allow_read_resource(resource, SVN_INVALID_REVNUM,
                                     resource->pool))
    return dav_svn__new_error(resource->pool, HTTP_FORBIDDEN,
                              DAV_ERR_LOCK_SAVE_LOCK,
                              "Path is not accessible.");

  /* Convert the path into an svn_lock_t. */
  serr = svn_fs_get_lock(&slock,
                         resource->info->repos->fs,
                         resource->info->repos_path,
                         resource->pool);
  if (serr)
    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                "Token doesn't point to a lock.",
                                resource->pool);

  /* Sanity check: does the incoming token actually represent the
     current lock on the incoming resource? */
  if ((! slock)
      || (strcmp(token->uuid_str, slock->token) != 0))
    return dav_svn__new_error(resource->pool, HTTP_UNAUTHORIZED,
                              DAV_ERR_LOCK_SAVE_LOCK,
                              "Lock refresh request doesn't match existing "
                              "lock.");

  /* Now use the tweaked svn_lock_t to 'refresh' the existing lock. */
  serr = svn_repos_fs_lock(&slock,
                           resource->info->repos->repos,
                           slock->path,
                           slock->token,
                           slock->comment,
                           slock->is_dav_comment,
                           (new_time == DAV_TIMEOUT_INFINITE)
                             ? 0 : (apr_time_t)new_time * APR_USEC_PER_SEC,
                           SVN_INVALID_REVNUM,
                           TRUE, /* forcibly steal existing lock */
                           resource->pool);

  if (serr && serr->apr_err == SVN_ERR_FS_NO_USER)
    {
      svn_error_clear(serr);
      return dav_svn__new_error(resource->pool, HTTP_UNAUTHORIZED,
                                DAV_ERR_LOCK_SAVE_LOCK,
                                "Anonymous lock refreshing is not allowed.");
    }
  else if (serr)
    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                "Failed to refresh existing lock.",
                                resource->pool);

  /* Convert the refreshed lock into a dav_lock and return it. */
  svn_lock_to_dav_lock(&dlock, slock, FALSE, resource->exists, resource->pool);
  *locks = dlock;

  return 0;
}
Пример #5
0
/*
** Remove any lock that has the specified locktoken.
**
** If locktoken == NULL, then ALL locks are removed.
*/
static dav_error *
remove_lock(dav_lockdb *lockdb,
            const dav_resource *resource,
            const dav_locktoken *locktoken)
{
  dav_lockdb_private *info = lockdb->info;
  svn_error_t *serr;
  svn_lock_t *slock;
  const char *token = NULL;

  /* Sanity check:  if the resource has no associated path in the fs,
     then there's nothing to do.  */
  if (! resource->info->repos_path)
    return 0;

  /* Another easy out: if an svn client sent a 'keep_locks' header
     (typically in a DELETE request, as part of 'svn commit
     --no-unlock'), then ignore dav_method_delete()'s attempt to
     unconditionally remove the lock.  */
  if (info->keep_locks)
    return 0;

  /* If the resource's fs path is unreadable, we don't allow a lock to
     be removed from it. */
  if (! dav_svn__allow_read_resource(resource, SVN_INVALID_REVNUM,
                                     resource->pool))
    return dav_svn__new_error(resource->pool, HTTP_FORBIDDEN,
                              DAV_ERR_LOCK_SAVE_LOCK,
                              "Path is not accessible.");

  if (locktoken == NULL)
    {
      /* Need to manually discover any lock on the resource. */
      serr = svn_fs_get_lock(&slock,
                             resource->info->repos->fs,
                             resource->info->repos_path,
                             resource->pool);
      if (serr)
        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                    "Failed to check path for a lock.",
                                    resource->pool);
      if (slock)
        token = slock->token;
    }
  else
    {
      token = locktoken->uuid_str;
    }

  if (token)
    {
      /* Notice that a generic DAV client is unable to forcibly
         'break' a lock, because info->lock_break will always be
         FALSE.  An svn client, however, can request a 'forced' break.*/
      serr = svn_repos_fs_unlock(resource->info->repos->repos,
                                 resource->info->repos_path,
                                 token,
                                 info->lock_break,
                                 resource->pool);

      if (serr && serr->apr_err == SVN_ERR_FS_NO_USER)
        {
          svn_error_clear(serr);
          return dav_svn__new_error(resource->pool, HTTP_UNAUTHORIZED,
                                    DAV_ERR_LOCK_SAVE_LOCK,
                                    "Anonymous lock removal is not allowed.");
        }
      else if (serr)
        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                    "Failed to remove a lock.",
                                    resource->pool);

      /* Log the unlocking as a 'high-level' action. */
      dav_svn__operational_log(resource->info,
                               svn_log__unlock_one_path(
                                   resource->info->repos_path,
                                   info->lock_break,
                                   resource->info->r->pool));
    }

  return 0;
}
Пример #6
0
/*
** Append the specified lock(s) to the set of locks on this resource.
**
** If "make_indirect" is true (non-zero), then the specified lock(s)
** should be converted to an indirect lock (if it is a direct lock)
** before appending. Note that the conversion to an indirect lock does
** not alter the passed-in lock -- the change is internal the
** append_locks function.
**
** Multiple locks are specified using the lock->next links.
*/
static dav_error *
append_locks(dav_lockdb *lockdb,
             const dav_resource *resource,
             int make_indirect,
             const dav_lock *lock)
{
  dav_lockdb_private *info = lockdb->info;
  svn_lock_t *slock;
  svn_error_t *serr;
  dav_error *derr;

  /* If the resource's fs path is unreadable, we don't allow a lock to
     be created on it. */
  if (! dav_svn__allow_read_resource(resource, SVN_INVALID_REVNUM,
                                     resource->pool))
    return dav_svn__new_error(resource->pool, HTTP_FORBIDDEN,
                              DAV_ERR_LOCK_SAVE_LOCK,
                              "Path is not accessible.");

  if (lock->next)
    return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST,
                              DAV_ERR_LOCK_SAVE_LOCK,
                              "Tried to attach multiple locks to a resource.");

  /* RFC2518bis (section 7.4) doesn't require us to support
     'lock-null' resources at all.  Instead, it asks that we treat
     'LOCK nonexistentURL' as a PUT (followed by a LOCK) of a 0-byte file.  */
  if (! resource->exists)
    {
      svn_revnum_t rev, new_rev;
      svn_fs_txn_t *txn;
      svn_fs_root_t *txn_root;
      const char *conflict_msg;
      dav_svn_repos *repos = resource->info->repos;
      apr_hash_t *revprop_table = apr_hash_make(resource->pool);
      apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR,
                   APR_HASH_KEY_STRING, svn_string_create(repos->username,
                                                          resource->pool));

      if (resource->info->repos->is_svn_client)
        return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED,
                                  DAV_ERR_LOCK_SAVE_LOCK,
                                  "Subversion clients may not lock "
                                  "nonexistent paths.");

      else if (! resource->info->repos->autoversioning)
        return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED,
                                  DAV_ERR_LOCK_SAVE_LOCK,
                                  "Attempted to lock non-existent path; "
                                  "turn on autoversioning first.");

      /* Commit a 0-byte file: */

      if ((serr = svn_fs_youngest_rev(&rev, repos->fs, resource->pool)))
        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                    "Could not determine youngest revision",
                                    resource->pool);

      if ((serr = svn_repos_fs_begin_txn_for_commit2(&txn, repos->repos, rev,
                                                     revprop_table,
                                                     resource->pool)))
        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                    "Could not begin a transaction",
                                    resource->pool);

      if ((serr = svn_fs_txn_root(&txn_root, txn, resource->pool)))
        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                    "Could not begin a transaction",
                                    resource->pool);

      if ((serr = svn_fs_make_file(txn_root, resource->info->repos_path,
                                   resource->pool)))
        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                    "Could not create empty file.",
                                    resource->pool);

      if ((serr = dav_svn__attach_auto_revprops(txn,
                                                resource->info->repos_path,
                                                resource->pool)))
        return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                    "Could not create empty file.",
                                    resource->pool);

      serr = svn_repos_fs_commit_txn(&conflict_msg, repos->repos,
                                     &new_rev, txn, resource->pool);
      if (SVN_IS_VALID_REVNUM(new_rev))
        {
          /* ### Log an error in post commit FS processing? */
          svn_error_clear(serr);
        }
      else
        {
          svn_error_clear(svn_fs_abort_txn(txn, resource->pool));
          if (serr)
            return dav_svn__convert_err(serr, HTTP_CONFLICT,
                                        apr_psprintf(resource->pool,
                                                     "Conflict when "
                                                     "committing '%s'.",
                                                     conflict_msg),
                                        resource->pool);
          else
            return dav_svn__new_error(resource->pool,
                                      HTTP_INTERNAL_SERVER_ERROR,
                                      0,
                                      "Commit failed but there was no error "
                                      "provided.");
        }
    }

  /* Convert the dav_lock into an svn_lock_t. */
  derr = dav_lock_to_svn_lock(&slock, lock, resource->info->repos_path,
                              info, resource->info->repos->is_svn_client,
                              resource->pool);
  if (derr)
    return derr;

  /* Now use the svn_lock_t to actually perform the lock. */
  serr = svn_repos_fs_lock(&slock,
                           resource->info->repos->repos,
                           slock->path,
                           slock->token,
                           slock->comment,
                           slock->is_dav_comment,
                           slock->expiration_date,
                           info->working_revnum,
                           info->lock_steal,
                           resource->pool);

  if (serr && serr->apr_err == SVN_ERR_FS_NO_USER)
    {
      svn_error_clear(serr);
      return dav_svn__new_error(resource->pool, HTTP_UNAUTHORIZED,
                                DAV_ERR_LOCK_SAVE_LOCK,
                                "Anonymous lock creation is not allowed.");
    }
  else if (serr)
    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                "Failed to create new lock.",
                                resource->pool);


  /* A standard webdav LOCK response doesn't include any information
     about the creation date.  We send it in a custom header, so that
     svn clients can fill in svn_lock_t->creation_date.  A generic DAV
     client should just ignore the header. */
  apr_table_setn(info->r->headers_out, SVN_DAV_CREATIONDATE_HEADER,
                 svn_time_to_cstring(slock->creation_date, resource->pool));

  /* A standard webdav LOCK response doesn't include any information
     about the owner of the lock.  ('DAV:owner' has nothing to do with
     authorization, it's just a comment that we map to
     svn_lock_t->comment.)  We send the owner in a custom header, so
     that svn clients can fill in svn_lock_t->owner.  A generic DAV
     client should just ignore the header. */
  apr_table_setn(info->r->headers_out, SVN_DAV_LOCK_OWNER_HEADER,
                 slock->owner);

  /* Log the locking as a 'high-level' action. */
  dav_svn__operational_log(resource->info,
                           svn_log__lock_one_path(slock->path, info->lock_steal,
                                                  resource->info->r->pool));

  return 0;
}
Пример #7
0
/*
** Get the locks associated with the specified resource.
**
** If resolve_locks is true (non-zero), then any indirect locks are
** resolved to their actual, direct lock (i.e. the reference to followed
** to the original lock).
**
** The locks, if any, are returned as a linked list in no particular
** order. If no locks are present, then *locks will be NULL.
**
** #define DAV_GETLOCKS_RESOLVED   0    -- resolve indirects to directs
** #define DAV_GETLOCKS_PARTIAL    1    -- leave indirects partially filled
** #define DAV_GETLOCKS_COMPLETE   2    -- fill out indirect locks
*/
static dav_error *
get_locks(dav_lockdb *lockdb,
          const dav_resource *resource,
          int calltype,
          dav_lock **locks)
{
  dav_lockdb_private *info = lockdb->info;
  svn_error_t *serr;
  svn_lock_t *slock;
  dav_lock *lock = NULL;

  /* We only support exclusive locks, not shared ones.  So this
     function always returns a "list" of exactly one lock, or just a
     NULL list.  The 'calltype' arg is also meaningless, since we
     don't support locks on collections.  */

  /* Sanity check:  if the resource has no associated path in the fs,
     then there's nothing to do.  */
  if (! resource->info->repos_path)
    {
      *locks = NULL;
      return 0;
    }

  /* The Big Lie: if the client ran 'svn lock', then we have
     to pretend that there's no existing lock.  Otherwise mod_dav will
     throw '403 Locked' without even attempting to create a new
     lock.  For the --force case, this is required and for the non-force case,
     we allow the filesystem to produce a better error for svn clients.
  */
  if (info->r->method_number == M_LOCK)
    {
      *locks = NULL;
      return 0;
    }

  /* If the resource's fs path is unreadable, we don't want to say
     anything about locks attached to it.*/
  if (! dav_svn__allow_read_resource(resource, SVN_INVALID_REVNUM,
                                     resource->pool))
    return dav_svn__new_error(resource->pool, HTTP_FORBIDDEN,
                              DAV_ERR_LOCK_SAVE_LOCK,
                              "Path is not accessible.");

  serr = svn_fs_get_lock(&slock,
                         resource->info->repos->fs,
                         resource->info->repos_path,
                         resource->pool);
  if (serr)
    return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                "Failed to check path for a lock.",
                                resource->pool);

  if (slock != NULL)
    {
      svn_lock_to_dav_lock(&lock, slock, info->lock_break,
                           resource->exists, resource->pool);

      /* Let svn clients know the creationdate of the slock. */
      apr_table_setn(info->r->headers_out, SVN_DAV_CREATIONDATE_HEADER,
                     svn_time_to_cstring(slock->creation_date,
                                         resource->pool));

      /* Let svn clients know who "owns" the slock. */
      apr_table_setn(info->r->headers_out, SVN_DAV_LOCK_OWNER_HEADER,
                     slock->owner);
    }

  *locks = lock;
  return 0;
}