]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_ra_serf/locks.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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 (strcasecmp(cdata->data, "Infinite") == 0)
160         lock_ctx->lock->expiration_date = 0;
161       else if (strncasecmp(cdata->data, "Second-", 7) == 0)
162         {
163           unsigned n;
164           SVN_ERR(svn_cstring_atoui(&n, cdata->data+7));
165
166           lock_ctx->lock->expiration_date = apr_time_now() +
167                                             apr_time_from_sec(n);
168         }
169       else
170         return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
171                                  _("Invalid LOCK timeout value '%s'"),
172                                  cdata->data);
173     }
174   else if (leaving_state == HREF)
175     {
176       if (cdata->len)
177         {
178           char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len);
179
180           apr_collapse_spaces(buf, buf);
181           lock_ctx->lock->token = buf;
182         }
183     }
184   else if (leaving_state == OWNER)
185     {
186       if (cdata->len)
187         {
188           lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool,
189                                                    cdata->data, cdata->len);
190         }
191     }
192
193   return SVN_NO_ERROR;
194 }
195
196
197 static svn_error_t *
198 set_lock_headers(serf_bucket_t *headers,
199                  void *baton,
200                  apr_pool_t *pool)
201 {
202   lock_info_t *lock_ctx = baton;
203
204   if (lock_ctx->force)
205     {
206       serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
207                               SVN_DAV_OPTION_LOCK_STEAL);
208     }
209
210   if (SVN_IS_VALID_REVNUM(lock_ctx->revision))
211     {
212       serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
213                               apr_ltoa(pool, lock_ctx->revision));
214     }
215
216   return APR_SUCCESS;
217 }
218
219
220 /* Register an error within the session. If something is already there,
221    then it will take precedence.  */
222 static svn_error_t *
223 determine_error(svn_ra_serf__handler_t *handler,
224                 svn_error_t *err)
225 {
226     {
227       apr_status_t errcode;
228
229       if (handler->sline.code == 423)
230         errcode = SVN_ERR_FS_PATH_ALREADY_LOCKED;
231       else if (handler->sline.code == 403)
232         errcode = SVN_ERR_RA_DAV_FORBIDDEN;
233       else
234         return err;
235
236       /* Client-side or server-side error already. Return it.  */
237       if (err != NULL)
238         return err;
239
240       /* The server did not send us a detailed human-readable error.
241          Provide a generic error.  */
242       err = svn_error_createf(errcode, NULL,
243                               _("Lock request failed: %d %s"),
244                               handler->sline.code,
245                               handler->sline.reason);
246     }
247
248   return err;
249 }
250
251
252 /* Implements svn_ra_serf__response_handler_t */
253 static svn_error_t *
254 handle_lock(serf_request_t *request,
255             serf_bucket_t *response,
256             void *handler_baton,
257             apr_pool_t *pool)
258 {
259   lock_info_t *ctx = handler_baton;
260
261   /* 403 (Forbidden) when a lock doesn't exist.
262      423 (Locked) when a lock already exists.  */
263   if (ctx->handler->sline.code == 403
264       || ctx->handler->sline.code == 423)
265     {
266       /* Go look in the body for a server-provided error. This will
267          reset flags for the core handler to Do The Right Thing. We
268          won't be back to this handler again.  */
269       return svn_error_trace(svn_ra_serf__expect_empty_body(
270                                request, response, ctx->handler, pool));
271     }
272
273   if (!ctx->read_headers)
274     {
275       serf_bucket_t *headers;
276       const char *val;
277
278       headers = serf_bucket_response_get_headers(response);
279
280       val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
281       if (val)
282         {
283           ctx->lock->owner = apr_pstrdup(ctx->pool, val);
284         }
285
286       val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
287       if (val)
288         {
289           SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
290                                         ctx->pool));
291         }
292
293       ctx->read_headers = TRUE;
294     }
295
296   return ctx->inner_handler(request, response, ctx->inner_baton, pool);
297 }
298
299 /* Implements svn_ra_serf__request_body_delegate_t */
300 static svn_error_t *
301 create_getlock_body(serf_bucket_t **body_bkt,
302                     void *baton,
303                     serf_bucket_alloc_t *alloc,
304                     apr_pool_t *pool)
305 {
306   serf_bucket_t *buckets;
307
308   buckets = serf_bucket_aggregate_create(alloc);
309
310   svn_ra_serf__add_xml_header_buckets(buckets, alloc);
311   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "propfind",
312                                     "xmlns", "DAV:",
313                                     NULL);
314   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "prop", NULL);
315   svn_ra_serf__add_tag_buckets(buckets, "lockdiscovery", NULL, alloc);
316   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "prop");
317   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "propfind");
318
319   *body_bkt = buckets;
320   return SVN_NO_ERROR;
321 }
322
323 static svn_error_t*
324 setup_getlock_headers(serf_bucket_t *headers,
325                       void *baton,
326                       apr_pool_t *pool)
327 {
328   serf_bucket_headers_setn(headers, "Depth", "0");
329
330   return SVN_NO_ERROR;
331 }
332
333 /* Implements svn_ra_serf__request_body_delegate_t */
334 static svn_error_t *
335 create_lock_body(serf_bucket_t **body_bkt,
336                  void *baton,
337                  serf_bucket_alloc_t *alloc,
338                  apr_pool_t *pool)
339 {
340   lock_info_t *ctx = baton;
341   serf_bucket_t *buckets;
342
343   buckets = serf_bucket_aggregate_create(alloc);
344
345   svn_ra_serf__add_xml_header_buckets(buckets, alloc);
346   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo",
347                                     "xmlns", "DAV:",
348                                     NULL);
349
350   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", NULL);
351   svn_ra_serf__add_tag_buckets(buckets, "exclusive", NULL, alloc);
352   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope");
353
354   svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", NULL);
355   svn_ra_serf__add_tag_buckets(buckets, "write", NULL, alloc);
356   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype");
357
358   if (ctx->lock->comment)
359     {
360       svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment,
361                                    alloc);
362     }
363
364   svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo");
365
366   *body_bkt = buckets;
367   return SVN_NO_ERROR;
368 }
369
370 svn_error_t *
371 svn_ra_serf__get_lock(svn_ra_session_t *ra_session,
372                       svn_lock_t **lock,
373                       const char *path,
374                       apr_pool_t *result_pool)
375 {
376   svn_ra_serf__session_t *session = ra_session->priv;
377   svn_ra_serf__handler_t *handler;
378   svn_ra_serf__xml_context_t *xmlctx;
379   apr_pool_t *scratch_pool = svn_pool_create(result_pool);
380   lock_info_t *lock_ctx;
381   const char *req_url;
382   svn_error_t *err;
383
384   req_url = svn_path_url_add_component2(session->session_url.path, path,
385                                         scratch_pool);
386
387   lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx));
388   lock_ctx->pool = result_pool;
389   lock_ctx->path = req_url;
390   lock_ctx->lock = svn_lock_create(result_pool);
391   lock_ctx->lock->path = apr_pstrdup(result_pool, path);
392
393   xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
394                                            NULL, locks_closed, NULL,
395                                            lock_ctx,
396                                            scratch_pool);
397   handler = svn_ra_serf__create_expat_handler(xmlctx, scratch_pool);
398
399   handler->method = "PROPFIND";
400   handler->path = req_url;
401   handler->body_type = "text/xml";
402   handler->conn = session->conns[0];
403   handler->session = session;
404
405   handler->body_delegate = create_getlock_body;
406   handler->body_delegate_baton = lock_ctx;
407
408   handler->header_delegate = setup_getlock_headers;
409   handler->header_delegate_baton = lock_ctx;
410
411   lock_ctx->inner_handler = handler->response_handler;
412   lock_ctx->inner_baton = handler->response_baton;
413   handler->response_handler = handle_lock;
414   handler->response_baton = lock_ctx;
415
416   lock_ctx->handler = handler;
417
418   err = svn_ra_serf__context_run_one(handler, scratch_pool);
419   err = determine_error(handler, err);
420
421   if (handler->sline.code == 404)
422     {
423       return svn_error_create(SVN_ERR_RA_ILLEGAL_URL, err,
424                               _("Malformed URL for repository"));
425     }
426   if (err)
427     {
428       /* TODO Shh.  We're telling a white lie for now. */
429       return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, err,
430                               _("Server does not support locking features"));
431     }
432
433   if (lock_ctx->lock && lock_ctx->lock->token)
434     *lock = lock_ctx->lock;
435   else
436     *lock = NULL;
437
438   svn_pool_destroy(scratch_pool);
439
440   return SVN_NO_ERROR;
441 }
442
443 svn_error_t *
444 svn_ra_serf__lock(svn_ra_session_t *ra_session,
445                   apr_hash_t *path_revs,
446                   const char *comment,
447                   svn_boolean_t force,
448                   svn_ra_lock_callback_t lock_func,
449                   void *lock_baton,
450                   apr_pool_t *scratch_pool)
451 {
452   svn_ra_serf__session_t *session = ra_session->priv;
453   apr_hash_index_t *hi;
454   apr_pool_t *iterpool;
455
456   iterpool = svn_pool_create(scratch_pool);
457
458   /* ### TODO for issue 2263: Send all the locks over the wire at once.  This
459      ### loop is just a temporary shim.
460      ### an alternative, which is backwards-compat with all servers is to
461      ### pipeline these requests. ie. stop using run_wait/run_one.  */
462
463   for (hi = apr_hash_first(scratch_pool, path_revs);
464        hi;
465        hi = apr_hash_next(hi))
466     {
467       svn_ra_serf__handler_t *handler;
468       svn_ra_serf__xml_context_t *xmlctx;
469       const char *req_url;
470       lock_info_t *lock_ctx;
471       svn_error_t *err;
472       svn_error_t *new_err = NULL;
473
474       svn_pool_clear(iterpool);
475
476       lock_ctx = apr_pcalloc(iterpool, sizeof(*lock_ctx));
477
478       lock_ctx->pool = iterpool;
479       lock_ctx->path = svn__apr_hash_index_key(hi);
480       lock_ctx->revision = *((svn_revnum_t*)svn__apr_hash_index_val(hi));
481       lock_ctx->lock = svn_lock_create(iterpool);
482       lock_ctx->lock->path = lock_ctx->path;
483       lock_ctx->lock->comment = comment;
484
485       lock_ctx->force = force;
486       req_url = svn_path_url_add_component2(session->session_url.path,
487                                             lock_ctx->path, iterpool);
488
489       xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
490                                                NULL, locks_closed, NULL,
491                                                lock_ctx,
492                                                iterpool);
493       handler = svn_ra_serf__create_expat_handler(xmlctx, iterpool);
494
495       handler->method = "LOCK";
496       handler->path = req_url;
497       handler->body_type = "text/xml";
498       handler->conn = session->conns[0];
499       handler->session = session;
500
501       handler->header_delegate = set_lock_headers;
502       handler->header_delegate_baton = lock_ctx;
503
504       handler->body_delegate = create_lock_body;
505       handler->body_delegate_baton = lock_ctx;
506
507       lock_ctx->inner_handler = handler->response_handler;
508       lock_ctx->inner_baton = handler->response_baton;
509       handler->response_handler = handle_lock;
510       handler->response_baton = lock_ctx;
511
512       lock_ctx->handler = handler;
513
514       err = svn_ra_serf__context_run_one(handler, iterpool);
515       err = determine_error(handler, err);
516
517       if (lock_func)
518         new_err = lock_func(lock_baton, lock_ctx->path, TRUE, lock_ctx->lock,
519                             err, iterpool);
520       svn_error_clear(err);
521
522       SVN_ERR(new_err);
523     }
524
525   svn_pool_destroy(iterpool);
526
527   return SVN_NO_ERROR;
528 }
529
530 struct unlock_context_t {
531   const char *token;
532   svn_boolean_t force;
533 };
534
535 static svn_error_t *
536 set_unlock_headers(serf_bucket_t *headers,
537                    void *baton,
538                    apr_pool_t *pool)
539 {
540   struct unlock_context_t *ctx = baton;
541
542   serf_bucket_headers_set(headers, "Lock-Token", ctx->token);
543   if (ctx->force)
544     {
545       serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
546                               SVN_DAV_OPTION_LOCK_BREAK);
547     }
548
549   return SVN_NO_ERROR;
550 }
551
552 svn_error_t *
553 svn_ra_serf__unlock(svn_ra_session_t *ra_session,
554                     apr_hash_t *path_tokens,
555                     svn_boolean_t force,
556                     svn_ra_lock_callback_t lock_func,
557                     void *lock_baton,
558                     apr_pool_t *scratch_pool)
559 {
560   svn_ra_serf__session_t *session = ra_session->priv;
561   apr_hash_index_t *hi;
562   apr_pool_t *iterpool;
563
564   iterpool = svn_pool_create(scratch_pool);
565
566   /* ### TODO for issue 2263: Send all the locks over the wire at once.  This
567      ### loop is just a temporary shim.
568      ### an alternative, which is backwards-compat with all servers is to
569      ### pipeline these requests. ie. stop using run_wait/run_one.  */
570
571   for (hi = apr_hash_first(scratch_pool, path_tokens);
572        hi;
573        hi = apr_hash_next(hi))
574     {
575       svn_ra_serf__handler_t *handler;
576       const char *req_url, *path, *token;
577       svn_lock_t *existing_lock = NULL;
578       struct unlock_context_t unlock_ctx;
579       svn_error_t *err = NULL;
580       svn_error_t *new_err = NULL;
581
582
583       svn_pool_clear(iterpool);
584
585       path = svn__apr_hash_index_key(hi);
586       token = svn__apr_hash_index_val(hi);
587
588       if (force && (!token || token[0] == '\0'))
589         {
590           SVN_ERR(svn_ra_serf__get_lock(ra_session, &existing_lock, path,
591                                         iterpool));
592           token = existing_lock ? existing_lock->token : NULL;
593           if (!token)
594             {
595               err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, NULL,
596                                       _("'%s' is not locked in the repository"),
597                                       path);
598
599               if (lock_func)
600                 {
601                   svn_error_t *err2;
602                   err2 = lock_func(lock_baton, path, FALSE, NULL, err,
603                                    iterpool);
604                   svn_error_clear(err);
605                   err = NULL;
606                   if (err2)
607                     return svn_error_trace(err2);
608                 }
609               else
610                 {
611                   svn_error_clear(err);
612                   err = NULL;
613                 }
614               continue;
615             }
616         }
617
618       unlock_ctx.force = force;
619       unlock_ctx.token = apr_pstrcat(iterpool, "<", token, ">", (char *)NULL);
620
621       req_url = svn_path_url_add_component2(session->session_url.path, path,
622                                             iterpool);
623
624       handler = apr_pcalloc(iterpool, sizeof(*handler));
625
626       handler->handler_pool = iterpool;
627       handler->method = "UNLOCK";
628       handler->path = req_url;
629       handler->conn = session->conns[0];
630       handler->session = session;
631
632       handler->header_delegate = set_unlock_headers;
633       handler->header_delegate_baton = &unlock_ctx;
634
635       handler->response_handler = svn_ra_serf__expect_empty_body;
636       handler->response_baton = handler;
637
638       SVN_ERR(svn_ra_serf__context_run_one(handler, iterpool));
639
640       switch (handler->sline.code)
641         {
642           case 204:
643             break; /* OK */
644           case 403:
645             /* Api users expect this specific error code to detect failures */
646             err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
647                                     _("Unlock request failed: %d %s"),
648                                     handler->sline.code,
649                                     handler->sline.reason);
650             break;
651           default:
652             err = svn_error_createf(SVN_ERR_RA_DAV_REQUEST_FAILED, NULL,
653                                     _("Unlock request failed: %d %s"),
654                                     handler->sline.code,
655                                     handler->sline.reason);
656         }
657
658       if (lock_func)
659         new_err = lock_func(lock_baton, path, FALSE, existing_lock, err,
660                             iterpool);
661
662       svn_error_clear(err);
663       SVN_ERR(new_err);
664     }
665
666   svn_pool_destroy(iterpool);
667
668   return SVN_NO_ERROR;
669 }