get_lock.c revision 299742
1/*
2 * get_lock.c :  obtain single lock information functions for ra_serf
3 *
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
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#include <apr_uri.h>
27#include <serf.h>
28
29#include "svn_dav.h"
30#include "svn_pools.h"
31#include "svn_ra.h"
32
33#include "../libsvn_ra/ra_loader.h"
34#include "svn_config.h"
35#include "svn_path.h"
36#include "svn_time.h"
37#include "svn_private_config.h"
38
39#include "ra_serf.h"
40
41
42/*
43 * This enum represents the current state of our XML parsing for a REPORT.
44 */
45enum {
46  INITIAL = 0,
47  MULTISTATUS,
48  RESPONSE,
49  PROPSTAT,
50  PROP,
51  LOCK_DISCOVERY,
52  ACTIVE_LOCK,
53  LOCK_TYPE,
54  LOCK_SCOPE,
55  DEPTH,
56  TIMEOUT,
57  LOCK_TOKEN,
58  OWNER,
59  HREF
60};
61
62typedef struct lock_info_t {
63  apr_pool_t *pool;
64
65  const char *path;
66
67  svn_lock_t *lock;
68
69  svn_boolean_t read_headers;
70
71  svn_ra_serf__handler_t *handler;
72
73  /* The expat handler. We wrap this to do a bit more work.  */
74  svn_ra_serf__response_handler_t inner_handler;
75  void *inner_baton;
76
77} lock_info_t;
78
79#define D_ "DAV:"
80#define S_ SVN_XML_NAMESPACE
81static const svn_ra_serf__xml_transition_t locks_ttable[] = {
82  /* The INITIAL state can transition into D:prop (LOCK) or
83     to D:multistatus (PROPFIND)  */
84  { INITIAL, D_, "multistatus", MULTISTATUS,
85    FALSE, { NULL }, FALSE },
86
87  { MULTISTATUS, D_, "response", RESPONSE,
88    FALSE, { NULL }, FALSE },
89
90  { RESPONSE, D_, "propstat", PROPSTAT,
91    FALSE, { NULL }, FALSE },
92
93  { PROPSTAT, D_, "prop", PROP,
94    FALSE, { NULL }, FALSE },
95
96  { PROP, D_, "lockdiscovery", LOCK_DISCOVERY,
97    FALSE, { NULL }, FALSE },
98
99  { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK,
100    FALSE, { NULL }, FALSE },
101
102#if 0
103  /* ### we don't really need to parse locktype/lockscope. we know what
104     ### the values are going to be. we *could* validate that the only
105     ### possible children are D:write and D:exclusive. we'd need to
106     ### modify the state transition to tell us about all children
107     ### (ie. maybe support "*" for the name) and then validate. but it
108     ### just isn't important to validate, so disable this for now... */
109
110  { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE,
111    FALSE, { NULL }, FALSE },
112
113  { LOCK_TYPE, D_, "write", WRITE,
114    FALSE, { NULL }, TRUE },
115
116  { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE,
117    FALSE, { NULL }, FALSE },
118
119  { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE,
120    FALSE, { NULL }, TRUE },
121#endif /* 0  */
122
123  { ACTIVE_LOCK, D_, "timeout", TIMEOUT,
124    TRUE, { NULL }, TRUE },
125
126  { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN,
127    FALSE, { NULL }, FALSE },
128
129  { LOCK_TOKEN, D_, "href", HREF,
130    TRUE, { NULL }, TRUE },
131
132  { ACTIVE_LOCK, D_, "owner", OWNER,
133    TRUE, { NULL }, TRUE },
134
135  /* ACTIVE_LOCK has a D:depth child, but we can ignore that.  */
136
137  { 0 }
138};
139
140static const int locks_expected_status[] = {
141  207,
142  0
143};
144
145/* Conforms to svn_ra_serf__xml_closed_t  */
146static svn_error_t *
147locks_closed(svn_ra_serf__xml_estate_t *xes,
148             void *baton,
149             int leaving_state,
150             const svn_string_t *cdata,
151             apr_hash_t *attrs,
152             apr_pool_t *scratch_pool)
153{
154  lock_info_t *lock_ctx = baton;
155
156  if (leaving_state == TIMEOUT)
157    {
158      if (strcasecmp(cdata->data, "Infinite") == 0)
159        lock_ctx->lock->expiration_date = 0;
160      else if (strncasecmp(cdata->data, "Second-", 7) == 0)
161        {
162          unsigned n;
163          SVN_ERR(svn_cstring_atoui(&n, cdata->data+7));
164
165          lock_ctx->lock->expiration_date = apr_time_now() +
166                                            apr_time_from_sec(n);
167        }
168      else
169         return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
170                                  _("Invalid LOCK timeout value '%s'"),
171                                  cdata->data);
172    }
173  else if (leaving_state == HREF)
174    {
175      if (cdata->len)
176        {
177          char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len);
178
179          apr_collapse_spaces(buf, buf);
180          lock_ctx->lock->token = buf;
181        }
182    }
183  else if (leaving_state == OWNER)
184    {
185      if (cdata->len)
186        {
187          lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool,
188                                                   cdata->data, cdata->len);
189        }
190    }
191
192  return SVN_NO_ERROR;
193}
194
195/* Implements svn_ra_serf__response_handler_t */
196static svn_error_t *
197handle_lock(serf_request_t *request,
198            serf_bucket_t *response,
199            void *handler_baton,
200            apr_pool_t *pool)
201{
202  lock_info_t *ctx = handler_baton;
203
204  if (!ctx->read_headers)
205    {
206      serf_bucket_t *headers;
207      const char *val;
208
209      headers = serf_bucket_response_get_headers(response);
210
211      val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
212      if (val)
213        {
214          ctx->lock->owner = apr_pstrdup(ctx->pool, val);
215        }
216
217      val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
218      if (val)
219        {
220          SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
221                                        ctx->pool));
222        }
223
224      ctx->read_headers = TRUE;
225    }
226
227  return ctx->inner_handler(request, response, ctx->inner_baton, pool);
228}
229
230/* Implements svn_ra_serf__request_body_delegate_t */
231static svn_error_t *
232create_getlock_body(serf_bucket_t **body_bkt,
233                    void *baton,
234                    serf_bucket_alloc_t *alloc,
235                    apr_pool_t *pool /* request pool */,
236                    apr_pool_t *scratch_pool)
237{
238  serf_bucket_t *buckets;
239
240  buckets = serf_bucket_aggregate_create(alloc);
241
242  svn_ra_serf__add_xml_header_buckets(buckets, alloc);
243  svn_ra_serf__add_open_tag_buckets(buckets, alloc, "propfind",
244                                    "xmlns", "DAV:",
245                                    SVN_VA_NULL);
246  svn_ra_serf__add_open_tag_buckets(buckets, alloc, "prop", SVN_VA_NULL);
247  svn_ra_serf__add_empty_tag_buckets(buckets, alloc,
248                                     "lockdiscovery", SVN_VA_NULL);
249  svn_ra_serf__add_close_tag_buckets(buckets, alloc, "prop");
250  svn_ra_serf__add_close_tag_buckets(buckets, alloc, "propfind");
251
252  *body_bkt = buckets;
253  return SVN_NO_ERROR;
254}
255
256static svn_error_t*
257setup_getlock_headers(serf_bucket_t *headers,
258                      void *baton,
259                      apr_pool_t *pool /* request pool */,
260                      apr_pool_t *scratch_pool)
261{
262  serf_bucket_headers_setn(headers, "Depth", "0");
263
264  return SVN_NO_ERROR;
265}
266
267svn_error_t *
268svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
269                      svn_lock_t **lock,
270                      const char *path,
271                      apr_pool_t *result_pool)
272{
273  svn_ra_serf__session_t *session = ra_session->priv;
274  svn_ra_serf__handler_t *handler;
275  svn_ra_serf__xml_context_t *xmlctx;
276  apr_pool_t *scratch_pool = svn_pool_create(result_pool);
277  lock_info_t *lock_ctx;
278  const char *req_url;
279  svn_error_t *err;
280
281  req_url = svn_path_url_add_component2(session->session_url.path, path,
282                                        scratch_pool);
283
284  lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx));
285  lock_ctx->pool = result_pool;
286  lock_ctx->path = req_url;
287  lock_ctx->lock = svn_lock_create(result_pool);
288  lock_ctx->lock->path = apr_pstrdup(result_pool, path);
289
290  xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
291                                           NULL, locks_closed, NULL,
292                                           lock_ctx,
293                                           scratch_pool);
294  handler = svn_ra_serf__create_expat_handler(session, xmlctx,
295                                              locks_expected_status,
296                                              scratch_pool);
297
298  handler->method = "PROPFIND";
299  handler->path = req_url;
300  handler->body_type = "text/xml";
301
302  handler->body_delegate = create_getlock_body;
303  handler->body_delegate_baton = lock_ctx;
304
305  handler->header_delegate = setup_getlock_headers;
306  handler->header_delegate_baton = lock_ctx;
307
308  handler->no_dav_headers = TRUE;
309
310  lock_ctx->inner_handler = handler->response_handler;
311  lock_ctx->inner_baton = handler->response_baton;
312  handler->response_handler = handle_lock;
313  handler->response_baton = lock_ctx;
314
315  lock_ctx->handler = handler;
316
317  err = svn_ra_serf__context_run_one(handler, scratch_pool);
318
319  if ((err && (handler->sline.code == 500 || handler->sline.code == 501))
320      || svn_error_find_cause(err, SVN_ERR_UNSUPPORTED_FEATURE))
321    return svn_error_trace(
322             svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
323                              _("Server does not support locking features")));
324  else if (svn_error_find_cause(err, SVN_ERR_FS_NOT_FOUND))
325    svn_error_clear(err); /* Behave like the other RA layers */
326  else if (handler->sline.code != 207)
327    return svn_error_trace(svn_ra_serf__unexpected_status(handler));
328
329  if (lock_ctx->lock && lock_ctx->lock->token)
330    *lock = lock_ctx->lock;
331  else
332    *lock = NULL;
333
334  svn_pool_destroy(scratch_pool);
335
336  return SVN_NO_ERROR;
337}
338