/*
* lock.c: mod_dav_svn locking provider functions
*
* ====================================================================
* Copyright (c) 2000-2006 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <httpd.h>
#include <http_log.h>
#include <mod_dav.h>
#include <apr_uuid.h>
#include <apr_time.h>
#include "svn_fs.h"
#include "svn_repos.h"
#include "svn_dav.h"
#include "svn_time.h"
#include "svn_pools.h"
#include "dav_svn.h"
struct dav_lockdb_private
{
/* These represent 'custom' request hearders only sent by svn clients: */
svn_boolean_t lock_steal;
svn_boolean_t lock_break;
svn_boolean_t keep_locks;
svn_revnum_t working_revnum;
/* The original request, so we can set 'custom' output headers. */
request_rec *r;
};
/* Helper func: convert an svn_lock_t to a dav_lock, allocated in
pool. EXISTS_P indicates whether slock->path actually exists or not.
If HIDE_AUTH_USER is set, then do not return the svn lock's 'owner'
as dlock->auth_user.
*/
static void
svn_lock_to_dav_lock(dav_lock **dlock,
const svn_lock_t *slock,
svn_boolean_t hide_auth_user,
svn_boolean_t exists_p,
apr_pool_t *pool)
{
dav_lock *lock = apr_pcalloc(pool, sizeof(*lock));
dav_locktoken *token = apr_pcalloc(pool, sizeof(*token));
lock->rectype = DAV_LOCKREC_DIRECT;
lock->scope = DAV_LOCKSCOPE_EXCLUSIVE;
lock->type = DAV_LOCKTYPE_WRITE;
lock->depth = 0;
lock->is_locknull = exists_p;
token->uuid_str = apr_pstrdup(pool, slock->token);
lock->locktoken = token;
/* the svn_lock_t 'comment' field maps to the 'DAV:owner' field. */
if (slock->comment)
{
if (! slock->is_dav_comment)
{
/* This comment was originally given to us by an svn client,
so, we need to wrap the naked comment with <DAV:owner>,
and xml-escape it for safe transport, lest we send back
an invalid http response. (mod_dav won't do it for us,
because it assumes that it personally created every lock
in the repository.) */
lock->owner = apr_pstrcat(pool,
"<D:owner xmlns:D=\"DAV:\">",
apr_xml_quote_string(pool,
slock->comment, 1),
"</D:owner>", NULL);
}
else
{
lock->owner = apr_pstrdup(pool, slock->comment);
}
}
else
lock->owner = NULL;
/* the svn_lock_t 'owner' is the actual authenticated owner of the
lock, and maps to the 'auth_user' field in the mod_dav lock. */
/* (If the client ran 'svn unlock --force', then we don't want to
return lock->auth_user. Otherwise mod_dav will throw an error
when lock->auth_user and r->user don't match.) */
if (! hide_auth_user)
lock->auth_user = apr_pstrdup(pool, slock->owner);
/* This is absurd. apr_time.h has an apr_time_t->time_t func,
but not the reverse?? */
if (slock->expiration_date)
lock->timeout = (time_t) (slock->expiration_date / APR_USEC_PER_SEC);
else
lock->timeout = DAV_TIMEOUT_INFINITE;
*dlock = lock;
}
/* Helper func for dav_lock_to_svn_lock: take an incoming
"<D:owner><foo></D:owner>" tag and convert it to
"<foo>". */
static dav_error *
unescape_xml(const char **output,
const char *input,
apr_pool_t *pool)
{
apr_xml_parser *xml_parser = apr_xml_parser_create(pool);
apr_xml_doc *xml_doc;
apr_status_t apr_err;
const char *xml_input = apr_pstrcat
(pool, "<?xml version=\"1.0\" encoding=\"utf-8\"?>", input, NULL);
apr_err = apr_xml_parser_feed(xml_parser, xml_input, strlen(xml_input));
if (!apr_err)
apr_err = apr_xml_parser_done(xml_parser, &xml_doc);
if (apr_err)
{
char errbuf[1024];
(void)apr_xml_parser_geterror(xml_parser, errbuf, sizeof(errbuf));
return dav_new_error(pool, HTTP_INTERNAL_SERVER_ERROR,
DAV_ERR_LOCK_SAVE_LOCK, errbuf);
}
apr_xml_to_text(pool, xml_doc->root, APR_XML_X2T_INNER,
xml_doc->namespaces, NULL, output, NULL);
return SVN_NO_ERROR;
}
/* Helper func: convert a dav_lock to an svn_lock_t, allocated in pool. */
static dav_error *
dav_lock_to_svn_lock(svn_lock_t **slock,
const dav_lock *dlock,
const char *path,
dav_lockdb_private *info,
svn_boolean_t is_svn_client,
apr_pool_t *pool)
{
svn_lock_t *lock;
/* Sanity checks */
if (dlock->type != DAV_LOCKTYPE_WRITE)
return dav_new_error(pool, HTTP_BAD_REQUEST,
DAV_ERR_LOCK_SAVE_LOCK,
"Only 'write' locks are supported.");
if (dlock->scope != DAV_LOCKSCOPE_EXCLUSIVE)
return dav_new_error(pool, HTTP_BAD_REQUEST,
DAV_ERR_LOCK_SAVE_LOCK,
"Only exclusive locks are supported.");
lock = svn_lock_create(pool);
lock->path = apr_pstrdup(pool, path);
lock->token = apr_pstrdup(pool, dlock->locktoken->uuid_str);
/* DAV has no concept of lock creationdate, so assume 'now' */
lock->creation_date = apr_time_now();
if (dlock->auth_user)
lock->owner = apr_pstrdup(pool, dlock->auth_user);
/* We need to be very careful about stripping the <D:owner> tag away
from the cdata. It's okay to do for svn clients, but not other
DAV clients! */
if (dlock->owner)
{
if (is_svn_client)
{
/* mod_dav has forcibly xml-escaped the comment before
handing it to us; we need to xml-unescape it (and remove
the <D:owner> wrapper) when storing in the repository, so
it looks reasonable to the rest of svn. */
dav_error *derr;
lock->is_dav_comment = 0; /* comment is NOT xml-wrapped. */
derr = unescape_xml(&(lock->comment), dlock->owner, pool);
if (derr)
return derr;
}
else
{
/* The comment comes from a non-svn client; don't touch
this data at all. */
lock->comment = apr_pstrdup(pool, dlock->owner);
lock->is_dav_comment = 1; /* comment IS xml-wrapped. */
}
}
if (dlock->timeout == DAV_TIMEOUT_INFINITE)
lock->expiration_date = 0; /* never expires */
else
lock->expiration_date = ((apr_time_t)dlock->timeout) * APR_USEC_PER_SEC;
*slock = lock;
return 0;
}
/* Helper func: invoke mod_dav_svn's authz_read callback on
PATH in HEAD revision, return the readability result in *READABLE. */
static dav_error *
check_readability(svn_boolean_t *readable,
request_rec *r,
const dav_svn_repos *repos,
const char *path,
apr_pool_t *pool)
{
svn_error_t *serr;
svn_fs_root_t *headroot;
svn_revnum_t headrev;
dav_svn_authz_read_baton arb;
arb.r = r;
arb.repos = repos;
serr = svn_fs_youngest_rev(&headrev, repos->fs, pool);
if (serr)
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"Failed to get youngest filesystem revision.",
pool);
serr = svn_fs_revision_root(&headroot, repos->fs, headrev, pool);
if (serr)
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"Failed to open revision root for HEAD.",
pool);
serr = dav_svn_authz_read(readable, headroot, path, &arb, pool);
if (serr)
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"Failed to check readability of a path.",
pool);
return 0;
}
/* ---------------------------------------------------------------- */
/* mod_dav locking vtable starts here: */
/* Return the supportedlock property for a resource */
static const char *
dav_svn_get_supportedlock(const dav_resource *resource)
{
/* This is imitating what mod_dav_fs is doing. Note that unlike
mod_dav_fs, however, we don't support "shared" locks, only
"exclusive" ones. Nor do we support locks on collections. */
static const char supported[] = DEBUG_CR
"<D:lockentry>" DEBUG_CR
"<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
"<D:locktype><D:write/></D:locktype>" DEBUG_CR
"</D:lockentry>" DEBUG_CR;
if (resource->collection)
return NULL;
else
return supported;
}
/* Parse a lock token URI, returning a lock token object allocated
* in the given pool.
*/
static dav_error *
dav_svn_parse_locktoken(apr_pool_t *pool,
const char *char_token,
dav_locktoken **locktoken_p)
{
dav_locktoken *token = apr_pcalloc(pool, sizeof(*token));
/* libsvn_fs already produces a valid locktoken URI. */
token->uuid_str = apr_pstrdup(pool, char_token);
*locktoken_p = token;
return 0;
}
/* Format a lock token object into a URI string, allocated in
* the given pool.
*
* Always returns non-NULL.
*/
static const char *
dav_svn_format_locktoken(apr_pool_t *p,
const dav_locktoken *locktoken)
{
/* libsvn_fs already produces a valid locktoken URI. */
return apr_pstrdup(p, locktoken->uuid_str);
}
/* Compare two lock tokens.
*
* Result < 0 => lt1 < lt2
* Result == 0 => lt1 == lt2
* Result > 0 => lt1 > lt2
*/
static int
dav_svn_compare_locktoken(const dav_locktoken *lt1,
const dav_locktoken *lt2)
{
return strcmp(lt1->uuid_str, lt2->uuid_str);
}
/* Open the provider's lock database.
*
* The provider may or may not use a "real" database for locks
* (a lock could be an attribute on a resource, for example).
*
* The provider may choose to use the value of the DAVLockDB directive
* (as returned by dav_get_lockdb_path()) to decide where to place
* any storage it may need.
*
* The request storage pool should be associated with the lockdb,
* so it can be used in subsequent operations.
*
* If ro != 0, only readonly operations will be performed.
* If force == 0, the open can be "lazy"; no subsequent locking operations
* may occur.
* If force != 0, locking operations will definitely occur.
*/
static dav_error *
dav_svn_open_lockdb(request_rec *r,
int ro,
int force,
dav_lockdb **lockdb)
{
const char *svn_client_options, *version_name;
dav_lockdb *db = apr_pcalloc(r->pool, sizeof(*db));
dav_lockdb_private *info = apr_pcalloc(r->pool, sizeof(*info));
info->r = r;
/* Is this an svn client? */
/* Check to see if an svn client sent any custom X-SVN-* headers in
the request. */
svn_client_options = apr_table_get(r->headers_in, SVN_DAV_OPTIONS_HEADER);
if (svn_client_options)
{
/* 'svn [lock | unlock] --force' */
if (ap_strstr_c(svn_client_options, SVN_DAV_OPTION_LOCK_BREAK))
info->lock_break = TRUE;
if (ap_strstr_c(svn_client_options, SVN_DAV_OPTION_LOCK_STEAL))
info->lock_steal = TRUE;
if (ap_strstr_c(svn_client_options, SVN_DAV_OPTION_KEEP_LOCKS))
info->keep_locks = TRUE;
}
/* 'svn lock' wants to make svn_fs_lock() do an out-of-dateness check. */
version_name = apr_table_get(r->headers_in, SVN_DAV_VERSION_NAME_HEADER);
info->working_revnum = version_name ?
SVN_STR_TO_REV(version_name): SVN_INVALID_REVNUM;
/* The generic lockdb structure. */
db->hooks = &dav_svn_hooks_locks;
db->ro = ro;
db->info = info;
*lockdb = db;
return 0;
}
/* Indicates completion of locking operations */
static void
dav_svn_close_lockdb(dav_lockdb *lockdb)
{
/* nothing to do here. */
return;
}
/* Take a resource out of the lock-null state. */
static dav_error *
dav_svn_remove_locknull_state(dav_lockdb *lockdb,
const dav_resource *resource)
{
/* mod_dav_svn supports RFC2518bis which does not actually require
the server to create lock-null resources. Instead, we create
zero byte files when a lock comes in on a non-existent path.
mod_dav_svn never creates any lock-null resources, so this
function is never called by mod_dav. */
return 0; /* Just to suppress compiler warnings. */
}
/*
** Create a (direct) lock structure for the given resource. A locktoken
** will be created.
**
** The lock provider may store private information into lock->info.
*/
static dav_error *
dav_svn_create_lock(dav_lockdb *lockdb,
const dav_resource *resource,
dav_lock **lock)
{
svn_error_t *serr;
dav_locktoken *token = apr_pcalloc(resource->pool, sizeof(*token));
dav_lock *dlock = apr_pcalloc(resource->pool, sizeof(*dlock));
dlock->rectype = DAV_LOCKREC_DIRECT;
dlock->is_locknull = resource->exists;
dlock->scope = DAV_LOCKSCOPE_UNKNOWN;
dlock->type = DAV_LOCKTYPE_UNKNOWN;
dlock->depth = 0;
serr = svn_fs_generate_lock_token(&(token->uuid_str),
resource->info->repos->fs,
resource->pool);
if (serr)
return dav_svn_convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
"Failed to generate a lock token.",
resource->pool);
dlock->locktoken = token;
/* allowing mod_dav to fill in dlock->timeout, owner, auth_user. */
/* dlock->info and dlock->next are NULL by default. */
*lock = dlock;
return 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 *
dav_svn_get_locks(dav_lockdb *lockdb,
const dav_resource *resource,
int calltype,
dav_lock **locks)
{
dav_lockdb_private *info = lockdb->info;
svn_error_t *serr;
dav_error *derr;
svn_lock_t *slock;
svn_boolean_t readable = FALSE;
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.*/
derr = check_readability(&readable,
resource->info->r, resource->info->repos,
resource->info->repos_path, resource->pool);
if (derr)
return derr;
if (! readable)
return dav_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;
}
/*
** 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 *
dav_svn_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;
dav_error *derr;
svn_lock_t *slock;
dav_lock *dlock = NULL;
svn_boolean_t readable = FALSE;
/* If the resource's fs path is unreadable, we don't want to say
anything about locks attached to it.*/
derr = check_readability(&readable,
resource->info->r, resource->info->repos,
resource->info->repos_path, resource->pool);
if (derr)
return derr;
if (! readable)
return dav_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_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;
}
/*
** 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 *
dav_svn_has_locks(dav_lockdb *lockdb,
const dav_resource *resource,
int *locks_present)
{
dav_lockdb_private *info = lockdb->info;
svn_error_t *serr;
dav_error *derr;
svn_lock_t *slock;
svn_boolean_t readable = FALSE;
/* 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)
{
*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.*/
derr = check_readability(&readable,
resource->info->r, resource->info->repos,
resource->info->repos_path, resource->pool);
if (derr)
return derr;
if (! readable)
return dav_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;
}
/*
** 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 *
dav_svn_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;
svn_boolean_t readable = FALSE;
/* If the resource's fs path is unreadable, we don't allow a lock to
be created on it. */
derr = check_readability(&readable,
resource->info->r, resource->info->repos,
resource->info->repos_path, resource->pool);
if (derr)
return derr;
if (! readable)
return dav_new_error(resource->pool, HTTP_FORBIDDEN,
DAV_ERR_LOCK_SAVE_LOCK,
"Path is not accessible.");
if (lock->next)
return dav_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;
if (resource->info->repos->is_svn_client)
return dav_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_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_commit(&txn, repos->repos, rev,
repos->username, NULL,
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);
if ((serr = svn_repos_fs_commit_txn(&conflict_msg, repos->repos,
&new_rev, txn, resource->pool)))
{
svn_error_clear(svn_fs_abort_txn(txn, resource->pool));
return dav_svn_convert_err(serr, HTTP_CONFLICT,
apr_psprintf(resource->pool,
"Conflict when committing "
"'%s'.", conflict_msg),
resource->pool);
}
}
/* 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)
return dav_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. */
apr_table_set(resource->info->r->subprocess_env, "SVN-ACTION",
apr_psprintf(resource->info->r->pool,
"lock '%s'",
svn_path_uri_encode(slock->path,
resource->info->r->pool)));
return 0;
}
/*
** Remove any lock that has the specified locktoken.
**
** If locktoken == NULL, then ALL locks are removed.
*/
static dav_error *
dav_svn_remove_lock(dav_lockdb *lockdb,
const dav_resource *resource,
const dav_locktoken *locktoken)
{
dav_lockdb_private *info = lockdb->info;
svn_error_t *serr;
dav_error *derr;
svn_boolean_t readable = FALSE;
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. */
derr = check_readability(&readable,
resource->info->r, resource->info->repos,
resource->info->repos_path, resource->pool);
if (derr)
return derr;
if (! readable)
return dav_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)
return dav_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. */
apr_table_set(resource->info->r->subprocess_env, "SVN-ACTION",
apr_psprintf(resource->info->r->pool,
"unlock '%s'",
svn_path_uri_encode(resource->info->repos_path,
resource->info->r->pool)));
return 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 *
dav_svn_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;
dav_error *derr;
svn_lock_t *slock;
dav_lock *dlock;
svn_boolean_t readable = FALSE;
/* If the resource's fs path is unreadable, we don't want to say
anything about locks attached to it.*/
derr = check_readability(&readable,
resource->info->r, resource->info->repos,
resource->info->repos_path, resource->pool);
if (derr)
return derr;
if (! readable)
return dav_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_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)
return dav_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;
}
/* The main locking vtable, provided to mod_dav */
const dav_hooks_locks dav_svn_hooks_locks = {
dav_svn_get_supportedlock,
dav_svn_parse_locktoken,
dav_svn_format_locktoken,
dav_svn_compare_locktoken,
dav_svn_open_lockdb,
dav_svn_close_lockdb,
dav_svn_remove_locknull_state,
dav_svn_create_lock,
dav_svn_get_locks,
dav_svn_find_lock,
dav_svn_has_locks,
dav_svn_append_locks,
dav_svn_remove_lock,
dav_svn_refresh_locks,
NULL,
NULL /* hook structure context */
};
syntax highlighted by Code2HTML, v. 0.9.1