]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_ra_serf/locks.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_ra_serf / locks.c
1 /*
2  * locks.c :  entry point for locking RA 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 \f
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 \f
42 /*
43  * This enum represents the current state of our XML parsing for a REPORT.
44  */
45 enum {
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
62 typedef 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 force;
70   svn_revnum_t revision;
71
72   svn_boolean_t read_headers;
73
74   svn_ra_serf__handler_t *handler;
75
76   /* The expat handler. We wrap this to do a bit more work.  */
77   svn_ra_serf__response_handler_t inner_handler;
78   void *inner_baton;
79
80 } lock_info_t;
81
82 #define D_ "DAV:"
83 #define S_ SVN_XML_NAMESPACE
84 static const svn_ra_serf__xml_transition_t locks_ttable[] = {
85   /* The INITIAL state can transition into D:prop (LOCK) or
86      to D:multistatus (PROPFIND)  */
87   { INITIAL, D_, "prop", PROP,
88     FALSE, { NULL }, FALSE },
89   { INITIAL, D_, "multistatus", MULTISTATUS,
90     FALSE, { NULL }, FALSE },
91
92   { MULTISTATUS, D_, "response", RESPONSE,
93     FALSE, { NULL }, FALSE },
94
95   { RESPONSE, D_, "propstat", PROPSTAT,
96     FALSE, { NULL }, FALSE },
97
98   { PROPSTAT, D_, "prop", PROP,
99     FALSE, { NULL }, FALSE },
100
101   { PROP, D_, "lockdiscovery", LOCK_DISCOVERY,
102     FALSE, { NULL }, FALSE },
103
104   { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK,
105     FALSE, { NULL }, FALSE },
106
107 #if 0
108   /* ### we don't really need to parse locktype/lockscope. we know what
109      ### the values are going to be. we *could* validate that the only
110      ### possible children are D:write and D:exclusive. we'd need to
111      ### modify the state transition to tell us about all children
112      ### (ie. maybe support "*" for the name) and then validate. but it
113      ### just isn't important to validate, so disable this for now... */
114
115   { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE,
116     FALSE, { NULL }, FALSE },
117
118   { LOCK_TYPE, D_, "write", WRITE,
119     FALSE, { NULL }, TRUE },
120
121   { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE,
122     FALSE, { NULL }, FALSE },
123
124   { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE,
125     FALSE, { NULL }, TRUE },
126 #endif /* 0  */
127
128   { ACTIVE_LOCK, D_, "timeout", TIMEOUT,
129     TRUE, { NULL }, TRUE },
130
131   { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN,
132     FALSE, { NULL }, FALSE },
133
134   { LOCK_TOKEN, D_, "href", HREF,
135     TRUE, { NULL }, TRUE },
136
137   { ACTIVE_LOCK, D_, "owner", OWNER,
138     TRUE, { NULL }, TRUE },
139
140   /* ACTIVE_LOCK has a D:depth child, but we can ignore that.  */
141
142   { 0 }
143 };
144
145 \f
146 /* Conforms to svn_ra_serf__xml_closed_t  */
147 static svn_error_t *
148 locks_closed(svn_ra_serf__xml_estate_t *xes,
149              void *baton,
150              int leaving_state,
151              const svn_string_t *cdata,
152              apr_hash_t *attrs,
153              apr_pool_t *scratch_pool)
154 {
155   lock_info_t *lock_ctx = baton;
156
157   if (leaving_state == TIMEOUT)
158     {
159       if (strcmp(cdata->data, "Infinite") == 0)
160         lock_ctx->lock->expiration_date = 0;
161       else
162         SVN_ERR(svn_time_from_cstring(&lock_ctx->lock->creation_date,
163                                       cdata->data, lock_ctx->pool));
164     }
165   else if (leaving_state == HREF)
166     {
167       if (cdata->len)
168         {
169           char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len);
170
171           apr_collapse_spaces(buf, buf);
172           lock_ctx->lock->token = buf;
173         }
174     }
175   else if (leaving_state == OWNER)
176     {
177       if (cdata->len)
178         {
179           lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool,
180                                                    cdata->data, cdata->len);
181         }
182     }
183
184   return SVN_NO_ERROR;
185 }
186
187
188 static svn_error_t *
189 set_lock_headers(serf_bucket_t *headers,
190                  void *baton,
191                  apr_pool_t *pool)
192 {
193   lock_info_t *lock_ctx = baton;
194
195   if (lock_ctx->force)
196     {
197       serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
198                               SVN_DAV_OPTION_LOCK_STEAL);
199     }
200
201   if (SVN_IS_VALID_REVNUM(lock_ctx->revision))
202     {
203       serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
204                               apr_ltoa(pool, lock_ctx->revision));
205     }
206
207   return APR_SUCCESS;
208 }
209
210
211 /* Register an error within the session. If something is already there,
212    then it will take precedence.  */
213 static svn_error_t *
214 determine_error(svn_ra_serf__handler_t *handler,
215                 svn_error_t *err)
216 {
217     {
218       apr_status_t errcode;
219
220       if (handler->sline.code == 423)
221         errcode = SVN_ERR_FS_PATH_ALREADY_LOCKED;
222       else if (handler->sline.code == 403)
223         errcode = SVN_ERR_RA_DAV_FORBIDDEN;
224       else
225         return err;
226
227       /* Client-side or server-side error already. Return it.  */
228       if (err != NULL)
229         return err;
230
231       /* The server did not send us a detailed human-readable error.
232          Provide a generic error.  */
233       err = svn_error_createf(errcode, NULL,
234                               _("Lock request failed: %d %s"),
235                               handler->sline.code,
236                               handler->sline.reason);
237     }
238
239   return err;
240 }
241
242
243 /* Implements svn_ra_serf__response_handler_t */
244 static svn_error_t *
245 handle_lock(serf_request_t *request,
246             serf_bucket_t *response,
247             void *handler_baton,
248             apr_pool_t *pool)
249 {
250   lock_info_t *ctx = handler_baton;
251
252   /* 403 (Forbidden) when a lock doesn't exist.
253      423 (Locked) when a lock already exists.  */
254   if (ctx->handler->sline.code == 403
255       || ctx->handler->sline.code == 423)
256     {
257       /* Go look in the body for a server-provided error. This will
258          reset flags for the core handler to Do The Right Thing. We
259          won't be back to this handler again.  */
260       return svn_error_trace(svn_ra_serf__expect_empty_body(
261                                request, response, ctx->handler, pool));
262     }
263
264   if (!ctx->read_headers)
265     {
266       serf_bucket_t *headers;
267       const char *val;
268
269       headers = serf_bucket_response_get_headers(response);
270
271       val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
272       if (val)
273         {
274           ctx->lock->owner = apr_pstrdup(ctx->pool, val);
275         }
276
277       val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
278       if (val)
279         {
280           SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
281                                         ctx->pool));
282         }
283
284       ctx->read_headers = TRUE;
285     }
286
287   return ctx->inner_handler(request, response, ctx->inner_baton, pool);
288 }
289
290 /* Implements svn_ra_serf__request_body_delegate_t */
291 static svn_error_t *
292 create_getlock_body(serf_bucket_t **body_bkt,
293                     void *baton,
294                     serf_bucket_alloc_t *alloc,
295                     apr_pool_t *pool)
296 {
297   serf_bucket_t *buckets;
298
299   buckets = serf_bucket_aggregate_create(alloc);
300
301   svn_ra_serf__add_xml_header_buckets(buckets, alloc);
302   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "propfind",
303                                     "xmlns", "DAV:",
304                                     NULL);
305   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "prop", NULL);
306   svn_ra_serf__add_tag_buckets(buckets, "lockdiscovery", NULL, alloc);
307   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "prop");
308   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "propfind");
309
310   *body_bkt = buckets;
311   return SVN_NO_ERROR;
312 }
313
314 static svn_error_t*
315 setup_getlock_headers(serf_bucket_t *headers,
316                       void *baton,
317                       apr_pool_t *pool)
318 {
319   serf_bucket_headers_setn(headers, "Depth", "0");
320
321   return SVN_NO_ERROR;
322 }
323
324 /* Implements svn_ra_serf__request_body_delegate_t */
325 static svn_error_t *
326 create_lock_body(serf_bucket_t **body_bkt,
327                  void *baton,
328                  serf_bucket_alloc_t *alloc,
329                  apr_pool_t *pool)
330 {
331   lock_info_t *ctx = baton;
332   serf_bucket_t *buckets;
333
334   buckets = serf_bucket_aggregate_create(alloc);
335
336   svn_ra_serf__add_xml_header_buckets(buckets, alloc);
337   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo",
338                                     "xmlns", "DAV:",
339                                     NULL);
340
341   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", NULL);
342   svn_ra_serf__add_tag_buckets(buckets, "exclusive", NULL, alloc);
343   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope");
344
345   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", NULL);
346   svn_ra_serf__add_tag_buckets(buckets, "write", NULL, alloc);
347   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype");
348
349   if (ctx->lock->comment)
350     {
351       svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment,
352                                    alloc);
353     }
354
355   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo");
356
357   *body_bkt = buckets;
358   return SVN_NO_ERROR;
359 }
360
361 svn_error_t *
362 svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
363                       svn_lock_t **lock,
364                       const char *path,
365                       apr_pool_t *pool)
366 {
367   svn_ra_serf__session_t *session = ra_session->priv;
368   svn_ra_serf__handler_t *handler;
369   svn_ra_serf__xml_context_t *xmlctx;
370   lock_info_t *lock_ctx;
371   const char *req_url;
372   svn_error_t *err;
373
374   req_url = svn_path_url_add_component2(session->session_url.path, path, pool);
375
376   lock_ctx = apr_pcalloc(pool, sizeof(*lock_ctx));
377
378   lock_ctx->pool = pool;
379   lock_ctx->path = req_url;
380   lock_ctx->lock = svn_lock_create(pool);
381   lock_ctx->lock->path = apr_pstrdup(pool, path); /* be sure  */
382
383   xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
384                                            NULL, locks_closed, NULL,
385                                            lock_ctx,
386                                            pool);
387   handler = svn_ra_serf__create_expat_handler(xmlctx, pool);
388
389   handler->method = "PROPFIND";
390   handler->path = req_url;
391   handler->body_type = "text/xml";
392   handler->conn = session->conns[0];
393   handler->session = session;
394
395   handler->body_delegate = create_getlock_body;
396   handler->body_delegate_baton = lock_ctx;
397
398   handler->header_delegate = setup_getlock_headers;
399   handler->header_delegate_baton = lock_ctx;
400
401   lock_ctx->inner_handler = handler->response_handler;
402   lock_ctx->inner_baton = handler->response_baton;
403   handler->response_handler = handle_lock;
404   handler->response_baton = lock_ctx;
405
406   lock_ctx->handler = handler;
407
408   err = svn_ra_serf__context_run_one(handler, pool);
409   err = determine_error(handler, err);
410
411   if (handler->sline.code == 404)
412     {
413       return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, err,
414                               _("Malformed URL for repository"));
415     }
416   if (err)
417     {
418       /* TODO Shh.  We're telling a white lie for now. */
419       return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
420                               _("Server does not support locking features"));
421     }
422
423   *lock = lock_ctx->lock;
424
425   return SVN_NO_ERROR;
426 }
427
428 svn_error_t *
429 svn_ra_serf__lock(svn_ra_session_t *ra_session,
430                   apr_hash_t *path_revs,
431                   const char *comment,
432                   svn_boolean_t force,
433                   svn_ra_lock_callback_t lock_func,
434                   void *lock_baton,
435                   apr_pool_t *scratch_pool)
436 {
437   svn_ra_serf__session_t *session = ra_session->priv;
438   apr_hash_index_t *hi;
439   apr_pool_t *iterpool;
440
441   iterpool = svn_pool_create(scratch_pool);
442
443   /* ### TODO for issue 2263: Send all the locks over the wire at once.  This
444      ### loop is just a temporary shim.
445      ### an alternative, which is backwards-compat with all servers is to
446      ### pipeline these requests. ie. stop using run_wait/run_one.  */
447
448   for (hi = apr_hash_first(scratch_pool, path_revs);
449        hi;
450        hi = apr_hash_next(hi))
451     {
452       svn_ra_serf__handler_t *handler;
453       svn_ra_serf__xml_context_t *xmlctx;
454       const char *req_url;
455       lock_info_t *lock_ctx;
456       svn_error_t *err;
457       svn_error_t *new_err = NULL;
458
459       svn_pool_clear(iterpool);
460
461       lock_ctx = apr_pcalloc(iterpool, sizeof(*lock_ctx));
462
463       lock_ctx->pool = iterpool;
464       lock_ctx->path = svn__apr_hash_index_key(hi);
465       lock_ctx->revision = *((svn_revnum_t*)svn__apr_hash_index_val(hi));
466       lock_ctx->lock = svn_lock_create(iterpool);
467       lock_ctx->lock->path = lock_ctx->path;
468       lock_ctx->lock->comment = comment;
469
470       lock_ctx->force = force;
471       req_url = svn_path_url_add_component2(session->session_url.path,
472                                             lock_ctx->path, iterpool);
473
474       xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
475                                                NULL, locks_closed, NULL,
476                                                lock_ctx,
477                                                iterpool);
478       handler = svn_ra_serf__create_expat_handler(xmlctx, iterpool);
479
480       handler->method = "LOCK";
481       handler->path = req_url;
482       handler->body_type = "text/xml";
483       handler->conn = session->conns[0];
484       handler->session = session;
485
486       handler->header_delegate = set_lock_headers;
487       handler->header_delegate_baton = lock_ctx;
488
489       handler->body_delegate = create_lock_body;
490       handler->body_delegate_baton = lock_ctx;
491
492       lock_ctx->inner_handler = handler->response_handler;
493       lock_ctx->inner_baton = handler->response_baton;
494       handler->response_handler = handle_lock;
495       handler->response_baton = lock_ctx;
496
497       lock_ctx->handler = handler;
498
499       err = svn_ra_serf__context_run_one(handler, iterpool);
500       err = determine_error(handler, err);
501
502       if (lock_func)
503         new_err = lock_func(lock_baton, lock_ctx->path, TRUE, lock_ctx->lock,
504                             err, iterpool);
505       svn_error_clear(err);
506
507       SVN_ERR(new_err);
508     }
509
510   svn_pool_destroy(iterpool);
511
512   return SVN_NO_ERROR;
513 }
514
515 struct unlock_context_t {
516   const char *token;
517   svn_boolean_t force;
518 };
519
520 static svn_error_t *
521 set_unlock_headers(serf_bucket_t *headers,
522                    void *baton,
523                    apr_pool_t *pool)
524 {
525   struct unlock_context_t *ctx = baton;
526
527   serf_bucket_headers_set(headers, "Lock-Token", ctx->token);
528   if (ctx->force)
529     {
530       serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
531                               SVN_DAV_OPTION_LOCK_BREAK);
532     }
533
534   return SVN_NO_ERROR;
535 }
536
537 svn_error_t *
538 svn_ra_serf__unlock(svn_ra_session_t *ra_session,
539                     apr_hash_t *path_tokens,
540                     svn_boolean_t force,
541                     svn_ra_lock_callback_t lock_func,
542                     void *lock_baton,
543                     apr_pool_t *scratch_pool)
544 {
545   svn_ra_serf__session_t *session = ra_session->priv;
546   apr_hash_index_t *hi;
547   apr_pool_t *iterpool;
548
549   iterpool = svn_pool_create(scratch_pool);
550
551   /* ### TODO for issue 2263: Send all the locks over the wire at once.  This
552      ### loop is just a temporary shim.
553      ### an alternative, which is backwards-compat with all servers is to
554      ### pipeline these requests. ie. stop using run_wait/run_one.  */
555
556   for (hi = apr_hash_first(scratch_pool, path_tokens);
557        hi;
558        hi = apr_hash_next(hi))
559     {
560       svn_ra_serf__handler_t *handler;
561       const char *req_url, *path, *token;
562       svn_lock_t *existing_lock = NULL;
563       struct unlock_context_t unlock_ctx;
564       svn_error_t *err = NULL;
565       svn_error_t *new_err = NULL;
566
567
568       svn_pool_clear(iterpool);
569
570       path = svn__apr_hash_index_key(hi);
571       token = svn__apr_hash_index_val(hi);
572
573       if (force && (!token || token[0] == '\0'))
574         {
575           SVN_ERR(svn_ra_serf__get_lock(ra_session, &existing_lock, path,
576                                         iterpool));
577           token = existing_lock->token;
578           if (!token)
579             {
580               err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, NULL,
581                                       _("'%s' is not locked in the repository"),
582                                       path);
583
584               if (lock_func)
585                 {
586                   svn_error_t *err2;
587                   err2 = lock_func(lock_baton, path, FALSE, NULL, err,
588                                    iterpool);
589                   svn_error_clear(err);
590                   err = NULL;
591                   if (err2)
592                     return svn_error_trace(err2);
593                 }
594               else
595                 {
596                   svn_error_clear(err);
597                   err = NULL;
598                 }
599               continue;
600             }
601         }
602
603       unlock_ctx.force = force;
604       unlock_ctx.token = apr_pstrcat(iterpool, "<", token, ">", (char *)NULL);
605
606       req_url = svn_path_url_add_component2(session->session_url.path, path,
607                                             iterpool);
608
609       handler = apr_pcalloc(iterpool, sizeof(*handler));
610
611       handler->handler_pool = iterpool;
612       handler->method = "UNLOCK";
613       handler->path = req_url;
614       handler->conn = session->conns[0];
615       handler->session = session;
616
617       handler->header_delegate = set_unlock_headers;
618       handler->header_delegate_baton = &unlock_ctx;
619
620       handler->response_handler = svn_ra_serf__expect_empty_body;
621       handler->response_baton = handler;
622
623       SVN_ERR(svn_ra_serf__context_run_one(handler, iterpool));
624
625       switch (handler->sline.code)
626         {
627           case 204:
628             break; /* OK */
629           case 403:
630             /* Api users expect this specific error code to detect failures */
631             err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
632                                     _("Unlock request failed: %d %s"),
633                                     handler->sline.code,
634                                     handler->sline.reason);
635             break;
636           default:
637             err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
638                                     _("Unlock request failed: %d %s"),
639                                     handler->sline.code,
640                                     handler->sline.reason);
641         }
642
643       if (lock_func)
644         new_err = lock_func(lock_baton, path, FALSE, existing_lock, err,
645                             iterpool);
646
647       svn_error_clear(err);
648       SVN_ERR(new_err);
649     }
650
651   svn_pool_destroy(iterpool);
652
653   return SVN_NO_ERROR;
654 }