Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_ra_neon / get_locks.c
bloba4ab25c68306aa744eba3bbad0dae1c6df2be808
1 /*
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>
27 #include <apr_xml.h>
29 #include <ne_basic.h>
31 #include "svn_error.h"
32 #include "svn_pools.h"
33 #include "svn_base64.h"
34 #include "svn_ra.h"
35 #include "../libsvn_ra/ra_loader.h"
36 #include "svn_path.h"
37 #include "svn_xml.h"
38 #include "svn_dav.h"
39 #include "svn_time.h"
41 #include "private/svn_dav_protocol.h"
42 #include "svn_private_config.h"
44 #include "ra_neon.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 },
76 { NULL }
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...>
91 * <S:lock>
92 * <S:path>/foo/bar/baz</S:path>
93 * <S:token>opaquelocktoken:706689a6-8cef-0310-9809-fb7545cbd44e
94 * </S:token>
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>
99 * </S:lock>
100 * ...
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
113 * like the request.
117 /* Context for parsing server's response. */
118 typedef struct {
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 */
128 } get_locks_baton_t;
132 /* This implements the `svn_ra_neon__startelm_cb_t' prototype. */
133 static svn_error_t *
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. */
143 if (!elm)
145 *elem = NE_XML_DECLINE;
146 return SVN_NO_ERROR;
149 if (elm->id == ELEM_lock)
151 if (parent_state != ELEM_get_locks_report)
152 return UNEXPECTED_ELEMENT(ns, ln);
153 else
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);
172 if (encoding)
173 baton->encoding = apr_pstrdup(baton->scratchpool, encoding);
176 *elem = elm->id;
178 return SVN_NO_ERROR;
182 /* This implements the `svn_ra_svn__cdata_cb_t' prototype. */
183 static svn_error_t *
184 getlocks_cdata_handler(void *userdata, int state,
185 const char *cdata, size_t len)
187 get_locks_baton_t *baton = userdata;
189 switch(state)
191 case ELEM_lock_path:
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);
199 break;
202 return SVN_NO_ERROR;
207 /* This implements the `svn_ra_neon__endelm_cb_t' prototype. */
208 static svn_error_t *
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. */
218 if (elm == NULL)
219 return SVN_NO_ERROR;
221 switch (elm->id)
223 case ELEM_lock:
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);
235 break;
237 case ELEM_lock_path:
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);
245 break;
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);
255 break;
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);
264 break;
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);
273 break;
275 case ELEM_lock_owner:
276 case ELEM_lock_comment:
278 const char *final_val;
280 if (baton->encoding)
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,
289 baton->scratchpool);
290 decoded_val = svn_base64_decode_string(encoded_val,
291 baton->scratchpool);
292 final_val = decoded_val->data;
294 else
295 return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA,
296 NULL,
297 _("Got unrecognized encoding '%s'"),
298 baton->encoding);
300 baton->encoding = NULL;
302 else
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);
316 break;
320 default:
321 break;
324 return SVN_NO_ERROR;
329 svn_error_t *
330 svn_ra_neon__get_locks(svn_ra_session_t *session,
331 apr_hash_t **locks,
332 const char *path,
333 apr_pool_t *pool)
335 svn_ra_neon__session_t *ras = session->priv;
336 const char *body, *url;
337 svn_error_t *err;
338 int status_code = 0;
339 get_locks_baton_t baton;
341 baton.lock_hash = apr_hash_make(pool);
342 baton.pool = 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 "\" "
351 "xmlns:D=\"DAV:\">"
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,
361 body, NULL, NULL,
362 getlocks_start_element,
363 getlocks_cdata_handler,
364 getlocks_end_element,
365 &baton,
366 NULL, /* extra headers */
367 &status_code,
368 FALSE,
369 pool);
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;
377 return SVN_NO_ERROR;
380 /* ### Should svn_ra_neon__parsed_request() take care of storing auth
381 ### info itself? */
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"));
394 else if (err)
395 return err;
397 *locks = baton.lock_hash;
398 return SVN_NO_ERROR;