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
;
243 /* To begin, we convert the incoming path into an absolute fs-path. */
244 url
= svn_path_url_add_component(ras
->url
->data
, path
, pool
);
245 SVN_ERR(svn_ra_neon__get_baseline_info(NULL
, NULL
, &fs_path
, NULL
, ras
,
246 url
, SVN_INVALID_REVNUM
, pool
));
248 if (ne_uri_parse(url
, &uri
) != 0)
251 return svn_error_createf(SVN_ERR_RA_DAV_CREATING_REQUEST
, NULL
,
252 _("Failed to parse URI '%s'"), url
);
255 req
= svn_ra_neon__request_create(ras
, "LOCK", uri
.path
, pool
);
259 lrb
->xml_table
= &(lock_elements
[3]);
260 lck_parser
= svn_ra_neon__xml_parser_create
262 lock_start_element
, lock_cdata
, lock_end_element
, lrb
);
264 body
= svn_stringbuf_createf
266 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" DEBUG_CR
267 "<D:lockinfo xmlns:D=\"DAV:\">" DEBUG_CR
268 " <D:lockscope><D:exclusive /></D:lockscope>" DEBUG_CR
269 " <D:locktype><D:write /></D:locktype>" DEBUG_CR
270 "%s" /* maybe owner */
273 ? apr_psprintf(req
->pool
, " <D:owner>%s</D:owner>" DEBUG_CR
, comment
)
276 extra_headers
= apr_hash_make(req
->pool
);
277 svn_ra_neon__set_header(extra_headers
, "Depth", "0");
278 svn_ra_neon__set_header(extra_headers
, "Timeout", "Infinite");
279 svn_ra_neon__set_header(extra_headers
, "Content-Type",
280 "text/xml; charset=\"utf-8\"");
282 svn_ra_neon__set_header(extra_headers
, SVN_DAV_OPTIONS_HEADER
,
283 SVN_DAV_OPTION_LOCK_STEAL
);
284 if (SVN_IS_VALID_REVNUM(current_rev
))
285 svn_ra_neon__set_header(extra_headers
, SVN_DAV_VERSION_NAME_HEADER
,
286 apr_psprintf(req
->pool
, "%ld", current_rev
));
288 SVN_ERR(svn_ra_neon__request_dispatch(&code
, req
, extra_headers
, body
->data
,
291 /*###FIXME: we never verified whether we have received back the type
292 of lock we requested: was it shared/exclusive? was it write/otherwise?
293 How many did we get back? Only one? */
294 SVN_ERR(lock_from_baton(lock
, req
, fs_path
.data
, lrb
, pool
));
296 svn_ra_neon__request_destroy(req
);
302 svn_ra_neon__lock(svn_ra_session_t
*session
,
303 apr_hash_t
*path_revs
,
306 svn_ra_lock_callback_t lock_func
,
310 apr_hash_index_t
*hi
;
311 apr_pool_t
*iterpool
= svn_pool_create(pool
);
312 svn_ra_neon__session_t
*ras
= session
->priv
;
313 svn_error_t
*ret_err
= NULL
;
315 /* ### TODO for issue 2263: Send all the locks over the wire at once. This
316 loop is just a temporary shim. */
317 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
323 svn_revnum_t
*revnum
;
324 svn_error_t
*err
, *callback_err
= NULL
;
326 svn_pool_clear(iterpool
);
328 apr_hash_this(hi
, &key
, NULL
, &val
);
332 err
= do_lock(&lock
, session
, path
, comment
, force
, *revnum
, iterpool
);
334 if (err
&& !SVN_ERR_IS_LOCK_ERROR(err
))
341 callback_err
= lock_func(lock_baton
, path
, TRUE
, err
? NULL
: lock
,
344 svn_error_clear(err
);
348 ret_err
= callback_err
;
354 svn_pool_destroy(iterpool
);
357 return svn_ra_neon__maybe_store_auth_info_after_result(ret_err
, ras
, pool
);
361 /* ###TODO for issue 2263: Send all lock tokens to the server at once. */
363 do_unlock(svn_ra_session_t
*session
,
369 svn_ra_neon__session_t
*ras
= session
->priv
;
371 const char *url_path
;
374 apr_hash_t
*extra_headers
= apr_hash_make(pool
);
376 /* Make a neon lock structure containing token and full URL to unlock. */
377 url
= svn_path_url_add_component(ras
->url
->data
, path
, pool
);
378 if (ne_uri_parse(url
, &uri
) != 0)
381 return svn_error_createf(SVN_ERR_RA_DAV_CREATING_REQUEST
, NULL
,
382 _("Failed to parse URI '%s'"), url
);
385 url_path
= apr_pstrdup(pool
, uri
.path
);
387 /* In the case of 'force', we might not have a token at all.
388 Unfortunately, mod_dav insists on having a valid token for
389 UNLOCK requests. That means we need to fetch the token. */
394 SVN_ERR(svn_ra_neon__get_lock(session
, &lock
, path
, pool
));
396 return svn_error_createf(SVN_ERR_RA_NOT_LOCKED
, NULL
,
397 _("'%s' is not locked in the repository"),
404 apr_hash_set(extra_headers
, "Lock-Token", APR_HASH_KEY_STRING
,
405 apr_psprintf(pool
, "<%s>", token
));
407 apr_hash_set(extra_headers
, SVN_DAV_OPTIONS_HEADER
, APR_HASH_KEY_STRING
,
408 SVN_DAV_OPTION_LOCK_BREAK
);
410 return svn_ra_neon__simple_request(NULL
, ras
, "UNLOCK", url_path
,
411 extra_headers
, NULL
, 204, 0, pool
);
416 svn_ra_neon__unlock(svn_ra_session_t
*session
,
417 apr_hash_t
*path_tokens
,
419 svn_ra_lock_callback_t lock_func
,
423 apr_hash_index_t
*hi
;
424 apr_pool_t
*iterpool
= svn_pool_create(pool
);
425 svn_ra_neon__session_t
*ras
= session
->priv
;
426 svn_error_t
*ret_err
= NULL
;
428 /* ### TODO for issue 2263: Send all the lock tokens over the wire at once.
429 This loop is just a temporary shim. */
430 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
436 svn_error_t
*err
, *callback_err
= NULL
;
438 svn_pool_clear(iterpool
);
440 apr_hash_this(hi
, &key
, NULL
, &val
);
442 /* Since we can't store NULL values in a hash, we turn "" to
444 if (strcmp(val
, "") != 0)
449 err
= do_unlock(session
, path
, token
, force
, iterpool
);
451 if (err
&& !SVN_ERR_IS_UNLOCK_ERROR(err
))
458 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
, iterpool
);
460 svn_error_clear(err
);
464 ret_err
= callback_err
;
469 svn_pool_destroy(iterpool
);
472 return svn_ra_neon__maybe_store_auth_info_after_result(ret_err
, ras
, pool
);
477 svn_ra_neon__get_lock(svn_ra_session_t
*session
,
483 svn_string_t fs_path
;
486 svn_ra_neon__session_t
*ras
= session
->priv
;
487 lock_baton_t
*lrb
= apr_pcalloc(pool
, sizeof(*lrb
));
488 svn_ra_neon__request_t
*req
;
489 ne_xml_parser
*lck_parser
;
490 apr_hash_t
*extra_headers
;
491 static const char *body
=
492 "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" DEBUG_CR
493 "<D:propfind xmlns:D=\"DAV:\">" DEBUG_CR
495 " <D:lockdiscovery />" DEBUG_CR
496 " </D:prop>" DEBUG_CR
499 /* To begin, we convert the incoming path into an absolute fs-path. */
500 url
= svn_path_url_add_component(ras
->url
->data
, path
, pool
);
502 err
= svn_ra_neon__get_baseline_info(NULL
, NULL
, &fs_path
, NULL
, ras
,
503 url
, SVN_INVALID_REVNUM
, pool
);
504 SVN_ERR(svn_ra_neon__maybe_store_auth_info_after_result(err
, ras
, pool
));
506 ne_uri_parse(url
, &uri
);
507 url
= apr_pstrdup(pool
, uri
.path
);
510 req
= svn_ra_neon__request_create(ras
, "PROPFIND", url
, pool
);
513 lrb
->xml_table
= lock_elements
;
514 lck_parser
= svn_ra_neon__xml_parser_create
515 (req
, ne_accept_207
, lock_start_element
, lock_cdata
, lock_end_element
, lrb
);
517 extra_headers
= apr_hash_make(req
->pool
);
518 svn_ra_neon__set_header(extra_headers
, "Depth", "0");
519 svn_ra_neon__set_header(extra_headers
, "Content-Type",
520 "text/xml; charset=\"utf-8\"");
522 SVN_ERR_W(svn_ra_neon__request_dispatch(NULL
, req
, extra_headers
, body
,
524 _("Failed to fetch lock information"));
525 /*###FIXME We assume here we only got one lock response. The WebDAV
526 spec makes no such guarantees. How to make sure we grab the one we need? */
527 SVN_ERR(lock_from_baton(lock
, req
, fs_path
.data
, lrb
, pool
));