2 * get_locks.c : RA get-locks API implementation
4 * ====================================================================
5 * Copyright (c) 2004-2007 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
22 #include <apr_want.h> /* for strcmp() */
24 #include <apr_pools.h>
25 #include <apr_tables.h>
26 #include <apr_strings.h>
31 #include "svn_error.h"
32 #include "svn_pools.h"
33 #include "svn_base64.h"
35 #include "../libsvn_ra/ra_loader.h"
41 #include "private/svn_dav_protocol.h"
42 #include "svn_private_config.h"
46 /* -------------------------------------------------------------------------
48 ** GET-LOCKS REPORT HANDLING
50 ** DeltaV provides a mechanism for fetching a list of locks below a
51 ** path, but it's often unscalable. It requires doing a PROPFIND of
52 ** depth infinity, looking for the 'DAV:lockdiscovery' prop on every
53 ** resource. But depth-infinity propfinds can sometimes behave like a
54 ** DoS attack, and mod_dav even disables them by default!
56 ** So we send a custom 'get-locks' REPORT on a public URI... which is
57 ** fine, since all lock queries are always against HEAD anyway. The
58 ** response is a just a list of svn_lock_t's. (Generic DAV clients,
59 ** of course, are free to do infinite PROPFINDs as they wish, assuming
60 ** the server allows it.)
63 /* Elements used in a get-locks-report response */
64 static const svn_ra_neon__xml_elm_t getlocks_report_elements
[] =
66 { SVN_XML_NAMESPACE
, "get-locks-report", ELEM_get_locks_report
, 0 },
67 { SVN_XML_NAMESPACE
, "lock", ELEM_lock
, 0},
68 { SVN_XML_NAMESPACE
, "path", ELEM_lock_path
, SVN_RA_NEON__XML_CDATA
},
69 { SVN_XML_NAMESPACE
, "token", ELEM_lock_token
, SVN_RA_NEON__XML_CDATA
},
70 { SVN_XML_NAMESPACE
, "owner", ELEM_lock_owner
, SVN_RA_NEON__XML_CDATA
},
71 { SVN_XML_NAMESPACE
, "comment", ELEM_lock_comment
, SVN_RA_NEON__XML_CDATA
},
72 { SVN_XML_NAMESPACE
, SVN_DAV__CREATIONDATE
,
73 ELEM_lock_creationdate
, SVN_RA_NEON__XML_CDATA
},
74 { SVN_XML_NAMESPACE
, "expirationdate",
75 ELEM_lock_expirationdate
, SVN_RA_NEON__XML_CDATA
},
80 * The get-locks-report xml request body is super-simple.
81 * The server doesn't need anything but the URI in the REPORT request line.
83 * <S:get-locks-report xmlns...>
84 * </S:get-locks-report>
86 * The get-locks-report xml response is just a list of svn_lock_t's
87 * that exist at or "below" the request URI. (The server runs
88 * svn_repos_fs_get_locks()).
90 * <S:get-locks-report xmlns...>
92 * <S:path>/foo/bar/baz</S:path>
93 * <S:token>opaquelocktoken:706689a6-8cef-0310-9809-fb7545cbd44e
95 * <S:owner>fred</S:owner>
96 * <S:comment encoding="base64">ET39IGCB93LL4M</S:comment>
97 * <S:creationdate>2005-02-07T14:17:08Z</S:creationdate>
98 * <S:expirationdate>2005-02-08T14:17:08Z</S:expirationdate>
101 * </S:get-locks-report>
104 * The <path> and <token> and date-element cdata is xml-escaped by mod_dav_svn.
106 * The <owner> and <comment> cdata is always xml-escaped, but
107 * possibly also base64-encoded if necessary, as indicated by the
108 * encoding attribute.
110 * The absence of <expirationdate> means that there's no expiration.
112 * If there are no locks to return, then the response will look just
117 /* Context for parsing server's response. */
119 svn_lock_t
*current_lock
; /* the lock being constructed */
120 svn_stringbuf_t
*cdata_accum
; /* a place to accumulate cdata */
121 const char *encoding
; /* normally NULL, else the value of
122 'encoding' attribute on cdata's tag.*/
123 apr_hash_t
*lock_hash
; /* the final hash returned */
125 apr_pool_t
*scratchpool
; /* temporary stuff goes in here */
126 apr_pool_t
*pool
; /* permanent stuff goes in here */
132 /* This implements the `svn_ra_neon__startelm_cb_t' prototype. */
134 getlocks_start_element(int *elem
, void *userdata
, int parent_state
,
135 const char *ns
, const char *ln
, const char **atts
)
137 get_locks_baton_t
*baton
= userdata
;
138 const svn_ra_neon__xml_elm_t
*elm
;
140 elm
= svn_ra_neon__lookup_xml_elem(getlocks_report_elements
, ns
, ln
);
142 /* Just skip unknown elements. */
145 *elem
= NE_XML_DECLINE
;
149 if (elm
->id
== ELEM_lock
)
151 if (parent_state
!= ELEM_get_locks_report
)
152 return UNEXPECTED_ELEMENT(ns
, ln
);
154 /* allocate a new svn_lock_t in the permanent pool */
155 baton
->current_lock
= svn_lock_create(baton
->pool
);
158 else if (elm
->id
== ELEM_lock_path
159 || elm
->id
== ELEM_lock_token
160 || elm
->id
== ELEM_lock_owner
161 || elm
->id
== ELEM_lock_comment
162 || elm
->id
== ELEM_lock_creationdate
163 || elm
->id
== ELEM_lock_expirationdate
)
165 const char *encoding
;
167 if (parent_state
!= ELEM_lock
)
168 return UNEXPECTED_ELEMENT(ns
, ln
);
170 /* look for any incoming encodings on these elements. */
171 encoding
= svn_xml_get_attr_value("encoding", atts
);
173 baton
->encoding
= apr_pstrdup(baton
->scratchpool
, encoding
);
182 /* This implements the `svn_ra_svn__cdata_cb_t' prototype. */
184 getlocks_cdata_handler(void *userdata
, int state
,
185 const char *cdata
, size_t len
)
187 get_locks_baton_t
*baton
= userdata
;
192 case ELEM_lock_token
:
193 case ELEM_lock_owner
:
194 case ELEM_lock_comment
:
195 case ELEM_lock_creationdate
:
196 case ELEM_lock_expirationdate
:
197 /* accumulate cdata in the scratchpool. */
198 svn_stringbuf_appendbytes(baton
->cdata_accum
, cdata
, len
);
207 /* This implements the `svn_ra_neon__endelm_cb_t' prototype. */
209 getlocks_end_element(void *userdata
, int state
,
210 const char *ns
, const char *ln
)
212 get_locks_baton_t
*baton
= userdata
;
213 const svn_ra_neon__xml_elm_t
*elm
;
215 elm
= svn_ra_neon__lookup_xml_elem(getlocks_report_elements
, ns
, ln
);
217 /* Just skip unknown elements. */
224 /* is the final svn_lock_t valid? all fields must be present
225 except for 'comment' and 'expiration_date'. */
226 if ((! baton
->current_lock
->path
)
227 || (! baton
->current_lock
->token
)
228 || (! baton
->current_lock
->owner
)
229 || (! baton
->current_lock
->creation_date
))
230 SVN_ERR(svn_error_create(SVN_ERR_RA_DAV_MALFORMED_DATA
, NULL
,
231 _("Incomplete lock data returned")));
233 apr_hash_set(baton
->lock_hash
, baton
->current_lock
->path
,
234 APR_HASH_KEY_STRING
, baton
->current_lock
);
238 /* neon has already xml-unescaped the cdata for us. */
239 baton
->current_lock
->path
= apr_pstrmemdup(baton
->pool
,
240 baton
->cdata_accum
->data
,
241 baton
->cdata_accum
->len
);
242 /* clean up the accumulator. */
243 svn_stringbuf_setempty(baton
->cdata_accum
);
244 svn_pool_clear(baton
->scratchpool
);
247 case ELEM_lock_token
:
248 /* neon has already xml-unescaped the cdata for us. */
249 baton
->current_lock
->token
= apr_pstrmemdup(baton
->pool
,
250 baton
->cdata_accum
->data
,
251 baton
->cdata_accum
->len
);
252 /* clean up the accumulator. */
253 svn_stringbuf_setempty(baton
->cdata_accum
);
254 svn_pool_clear(baton
->scratchpool
);
257 case ELEM_lock_creationdate
:
258 SVN_ERR(svn_time_from_cstring(&(baton
->current_lock
->creation_date
),
259 baton
->cdata_accum
->data
,
260 baton
->scratchpool
));
261 /* clean up the accumulator. */
262 svn_stringbuf_setempty(baton
->cdata_accum
);
263 svn_pool_clear(baton
->scratchpool
);
266 case ELEM_lock_expirationdate
:
267 SVN_ERR(svn_time_from_cstring(&(baton
->current_lock
->expiration_date
),
268 baton
->cdata_accum
->data
,
269 baton
->scratchpool
));
270 /* clean up the accumulator. */
271 svn_stringbuf_setempty(baton
->cdata_accum
);
272 svn_pool_clear(baton
->scratchpool
);
275 case ELEM_lock_owner
:
276 case ELEM_lock_comment
:
278 const char *final_val
;
282 /* Possibly recognize other encodings someday. */
283 if (strcmp(baton
->encoding
, "base64") == 0)
285 svn_string_t
*encoded_val
;
286 const svn_string_t
*decoded_val
;
288 encoded_val
= svn_string_create_from_buf(baton
->cdata_accum
,
290 decoded_val
= svn_base64_decode_string(encoded_val
,
292 final_val
= decoded_val
->data
;
295 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA
,
297 _("Got unrecognized encoding '%s'"),
300 baton
->encoding
= NULL
;
304 /* neon has already xml-unescaped the cdata for us. */
305 final_val
= baton
->cdata_accum
->data
;
308 if (elm
->id
== ELEM_lock_owner
)
309 baton
->current_lock
->owner
= apr_pstrdup(baton
->pool
, final_val
);
310 if (elm
->id
== ELEM_lock_comment
)
311 baton
->current_lock
->comment
= apr_pstrdup(baton
->pool
, final_val
);
313 /* clean up the accumulator. */
314 svn_stringbuf_setempty(baton
->cdata_accum
);
315 svn_pool_clear(baton
->scratchpool
);
330 svn_ra_neon__get_locks(svn_ra_session_t
*session
,
335 svn_ra_neon__session_t
*ras
= session
->priv
;
336 const char *body
, *url
;
339 get_locks_baton_t baton
;
341 baton
.lock_hash
= apr_hash_make(pool
);
343 baton
.scratchpool
= svn_pool_create(pool
);
344 baton
.current_lock
= NULL
;
345 baton
.encoding
= NULL
;
346 baton
.cdata_accum
= svn_stringbuf_create("", pool
);
348 body
= apr_psprintf(pool
,
349 "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
350 "<S:get-locks-report xmlns:S=\"" SVN_XML_NAMESPACE
"\" "
352 "</S:get-locks-report>");
355 /* We always run the report on the 'public' URL, which represents
356 HEAD anyway. If the path doesn't exist in HEAD, then there can't
357 possibly be a lock, so we just return no locks. */
358 url
= svn_path_url_add_component(ras
->url
->data
, path
, pool
);
360 err
= svn_ra_neon__parsed_request(ras
, "REPORT", url
,
362 getlocks_start_element
,
363 getlocks_cdata_handler
,
364 getlocks_end_element
,
366 NULL
, /* extra headers */
371 svn_pool_destroy(baton
.scratchpool
);
373 if (err
&& err
->apr_err
== SVN_ERR_RA_DAV_PATH_NOT_FOUND
)
375 svn_error_clear(err
);
376 *locks
= baton
.lock_hash
;
380 /* ### Should svn_ra_neon__parsed_request() take care of storing auth
382 err
= svn_ra_neon__maybe_store_auth_info_after_result(err
, ras
, pool
);
384 /* Map status 501: Method Not Implemented to our not implemented error.
385 1.0.x servers and older don't support this report. */
386 if (status_code
== 501)
387 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, err
,
388 _("Server does not support locking features"));
390 if (err
&& err
->apr_err
== SVN_ERR_UNSUPPORTED_FEATURE
)
391 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, err
,
392 _("Server does not support locking features"));
397 *locks
= baton
.lock_hash
;