2
* lock.c : routines for managing lock states in the DAV server
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
13
* http://www.apache.org/licenses/LICENSE-2.0
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
21
* ====================================================================
26
#define APR_WANT_STRFUNC
30
#include "svn_error.h"
31
#include "svn_pools.h"
33
#include "../libsvn_ra/ra_loader.h"
35
#include "svn_string.h"
37
#include "svn_private_config.h"
41
static const svn_ra_neon__xml_elm_t lock_elements[] =
43
/* lockdiscovery elements */
44
{ "DAV:", "response", ELEM_response, 0 },
45
{ "DAV:", "propstat", ELEM_propstat, 0 },
46
{ "DAV:", "status", ELEM_status, SVN_RA_NEON__XML_CDATA },
47
/* extend lockdiscovery elements here;
48
### Remember to update do_lock() when you change the number of
49
elements here: it contains a hard reference to the next element. */
51
/* lock and lockdiscovery elements */
52
{ "DAV:", "prop", ELEM_prop, 0 },
53
{ "DAV:", "lockdiscovery", ELEM_lock_discovery, 0 },
54
{ "DAV:", "activelock", ELEM_lock_activelock, 0 },
55
{ "DAV:", "locktype", ELEM_lock_type, SVN_RA_NEON__XML_CDATA },
56
{ "DAV:", "lockscope", ELEM_lock_scope, SVN_RA_NEON__XML_CDATA },
57
{ "DAV:", "depth", ELEM_lock_depth, SVN_RA_NEON__XML_CDATA },
58
{ "DAV:", "owner", ELEM_lock_owner, SVN_RA_NEON__XML_COLLECT },
59
{ "DAV:", "timeout", ELEM_lock_timeout, SVN_RA_NEON__XML_CDATA },
60
{ "DAV:", "locktoken", ELEM_lock_token, 0 },
61
{ "DAV:", "href", ELEM_href, SVN_RA_NEON__XML_CDATA },
62
{ "", "", ELEM_unknown, SVN_RA_NEON__XML_COLLECT },
63
/* extend lock elements here */
68
typedef struct lock_baton_t
70
svn_stringbuf_t *cdata;
72
const svn_ra_neon__xml_elm_t *xml_table;
74
/* lockdiscovery fields */
75
svn_stringbuf_t *href;
76
svn_stringbuf_t *status_line;
78
/* lock and lockdiscovery fields */
80
svn_stringbuf_t *owner;
81
svn_stringbuf_t *timeout;
82
svn_stringbuf_t *depth;
83
svn_stringbuf_t *token;
87
lock_start_element(int *elem, void *baton, int parent,
88
const char *nspace, const char *name, const char **atts)
90
lock_baton_t *b = baton;
91
const svn_ra_neon__xml_elm_t *elm =
92
svn_ra_neon__lookup_xml_elem(b->xml_table, nspace, name);
96
*elem = NE_XML_DECLINE;
100
/* collect interesting element contents */
101
/* owner, href inside locktoken, depth, timeout */
104
case ELEM_lock_owner:
105
case ELEM_lock_timeout:
106
case ELEM_lock_depth:
108
b->cdata = svn_stringbuf_create("", b->pool);
112
if (parent == ELEM_lock_token
113
|| parent == ELEM_response)
114
b->cdata = svn_stringbuf_create("", b->pool);
127
lock_end_element(void *baton, int state, const char *nspace, const char *name)
129
lock_baton_t *b = baton;
134
case ELEM_lock_owner:
138
case ELEM_lock_timeout:
139
b->timeout = b->cdata;
142
case ELEM_lock_depth:
147
if (b->parent == ELEM_lock_token)
154
b->status_line = b->cdata;
164
lock_cdata(void *baton, int state, const char *cdata, size_t len)
166
lock_baton_t *b = baton;
169
svn_stringbuf_appendbytes(b->cdata, cdata, len);
176
lock_from_baton(svn_lock_t **lock,
177
svn_ra_neon__request_t *req,
179
lock_baton_t *lrb, apr_pool_t *pool)
182
svn_lock_t *lck = svn_lock_create(pool);
185
lck->token = lrb->token->data;
193
val = ne_get_response_header(req->ne_req, SVN_DAV_CREATIONDATE_HEADER);
195
SVN_ERR_W(svn_time_from_cstring(&(lck->creation_date), val, pool),
196
_("Invalid creation date header value in response."));
198
val = ne_get_response_header(req->ne_req, SVN_DAV_LOCK_OWNER_HEADER);
200
lck->owner = apr_pstrdup(pool, val);
202
lck->comment = lrb->owner->data;
207
const char *timeout_str = lrb->timeout->data;
209
if (strcmp(timeout_str, "Infinite") != 0)
211
if (strncmp("Second-", timeout_str, strlen("Second-")) == 0)
215
SVN_ERR(svn_cstring_atoi(&time_offset, &(timeout_str[7])));
216
lck->expiration_date = lck->creation_date
217
+ apr_time_from_sec(time_offset);
220
return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS,
221
NULL, _("Invalid timeout value"));
224
lck->expiration_date = 0;
232
do_lock(svn_lock_t **lock,
233
svn_ra_session_t *session,
237
svn_revnum_t current_rev,
240
svn_ra_neon__request_t *req;
241
svn_stringbuf_t *body;
246
ne_xml_parser *lck_parser;
247
svn_ra_neon__session_t *ras = session->priv;
248
lock_baton_t *lrb = apr_pcalloc(pool, sizeof(*lrb));
249
apr_hash_t *extra_headers;
250
svn_error_t *err = SVN_NO_ERROR;
252
/* To begin, we convert the incoming path into an absolute fs-path. */
253
url = svn_path_url_add_component2(ras->url->data, path, pool);
254
SVN_ERR(svn_ra_neon__get_baseline_info(NULL, &fs_path, NULL, ras,
255
url, SVN_INVALID_REVNUM, pool));
257
if (ne_uri_parse(url, &uri) != 0)
260
return svn_error_createf(SVN_ERR_RA_DAV_CREATING_REQUEST, NULL,
261
_("Failed to parse URI '%s'"), url);
264
SVN_ERR(svn_ra_neon__request_create(&req, ras, "LOCK", uri.path, pool));
268
lrb->xml_table = &(lock_elements[3]);
269
lck_parser = svn_ra_neon__xml_parser_create
271
lock_start_element, lock_cdata, lock_end_element, lrb);
273
body = svn_stringbuf_createf
275
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" DEBUG_CR
276
"<D:lockinfo xmlns:D=\"DAV:\">" DEBUG_CR
277
" <D:lockscope><D:exclusive /></D:lockscope>" DEBUG_CR
278
" <D:locktype><D:write /></D:locktype>" DEBUG_CR
279
"%s" /* maybe owner */
281
comment ? apr_pstrcat(pool,
283
apr_xml_quote_string(pool, comment, 0),
288
extra_headers = apr_hash_make(req->pool);
289
svn_ra_neon__set_header(extra_headers, "Depth", "0");
290
svn_ra_neon__set_header(extra_headers, "Timeout", "Infinite");
291
svn_ra_neon__set_header(extra_headers, "Content-Type",
292
"text/xml; charset=\"utf-8\"");
294
svn_ra_neon__set_header(extra_headers, SVN_DAV_OPTIONS_HEADER,
295
SVN_DAV_OPTION_LOCK_STEAL);
296
if (SVN_IS_VALID_REVNUM(current_rev))
297
svn_ra_neon__set_header(extra_headers, SVN_DAV_VERSION_NAME_HEADER,
298
apr_psprintf(req->pool, "%ld", current_rev));
300
err = svn_ra_neon__request_dispatch(&code, req, extra_headers, body->data,
305
err = svn_ra_neon__check_parse_error("LOCK", lck_parser, url);
309
/*###FIXME: we never verified whether we have received back the type
310
of lock we requested: was it shared/exclusive? was it write/otherwise?
311
How many did we get back? Only one? */
312
err = lock_from_baton(lock, req, fs_path, lrb, pool);
315
/* 405 == Method Not Allowed (Occurs when trying to lock a working
316
copy path which no longer exists at HEAD in the repository. */
319
svn_error_clear(err);
320
err = svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
321
_("Lock request failed: %d %s"),
322
code, req->code_desc);
324
svn_ra_neon__request_destroy(req);
330
svn_ra_neon__lock(svn_ra_session_t *session,
331
apr_hash_t *path_revs,
334
svn_ra_lock_callback_t lock_func,
338
apr_hash_index_t *hi;
339
apr_pool_t *iterpool = svn_pool_create(pool);
340
svn_ra_neon__session_t *ras = session->priv;
341
svn_error_t *ret_err = NULL;
343
/* ### TODO for issue 2263: Send all the locks over the wire at once. This
344
loop is just a temporary shim. */
345
for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
351
svn_revnum_t *revnum;
352
svn_error_t *err, *callback_err = NULL;
354
svn_pool_clear(iterpool);
356
apr_hash_this(hi, &key, NULL, &val);
360
err = do_lock(&lock, session, path, comment, force, *revnum, iterpool);
362
if (err && !SVN_ERR_IS_LOCK_ERROR(err))
369
callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
372
svn_error_clear(err);
376
ret_err = callback_err;
382
svn_pool_destroy(iterpool);
385
return svn_ra_neon__maybe_store_auth_info_after_result(ret_err, ras, pool);
389
/* ###TODO for issue 2263: Send all lock tokens to the server at once. */
391
do_unlock(svn_ra_session_t *session,
395
const svn_lock_t **old_lock,
398
svn_ra_neon__session_t *ras = session->priv;
400
const char *url_path;
402
svn_error_t *err = SVN_NO_ERROR;
404
apr_hash_t *extra_headers = apr_hash_make(pool);
409
/* Make a neon lock structure containing token and full URL to unlock. */
410
url = svn_path_url_add_component2(ras->url->data, path, pool);
411
if (ne_uri_parse(url, &uri) != 0)
414
return svn_error_createf(SVN_ERR_RA_DAV_CREATING_REQUEST, NULL,
415
_("Failed to parse URI '%s'"), url);
418
url_path = apr_pstrdup(pool, uri.path);
420
/* In the case of 'force', we might not have a token at all.
421
Unfortunately, mod_dav insists on having a valid token for
422
UNLOCK requests. That means we need to fetch the token. */
427
SVN_ERR(svn_ra_neon__get_lock(session, &lock, path, pool));
429
return svn_error_createf(SVN_ERR_RA_NOT_LOCKED, NULL,
430
_("'%s' is not locked in the repository"),
437
apr_hash_set(extra_headers, "Lock-Token", APR_HASH_KEY_STRING,
438
apr_psprintf(pool, "<%s>", token));
440
apr_hash_set(extra_headers, SVN_DAV_OPTIONS_HEADER, APR_HASH_KEY_STRING,
441
SVN_DAV_OPTION_LOCK_BREAK);
446
err = svn_ra_neon__simple_request(&code, ras, "UNLOCK", url_path,
447
extra_headers, NULL, 204, 0, pool);
449
if (err && ((err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED)
450
|| (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN)))
455
return svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, err,
456
_("Unlock failed on '%s'"
457
" (%d Forbidden)"), path, code);
459
return svn_error_createf(SVN_ERR_FS_NO_SUCH_LOCK, err,
460
_("No lock on path '%s'"
461
" (%d Bad Request)"), path, code);
467
return svn_error_trace(err);
472
svn_ra_neon__unlock(svn_ra_session_t *session,
473
apr_hash_t *path_tokens,
475
svn_ra_lock_callback_t lock_func,
479
apr_hash_index_t *hi;
480
apr_pool_t *iterpool = svn_pool_create(pool);
481
svn_ra_neon__session_t *ras = session->priv;
482
svn_error_t *ret_err = NULL;
484
/* ### TODO for issue 2263: Send all the lock tokens over the wire at once.
485
This loop is just a temporary shim. */
486
for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
492
svn_error_t *err, *callback_err = NULL;
493
const svn_lock_t *old_lock = NULL;
495
svn_pool_clear(iterpool);
497
apr_hash_this(hi, &key, NULL, &val);
499
/* Since we can't store NULL values in a hash, we turn "" to
501
if (strcmp(val, "") != 0)
506
err = do_unlock(session, path, token, force, &old_lock, iterpool);
508
if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
515
callback_err = svn_error_trace(
516
lock_func(lock_baton, path, FALSE, old_lock, err, iterpool));
518
svn_error_clear(err);
522
ret_err = callback_err;
527
svn_pool_destroy(iterpool);
530
return svn_error_trace(
531
svn_ra_neon__maybe_store_auth_info_after_result(ret_err, ras, pool));
536
svn_ra_neon__get_lock_internal(svn_ra_neon__session_t *ras,
545
lock_baton_t *lrb = apr_pcalloc(pool, sizeof(*lrb));
546
svn_ra_neon__request_t *req;
547
ne_xml_parser *lck_parser;
548
apr_hash_t *extra_headers;
549
static const char *body =
550
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" DEBUG_CR
551
"<D:propfind xmlns:D=\"DAV:\">" DEBUG_CR
553
" <D:lockdiscovery />" DEBUG_CR
554
" </D:prop>" DEBUG_CR
557
/* To begin, we convert the incoming path into an absolute fs-path. */
558
url = svn_path_url_add_component2(ras->url->data, path, pool);
560
err = svn_ra_neon__get_baseline_info(NULL, &fs_path, NULL, ras,
561
url, SVN_INVALID_REVNUM, pool);
562
SVN_ERR(svn_ra_neon__maybe_store_auth_info_after_result(err, ras, pool));
564
ne_uri_parse(url, &uri);
565
url = apr_pstrdup(pool, uri.path);
568
SVN_ERR(svn_ra_neon__request_create(&req, ras, "PROPFIND", url, pool));
571
lrb->xml_table = lock_elements;
572
lck_parser = svn_ra_neon__xml_parser_create
573
(req, ne_accept_207, lock_start_element, lock_cdata, lock_end_element, lrb);
575
extra_headers = apr_hash_make(req->pool);
576
svn_ra_neon__set_header(extra_headers, "Depth", "0");
577
svn_ra_neon__set_header(extra_headers, "Content-Type",
578
"text/xml; charset=\"utf-8\"");
580
err = svn_ra_neon__request_dispatch(NULL, req, extra_headers, body,
584
err = svn_error_quick_wrap(err, _("Failed to fetch lock information"));
588
err = svn_ra_neon__check_parse_error("PROPFIND", lck_parser, url);
592
/*###FIXME We assume here we only got one lock response. The WebDAV
593
spec makes no such guarantees. How to make sure we grab the one we need? */
594
err = lock_from_baton(lock, req, fs_path, lrb, pool);
597
svn_ra_neon__request_destroy(req);
603
svn_ra_neon__get_lock(svn_ra_session_t *session,
608
return svn_ra_neon__get_lock_internal(session->priv, lock, path, pool);