2 * lock.c : routines for managing lock states in the DAV server
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
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.
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 * ====================================================================
21 #define APR_WANT_STRFUNC
24 #include "svn_error.h"
25 #include "svn_pools.h"
27 #include "../libsvn_ra/ra_loader.h"
30 #include "svn_private_config.h"
34 static const svn_ra_neon__xml_elm_t lock_elements
[] =
36 /* lockdiscovery elements */
37 { "DAV:", "response", ELEM_response
, 0 },
38 { "DAV:", "propstat", ELEM_propstat
, 0 },
39 { "DAV:", "status", ELEM_status
, SVN_RA_NEON__XML_CDATA
},
40 /* extend lockdiscovery elements here;
41 ### Remember to update do_lock() when you change the number of
42 elements here: it contains a hard reference to the next element. */
44 /* lock and lockdiscovery elements */
45 { "DAV:", "prop", ELEM_prop
, 0 },
46 { "DAV:", "lockdiscovery", ELEM_lock_discovery
, 0 },
47 { "DAV:", "activelock", ELEM_lock_activelock
, 0 },
48 { "DAV:", "locktype", ELEM_lock_type
, SVN_RA_NEON__XML_CDATA
},
49 { "DAV:", "lockscope", ELEM_lock_scope
, SVN_RA_NEON__XML_CDATA
},
50 { "DAV:", "depth", ELEM_lock_depth
, SVN_RA_NEON__XML_CDATA
},
51 { "DAV:", "owner", ELEM_lock_owner
, SVN_RA_NEON__XML_COLLECT
},
52 { "DAV:", "timeout", ELEM_lock_timeout
, SVN_RA_NEON__XML_CDATA
},
53 { "DAV:", "locktoken", ELEM_lock_token
, 0 },
54 { "DAV:", "href", ELEM_href
, SVN_RA_NEON__XML_CDATA
},
55 { "", "", ELEM_unknown
, SVN_RA_NEON__XML_COLLECT
},
56 /* extend lock elements here */
63 svn_stringbuf_t
*cdata
;
65 const svn_ra_neon__xml_elm_t
*xml_table
;
67 /* lockdiscovery fields */
68 svn_stringbuf_t
*href
;
69 svn_stringbuf_t
*status_line
;
71 /* lock and lockdiscovery fields */
73 svn_stringbuf_t
*owner
;
74 svn_stringbuf_t
*timeout
;
75 svn_stringbuf_t
*depth
;
76 svn_stringbuf_t
*token
;
80 lock_start_element(int *elem
, void *baton
, int parent
,
81 const char *nspace
, const char *name
, const char **atts
)
83 lock_baton_t
*b
= baton
;
84 const svn_ra_neon__xml_elm_t
*elm
=
85 svn_ra_neon__lookup_xml_elem(b
->xml_table
, nspace
, name
);
89 *elem
= NE_XML_DECLINE
;
93 /* collect interesting element contents */
94 /* owner, href inside locktoken, depth, timeout */
98 case ELEM_lock_timeout
:
101 b
->cdata
= svn_stringbuf_create("", b
->pool
);
105 if (parent
== ELEM_lock_token
106 || parent
== ELEM_response
)
107 b
->cdata
= svn_stringbuf_create("", b
->pool
);
120 lock_end_element(void *baton
, int state
, const char *nspace
, const char *name
)
122 lock_baton_t
*b
= baton
;
127 case ELEM_lock_owner
:
131 case ELEM_lock_timeout
:
132 b
->timeout
= b
->cdata
;
135 case ELEM_lock_depth
:
140 if (b
->parent
== ELEM_lock_token
)
147 b
->status_line
= b
->cdata
;
157 lock_cdata(void *baton
, int state
, const char *cdata
, size_t len
)
159 lock_baton_t
*b
= baton
;
162 svn_stringbuf_appendbytes(b
->cdata
, cdata
, len
);
169 lock_from_baton(svn_lock_t
**lock
,
170 svn_ra_neon__request_t
*req
,
172 lock_baton_t
*lrb
, apr_pool_t
*pool
)
175 svn_lock_t
*lck
= svn_lock_create(pool
);
178 lck
->token
= lrb
->token
->data
;
186 val
= ne_get_response_header(req
->ne_req
, SVN_DAV_CREATIONDATE_HEADER
);
188 SVN_ERR_W(svn_time_from_cstring(&(lck
->creation_date
), val
, pool
),
189 _("Invalid creation date header value in response."));
191 val
= ne_get_response_header(req
->ne_req
, SVN_DAV_LOCK_OWNER_HEADER
);
193 lck
->owner
= apr_pstrdup(pool
, val
);
195 lck
->comment
= lrb
->owner
->data
;
200 const char *timeout_str
= lrb
->timeout
->data
;
202 if (strcmp(timeout_str
, "Infinite") != 0)
204 if (strncmp("Second-", timeout_str
, strlen("Second-")) == 0)
206 int time_offset
= atoi(&(timeout_str
[7]));
208 lck
->expiration_date
= lck
->creation_date
209 + apr_time_from_sec(time_offset
);
212 return svn_error_create(SVN_ERR_RA_DAV_RESPONSE_HEADER_BADNESS
,
213 NULL
, _("Invalid timeout value"));
216 lck
->expiration_date
= 0;
224 do_lock(svn_lock_t
**lock
,
225 svn_ra_session_t
*session
,
229 svn_revnum_t current_rev
,
232 svn_ra_neon__request_t
*req
;
233 svn_stringbuf_t
*body
;
237 svn_string_t fs_path
;
238 ne_xml_parser
*lck_parser
;
239 svn_ra_neon__session_t
*ras
= session
->priv
;
240 lock_baton_t
*lrb
= apr_pcalloc(pool
, sizeof(*lrb
));
241 apr_hash_t
*extra_headers
;
242 svn_error_t
*err
= SVN_NO_ERROR
;
244 /* To begin, we convert the incoming path into an absolute fs-path. */
245 url
= svn_path_url_add_component(ras
->url
->data
, path
, pool
);
246 SVN_ERR(svn_ra_neon__get_baseline_info(NULL
, NULL
, &fs_path
, NULL
, ras
,
247 url
, SVN_INVALID_REVNUM
, pool
));
249 if (ne_uri_parse(url
, &uri
) != 0)
252 return svn_error_createf(SVN_ERR_RA_DAV_CREATING_REQUEST
, NULL
,
253 _("Failed to parse URI '%s'"), url
);
256 req
= svn_ra_neon__request_create(ras
, "LOCK", uri
.path
, pool
);
260 lrb
->xml_table
= &(lock_elements
[3]);
261 lck_parser
= svn_ra_neon__xml_parser_create
263 lock_start_element
, lock_cdata
, lock_end_element
, lrb
);
265 body
= svn_stringbuf_createf
267 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" DEBUG_CR
268 "<D:lockinfo xmlns:D=\"DAV:\">" DEBUG_CR
269 " <D:lockscope><D:exclusive /></D:lockscope>" DEBUG_CR
270 " <D:locktype><D:write /></D:locktype>" DEBUG_CR
271 "%s" /* maybe owner */
274 ? apr_psprintf(req
->pool
, " <D:owner>%s</D:owner>" DEBUG_CR
, comment
)
277 extra_headers
= apr_hash_make(req
->pool
);
278 svn_ra_neon__set_header(extra_headers
, "Depth", "0");
279 svn_ra_neon__set_header(extra_headers
, "Timeout", "Infinite");
280 svn_ra_neon__set_header(extra_headers
, "Content-Type",
281 "text/xml; charset=\"utf-8\"");
283 svn_ra_neon__set_header(extra_headers
, SVN_DAV_OPTIONS_HEADER
,
284 SVN_DAV_OPTION_LOCK_STEAL
);
285 if (SVN_IS_VALID_REVNUM(current_rev
))
286 svn_ra_neon__set_header(extra_headers
, SVN_DAV_VERSION_NAME_HEADER
,
287 apr_psprintf(req
->pool
, "%ld", current_rev
));
289 err
= svn_ra_neon__request_dispatch(&code
, req
, extra_headers
, body
->data
,
294 /*###FIXME: we never verified whether we have received back the type
295 of lock we requested: was it shared/exclusive? was it write/otherwise?
296 How many did we get back? Only one? */
297 err
= lock_from_baton(lock
, req
, fs_path
.data
, lrb
, pool
);
300 svn_ra_neon__request_destroy(req
);
305 svn_ra_neon__lock(svn_ra_session_t
*session
,
306 apr_hash_t
*path_revs
,
309 svn_ra_lock_callback_t lock_func
,
313 apr_hash_index_t
*hi
;
314 apr_pool_t
*iterpool
= svn_pool_create(pool
);
315 svn_ra_neon__session_t
*ras
= session
->priv
;
316 svn_error_t
*ret_err
= NULL
;
318 /* ### TODO for issue 2263: Send all the locks over the wire at once. This
319 loop is just a temporary shim. */
320 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
326 svn_revnum_t
*revnum
;
327 svn_error_t
*err
, *callback_err
= NULL
;
329 svn_pool_clear(iterpool
);
331 apr_hash_this(hi
, &key
, NULL
, &val
);
335 err
= do_lock(&lock
, session
, path
, comment
, force
, *revnum
, iterpool
);
337 if (err
&& !SVN_ERR_IS_LOCK_ERROR(err
))
344 callback_err
= lock_func(lock_baton
, path
, TRUE
, err
? NULL
: lock
,
347 svn_error_clear(err
);
351 ret_err
= callback_err
;
357 svn_pool_destroy(iterpool
);
360 return svn_ra_neon__maybe_store_auth_info_after_result(ret_err
, ras
, pool
);
364 /* ###TODO for issue 2263: Send all lock tokens to the server at once. */
366 do_unlock(svn_ra_session_t
*session
,
372 svn_ra_neon__session_t
*ras
= session
->priv
;
374 const char *url_path
;
377 apr_hash_t
*extra_headers
= apr_hash_make(pool
);
379 /* Make a neon lock structure containing token and full URL to unlock. */
380 url
= svn_path_url_add_component(ras
->url
->data
, path
, pool
);
381 if (ne_uri_parse(url
, &uri
) != 0)
384 return svn_error_createf(SVN_ERR_RA_DAV_CREATING_REQUEST
, NULL
,
385 _("Failed to parse URI '%s'"), url
);
388 url_path
= apr_pstrdup(pool
, uri
.path
);
390 /* In the case of 'force', we might not have a token at all.
391 Unfortunately, mod_dav insists on having a valid token for
392 UNLOCK requests. That means we need to fetch the token. */
397 SVN_ERR(svn_ra_neon__get_lock(session
, &lock
, path
, pool
));
399 return svn_error_createf(SVN_ERR_RA_NOT_LOCKED
, NULL
,
400 _("'%s' is not locked in the repository"),
407 apr_hash_set(extra_headers
, "Lock-Token", APR_HASH_KEY_STRING
,
408 apr_psprintf(pool
, "<%s>", token
));
410 apr_hash_set(extra_headers
, SVN_DAV_OPTIONS_HEADER
, APR_HASH_KEY_STRING
,
411 SVN_DAV_OPTION_LOCK_BREAK
);
413 return svn_ra_neon__simple_request(NULL
, ras
, "UNLOCK", url_path
,
414 extra_headers
, NULL
, 204, 0, pool
);
419 svn_ra_neon__unlock(svn_ra_session_t
*session
,
420 apr_hash_t
*path_tokens
,
422 svn_ra_lock_callback_t lock_func
,
426 apr_hash_index_t
*hi
;
427 apr_pool_t
*iterpool
= svn_pool_create(pool
);
428 svn_ra_neon__session_t
*ras
= session
->priv
;
429 svn_error_t
*ret_err
= NULL
;
431 /* ### TODO for issue 2263: Send all the lock tokens over the wire at once.
432 This loop is just a temporary shim. */
433 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
439 svn_error_t
*err
, *callback_err
= NULL
;
441 svn_pool_clear(iterpool
);
443 apr_hash_this(hi
, &key
, NULL
, &val
);
445 /* Since we can't store NULL values in a hash, we turn "" to
447 if (strcmp(val
, "") != 0)
452 err
= do_unlock(session
, path
, token
, force
, iterpool
);
454 if (err
&& !SVN_ERR_IS_UNLOCK_ERROR(err
))
461 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
, iterpool
);
463 svn_error_clear(err
);
467 ret_err
= callback_err
;
472 svn_pool_destroy(iterpool
);
475 return svn_ra_neon__maybe_store_auth_info_after_result(ret_err
, ras
, pool
);
480 svn_ra_neon__get_lock_internal(svn_ra_neon__session_t
*ras
,
486 svn_string_t fs_path
;
489 lock_baton_t
*lrb
= apr_pcalloc(pool
, sizeof(*lrb
));
490 svn_ra_neon__request_t
*req
;
491 ne_xml_parser
*lck_parser
;
492 apr_hash_t
*extra_headers
;
493 static const char *body
=
494 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" DEBUG_CR
495 "<D:propfind xmlns:D=\"DAV:\">" DEBUG_CR
497 " <D:lockdiscovery />" DEBUG_CR
498 " </D:prop>" DEBUG_CR
501 /* To begin, we convert the incoming path into an absolute fs-path. */
502 url
= svn_path_url_add_component(ras
->url
->data
, path
, pool
);
504 err
= svn_ra_neon__get_baseline_info(NULL
, NULL
, &fs_path
, NULL
, ras
,
505 url
, SVN_INVALID_REVNUM
, pool
);
506 SVN_ERR(svn_ra_neon__maybe_store_auth_info_after_result(err
, ras
, pool
));
508 ne_uri_parse(url
, &uri
);
509 url
= apr_pstrdup(pool
, uri
.path
);
512 req
= svn_ra_neon__request_create(ras
, "PROPFIND", url
, pool
);
515 lrb
->xml_table
= lock_elements
;
516 lck_parser
= svn_ra_neon__xml_parser_create
517 (req
, ne_accept_207
, lock_start_element
, lock_cdata
, lock_end_element
, lrb
);
519 extra_headers
= apr_hash_make(req
->pool
);
520 svn_ra_neon__set_header(extra_headers
, "Depth", "0");
521 svn_ra_neon__set_header(extra_headers
, "Content-Type",
522 "text/xml; charset=\"utf-8\"");
524 err
= svn_ra_neon__request_dispatch(NULL
, req
, extra_headers
, body
,
528 err
= svn_error_quick_wrap(err
, _("Failed to fetch lock information"));
532 /*###FIXME We assume here we only got one lock response. The WebDAV
533 spec makes no such guarantees. How to make sure we grab the one we need? */
534 err
= lock_from_baton(lock
, req
, fs_path
.data
, lrb
, pool
);
537 svn_ra_neon__request_destroy(req
);
543 svn_ra_neon__get_lock(svn_ra_session_t
*session
,
548 return svn_ra_neon__get_lock_internal(session
->priv
, lock
, path
, pool
);