~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/libsvn_ra_serf/lock.c

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * lock.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
 
 
26
#include <apr_uri.h>
 
27
#include <serf.h>
 
28
#include <assert.h>
 
29
 
 
30
#include "svn_dav.h"
 
31
#include "svn_hash.h"
 
32
#include "svn_pools.h"
 
33
#include "svn_ra.h"
 
34
 
 
35
#include "../libsvn_ra/ra_loader.h"
 
36
#include "svn_config.h"
 
37
#include "svn_path.h"
 
38
#include "svn_sorts.h"
 
39
#include "svn_time.h"
 
40
#include "svn_private_config.h"
 
41
#include "private/svn_sorts_private.h"
 
42
 
 
43
#include "ra_serf.h"
 
44
 
 
45
 
 
46
/*
 
47
 * This enum represents the current state of our XML parsing for a REPORT.
 
48
 */
 
49
enum {
 
50
  INITIAL = 0,
 
51
  PROP,
 
52
  LOCK_DISCOVERY,
 
53
  ACTIVE_LOCK,
 
54
  LOCK_TYPE,
 
55
  LOCK_SCOPE,
 
56
  DEPTH,
 
57
  TIMEOUT,
 
58
  LOCK_TOKEN,
 
59
  OWNER,
 
60
  HREF
 
61
};
 
62
 
 
63
 
 
64
typedef struct lock_ctx_t {
 
65
  apr_pool_t *pool;
 
66
 
 
67
  const char *path;
 
68
 
 
69
  const char *token; /* For unlock */
 
70
  svn_lock_t *lock; /* For lock */
 
71
 
 
72
  svn_boolean_t force;
 
73
  svn_revnum_t revision;
 
74
 
 
75
  svn_boolean_t read_headers;
 
76
 
 
77
  svn_ra_serf__handler_t *handler;
 
78
 
 
79
  /* The expat handler. We wrap this to do a bit more work.  */
 
80
  svn_ra_serf__response_handler_t inner_handler;
 
81
  void *inner_baton;
 
82
 
 
83
} lock_ctx_t;
 
84
 
 
85
 
 
86
#define D_ "DAV:"
 
87
#define S_ SVN_XML_NAMESPACE
 
88
static const svn_ra_serf__xml_transition_t locks_ttable[] = {
 
89
  /* The INITIAL state can transition into D:prop (LOCK) or
 
90
     to D:multistatus (PROPFIND)  */
 
91
  { INITIAL, D_, "prop", PROP,
 
92
    FALSE, { NULL }, FALSE },
 
93
 
 
94
  { PROP, D_, "lockdiscovery", LOCK_DISCOVERY,
 
95
    FALSE, { NULL }, FALSE },
 
96
 
 
97
  { LOCK_DISCOVERY, D_, "activelock", ACTIVE_LOCK,
 
98
    FALSE, { NULL }, FALSE },
 
99
 
 
100
#if 0
 
101
  /* ### we don't really need to parse locktype/lockscope. we know what
 
102
     ### the values are going to be. we *could* validate that the only
 
103
     ### possible children are D:write and D:exclusive. we'd need to
 
104
     ### modify the state transition to tell us about all children
 
105
     ### (ie. maybe support "*" for the name) and then validate. but it
 
106
     ### just isn't important to validate, so disable this for now... */
 
107
 
 
108
  { ACTIVE_LOCK, D_, "locktype", LOCK_TYPE,
 
109
    FALSE, { NULL }, FALSE },
 
110
 
 
111
  { LOCK_TYPE, D_, "write", WRITE,
 
112
    FALSE, { NULL }, TRUE },
 
113
 
 
114
  { ACTIVE_LOCK, D_, "lockscope", LOCK_SCOPE,
 
115
    FALSE, { NULL }, FALSE },
 
116
 
 
117
  { LOCK_SCOPE, D_, "exclusive", EXCLUSIVE,
 
118
    FALSE, { NULL }, TRUE },
 
119
#endif /* 0  */
 
120
 
 
121
  { ACTIVE_LOCK, D_, "timeout", TIMEOUT,
 
122
    TRUE, { NULL }, TRUE },
 
123
 
 
124
  { ACTIVE_LOCK, D_, "locktoken", LOCK_TOKEN,
 
125
    FALSE, { NULL }, FALSE },
 
126
 
 
127
  { LOCK_TOKEN, D_, "href", HREF,
 
128
    TRUE, { NULL }, TRUE },
 
129
 
 
130
  { ACTIVE_LOCK, D_, "owner", OWNER,
 
131
    TRUE, { NULL }, TRUE },
 
132
 
 
133
  /* ACTIVE_LOCK has a D:depth child, but we can ignore that.  */
 
134
 
 
135
  { 0 }
 
136
};
 
137
 
 
138
/* Conforms to svn_ra_serf__xml_closed_t  */
 
139
static svn_error_t *
 
140
locks_closed(svn_ra_serf__xml_estate_t *xes,
 
141
             void *baton,
 
142
             int leaving_state,
 
143
             const svn_string_t *cdata,
 
144
             apr_hash_t *attrs,
 
145
             apr_pool_t *scratch_pool)
 
146
{
 
147
  lock_ctx_t *lock_ctx = baton;
 
148
 
 
149
  if (leaving_state == TIMEOUT)
 
150
    {
 
151
      /* This function just parses the result of our own lock request,
 
152
         so on a normal server we will only encounter 'Infinite' here. */
 
153
      if (strcasecmp(cdata->data, "Infinite") == 0)
 
154
        lock_ctx->lock->expiration_date = 0;
 
155
      else if (strncasecmp(cdata->data, "Second-", 7) == 0)
 
156
        {
 
157
          unsigned n;
 
158
          SVN_ERR(svn_cstring_atoui(&n, cdata->data+7));
 
159
 
 
160
          lock_ctx->lock->expiration_date = apr_time_now() +
 
161
                                            apr_time_from_sec(n);
 
162
        }
 
163
      else
 
164
         return svn_error_createf(SVN_ERR_RA_DAV_MALFORMED_DATA, NULL,
 
165
                                  _("Invalid LOCK timeout value '%s'"),
 
166
                                  cdata->data);
 
167
    }
 
168
  else if (leaving_state == HREF)
 
169
    {
 
170
      if (cdata->len)
 
171
        {
 
172
          char *buf = apr_pstrmemdup(lock_ctx->pool, cdata->data, cdata->len);
 
173
 
 
174
          apr_collapse_spaces(buf, buf);
 
175
          lock_ctx->lock->token = buf;
 
176
        }
 
177
    }
 
178
  else if (leaving_state == OWNER)
 
179
    {
 
180
      if (cdata->len)
 
181
        {
 
182
          lock_ctx->lock->comment = apr_pstrmemdup(lock_ctx->pool,
 
183
                                                   cdata->data, cdata->len);
 
184
        }
 
185
    }
 
186
 
 
187
  return SVN_NO_ERROR;
 
188
}
 
189
 
 
190
 
 
191
static svn_error_t *
 
192
set_lock_headers(serf_bucket_t *headers,
 
193
                 void *baton,
 
194
                 apr_pool_t *pool /* request pool */,
 
195
                 apr_pool_t *scratch_pool)
 
196
{
 
197
  lock_ctx_t *lock_ctx = baton;
 
198
 
 
199
  if (lock_ctx->force)
 
200
    {
 
201
      serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
 
202
                              SVN_DAV_OPTION_LOCK_STEAL);
 
203
    }
 
204
 
 
205
  if (SVN_IS_VALID_REVNUM(lock_ctx->revision))
 
206
    {
 
207
      serf_bucket_headers_set(headers, SVN_DAV_VERSION_NAME_HEADER,
 
208
                              apr_ltoa(pool, lock_ctx->revision));
 
209
    }
 
210
 
 
211
  return APR_SUCCESS;
 
212
}
 
213
 
 
214
/* Helper function for svn_ra_serf__lock and svn_ra_serf__unlock */
 
215
static svn_error_t *
 
216
run_locks(svn_ra_serf__session_t *sess,
 
217
          apr_array_header_t *lock_ctxs,
 
218
          svn_boolean_t locking,
 
219
          svn_ra_lock_callback_t lock_func,
 
220
          void *lock_baton,
 
221
          apr_pool_t *scratch_pool)
 
222
{
 
223
  apr_pool_t *iterpool;
 
224
  apr_interval_time_t waittime_left = sess->timeout;
 
225
 
 
226
  assert(sess->pending_error == SVN_NO_ERROR);
 
227
 
 
228
  iterpool = svn_pool_create(scratch_pool);
 
229
  while (lock_ctxs->nelts)
 
230
    {
 
231
      int i;
 
232
 
 
233
      svn_pool_clear(iterpool);
 
234
 
 
235
      SVN_ERR(svn_ra_serf__context_run(sess, &waittime_left, iterpool));
 
236
 
 
237
      for (i = 0; i < lock_ctxs->nelts; i++)
 
238
        {
 
239
          lock_ctx_t *ctx = APR_ARRAY_IDX(lock_ctxs, i, lock_ctx_t *);
 
240
 
 
241
          if (ctx->handler->done)
 
242
            {
 
243
              svn_error_t *server_err = NULL;
 
244
              svn_error_t *cb_err = NULL;
 
245
              svn_error_t *err;
 
246
 
 
247
              if (ctx->handler->server_error)
 
248
                server_err = svn_ra_serf__server_error_create(ctx->handler, iterpool);
 
249
 
 
250
              /* Api users expect specific error code to detect failures,
 
251
                 pass the rest to svn_ra_serf__error_on_status */
 
252
              switch (ctx->handler->sline.code)
 
253
                {
 
254
                  case 200:
 
255
                  case 204:
 
256
                    err = NULL; /* (un)lock succeeded */
 
257
                    break;
 
258
 
 
259
                  case 400:
 
260
                    err = svn_error_createf(SVN_ERR_FS_NO_SUCH_LOCK, NULL,
 
261
                                            _("No lock on path '%s' (%d %s)"),
 
262
                                            ctx->path,
 
263
                                            ctx->handler->sline.code,
 
264
                                            ctx->handler->sline.reason);
 
265
                    break;
 
266
                  case 403:
 
267
                    /* ### Authz can also lead to 403. */
 
268
                    err = svn_error_createf(SVN_ERR_FS_LOCK_OWNER_MISMATCH,
 
269
                                            NULL,
 
270
                                            _("Unlock of '%s' failed (%d %s)"),
 
271
                                            ctx->path,
 
272
                                            ctx->handler->sline.code,
 
273
                                            ctx->handler->sline.reason);
 
274
                    break;
 
275
                  case 405:
 
276
                    err = svn_error_createf(SVN_ERR_FS_OUT_OF_DATE,
 
277
                                            NULL,
 
278
                                            _("Path '%s' doesn't exist in "
 
279
                                              "HEAD revision (%d %s)"),
 
280
                                            ctx->path,
 
281
                                            ctx->handler->sline.code,
 
282
                                            ctx->handler->sline.reason);
 
283
                    break;
 
284
                  case 423:
 
285
                    err = svn_error_createf(SVN_ERR_FS_PATH_ALREADY_LOCKED,
 
286
                                            NULL,
 
287
                                            _("Path '%s' already locked "
 
288
                                              "(%d %s)"),
 
289
                                            ctx->path,
 
290
                                            ctx->handler->sline.code,
 
291
                                            ctx->handler->sline.reason);
 
292
                    break;
 
293
 
 
294
                  case 404:
 
295
                  case 409:
 
296
                  case 500:
 
297
                    if (server_err)
 
298
                      {
 
299
                        /* Handle out of date, etc by just passing the server
 
300
                           error */
 
301
                        err = NULL;
 
302
                        break;
 
303
                      }
 
304
 
 
305
                    /* Fall through */
 
306
                  default:
 
307
                    err = svn_ra_serf__unexpected_status(ctx->handler);
 
308
                    break;
 
309
                }
 
310
 
 
311
              if (server_err && err && server_err->apr_err == err->apr_err)
 
312
                err = svn_error_compose_create(server_err, err);
 
313
              else
 
314
                err = svn_error_compose_create(err, server_err);
 
315
 
 
316
              if (err
 
317
                  && !SVN_ERR_IS_UNLOCK_ERROR(err)
 
318
                  && !SVN_ERR_IS_LOCK_ERROR(err))
 
319
                {
 
320
                  /* If the error that we are going to report is just about the
 
321
                     POST unlock hook, we should first report that the operation
 
322
                     succeeded, or the repository and working copy will be
 
323
                     out of sync... */
 
324
 
 
325
                  if (lock_func &&
 
326
                      err->apr_err == SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED)
 
327
                    {
 
328
                      err = svn_error_compose_create(
 
329
                                  err, lock_func(lock_baton, ctx->path, locking,
 
330
                                                 NULL, NULL, ctx->pool));
 
331
                    }
 
332
 
 
333
                  return svn_error_trace(err); /* Don't go through callbacks */
 
334
                }
 
335
 
 
336
              if (lock_func)
 
337
                {
 
338
                  svn_lock_t *report_lock = NULL;
 
339
 
 
340
                  if (locking && ctx->lock->token)
 
341
                    report_lock = ctx->lock;
 
342
 
 
343
                  cb_err = lock_func(lock_baton, ctx->path, locking,
 
344
                                     report_lock, err, ctx->pool);
 
345
                }
 
346
              svn_error_clear(err);
 
347
 
 
348
              SVN_ERR(cb_err);
 
349
 
 
350
              waittime_left = sess->timeout;
 
351
              svn_sort__array_delete(lock_ctxs, i, 1);
 
352
              i--;
 
353
 
 
354
              svn_pool_destroy(ctx->pool);
 
355
              continue;
 
356
            }
 
357
        }
 
358
    }
 
359
  svn_pool_destroy(iterpool);
 
360
 
 
361
  return SVN_NO_ERROR;
 
362
}
 
363
 
 
364
/* Implements svn_ra_serf__response_handler_t */
 
365
static svn_error_t *
 
366
handle_lock(serf_request_t *request,
 
367
            serf_bucket_t *response,
 
368
            void *handler_baton,
 
369
            apr_pool_t *pool)
 
370
{
 
371
  lock_ctx_t *ctx = handler_baton;
 
372
 
 
373
  if (!ctx->read_headers)
 
374
    {
 
375
      serf_bucket_t *headers;
 
376
      const char *val;
 
377
 
 
378
      headers = serf_bucket_response_get_headers(response);
 
379
 
 
380
      val = serf_bucket_headers_get(headers, SVN_DAV_LOCK_OWNER_HEADER);
 
381
      if (val)
 
382
        {
 
383
          ctx->lock->owner = apr_pstrdup(ctx->pool, val);
 
384
        }
 
385
 
 
386
      val = serf_bucket_headers_get(headers, SVN_DAV_CREATIONDATE_HEADER);
 
387
      if (val)
 
388
        {
 
389
          SVN_ERR(svn_time_from_cstring(&ctx->lock->creation_date, val,
 
390
                                        ctx->pool));
 
391
        }
 
392
 
 
393
      ctx->read_headers = TRUE;
 
394
    }
 
395
 
 
396
  return ctx->inner_handler(request, response, ctx->inner_baton, pool);
 
397
}
 
398
 
 
399
/* Implements svn_ra_serf__request_body_delegate_t */
 
400
static svn_error_t *
 
401
create_lock_body(serf_bucket_t **body_bkt,
 
402
                 void *baton,
 
403
                 serf_bucket_alloc_t *alloc,
 
404
                 apr_pool_t *pool /* request pool */,
 
405
                 apr_pool_t *scratch_pool)
 
406
{
 
407
  lock_ctx_t *ctx = baton;
 
408
  serf_bucket_t *buckets;
 
409
 
 
410
  buckets = serf_bucket_aggregate_create(alloc);
 
411
 
 
412
  svn_ra_serf__add_xml_header_buckets(buckets, alloc);
 
413
  svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockinfo",
 
414
                                    "xmlns", "DAV:",
 
415
                                    SVN_VA_NULL);
 
416
 
 
417
  svn_ra_serf__add_open_tag_buckets(buckets, alloc, "lockscope", SVN_VA_NULL);
 
418
  svn_ra_serf__add_empty_tag_buckets(buckets, alloc, "exclusive", SVN_VA_NULL);
 
419
  svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockscope");
 
420
 
 
421
  svn_ra_serf__add_open_tag_buckets(buckets, alloc, "locktype", SVN_VA_NULL);
 
422
  svn_ra_serf__add_empty_tag_buckets(buckets, alloc, "write", SVN_VA_NULL);
 
423
  svn_ra_serf__add_close_tag_buckets(buckets, alloc, "locktype");
 
424
 
 
425
  if (ctx->lock->comment)
 
426
    {
 
427
      svn_ra_serf__add_tag_buckets(buckets, "owner", ctx->lock->comment,
 
428
                                   alloc);
 
429
    }
 
430
 
 
431
  svn_ra_serf__add_close_tag_buckets(buckets, alloc, "lockinfo");
 
432
 
 
433
  *body_bkt = buckets;
 
434
  return SVN_NO_ERROR;
 
435
}
 
436
 
 
437
svn_error_t *
 
438
svn_ra_serf__lock(svn_ra_session_t *ra_session,
 
439
                  apr_hash_t *path_revs,
 
440
                  const char *comment,
 
441
                  svn_boolean_t force,
 
442
                  svn_ra_lock_callback_t lock_func,
 
443
                  void *lock_baton,
 
444
                  apr_pool_t *scratch_pool)
 
445
{
 
446
  svn_ra_serf__session_t *session = ra_session->priv;
 
447
  apr_hash_index_t *hi;
 
448
  apr_pool_t *iterpool;
 
449
  apr_array_header_t *lock_requests;
 
450
 
 
451
  lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_revs),
 
452
                                 sizeof(lock_ctx_t*));
 
453
 
 
454
  /* ### Perhaps we should open more connections than just one? See update.c */
 
455
 
 
456
  iterpool = svn_pool_create(scratch_pool);
 
457
 
 
458
  for (hi = apr_hash_first(scratch_pool, path_revs);
 
459
       hi;
 
460
       hi = apr_hash_next(hi))
 
461
    {
 
462
      svn_ra_serf__handler_t *handler;
 
463
      svn_ra_serf__xml_context_t *xmlctx;
 
464
      const char *req_url;
 
465
      lock_ctx_t *lock_ctx;
 
466
      apr_pool_t *lock_pool;
 
467
 
 
468
      svn_pool_clear(iterpool);
 
469
 
 
470
      lock_pool = svn_pool_create(scratch_pool);
 
471
      lock_ctx = apr_pcalloc(scratch_pool, sizeof(*lock_ctx));
 
472
 
 
473
      lock_ctx->pool = lock_pool;
 
474
      lock_ctx->path = apr_hash_this_key(hi);
 
475
      lock_ctx->revision = *((svn_revnum_t*)apr_hash_this_val(hi));
 
476
      lock_ctx->lock = svn_lock_create(lock_pool);
 
477
      lock_ctx->lock->path = lock_ctx->path;
 
478
      lock_ctx->lock->comment = comment;
 
479
 
 
480
      lock_ctx->force = force;
 
481
      req_url = svn_path_url_add_component2(session->session_url.path,
 
482
                                            lock_ctx->path, lock_pool);
 
483
 
 
484
      xmlctx = svn_ra_serf__xml_context_create(locks_ttable,
 
485
                                               NULL, locks_closed, NULL,
 
486
                                               lock_ctx,
 
487
                                               lock_pool);
 
488
      handler = svn_ra_serf__create_expat_handler(session, xmlctx, NULL,
 
489
                                                  lock_pool);
 
490
 
 
491
      handler->method = "LOCK";
 
492
      handler->path = req_url;
 
493
      handler->body_type = "text/xml";
 
494
 
 
495
      /* Same stupid algorithm from get_best_connection() in update.c */
 
496
      handler->conn = session->conns[session->cur_conn];
 
497
      session->cur_conn++;
 
498
 
 
499
      if (session->cur_conn >= session->num_conns)
 
500
        session->cur_conn = 0;
 
501
 
 
502
      handler->header_delegate = set_lock_headers;
 
503
      handler->header_delegate_baton = lock_ctx;
 
504
 
 
505
      handler->body_delegate = create_lock_body;
 
506
      handler->body_delegate_baton = lock_ctx;
 
507
 
 
508
      lock_ctx->inner_handler = handler->response_handler;
 
509
      lock_ctx->inner_baton = handler->response_baton;
 
510
      handler->response_handler = handle_lock;
 
511
      handler->response_baton = lock_ctx;
 
512
 
 
513
      handler->no_fail_on_http_failure_status = TRUE;
 
514
 
 
515
      lock_ctx->handler = handler;
 
516
 
 
517
      APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
 
518
 
 
519
      svn_ra_serf__request_create(handler);
 
520
    }
 
521
 
 
522
  SVN_ERR(run_locks(session, lock_requests, TRUE, lock_func, lock_baton,
 
523
                    iterpool));
 
524
 
 
525
  svn_pool_destroy(iterpool);
 
526
 
 
527
  return SVN_NO_ERROR;
 
528
}
 
529
 
 
530
static svn_error_t *
 
531
set_unlock_headers(serf_bucket_t *headers,
 
532
                   void *baton,
 
533
                   apr_pool_t *pool /* request pool */,
 
534
                   apr_pool_t *scratch_pool)
 
535
{
 
536
  lock_ctx_t *ctx = baton;
 
537
 
 
538
  serf_bucket_headers_set(headers, "Lock-Token", ctx->token);
 
539
  if (ctx->force)
 
540
    {
 
541
      serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
 
542
                              SVN_DAV_OPTION_LOCK_BREAK);
 
543
    }
 
544
 
 
545
  return SVN_NO_ERROR;
 
546
}
 
547
 
 
548
svn_error_t *
 
549
svn_ra_serf__unlock(svn_ra_session_t *ra_session,
 
550
                    apr_hash_t *path_tokens,
 
551
                    svn_boolean_t force,
 
552
                    svn_ra_lock_callback_t lock_func,
 
553
                    void *lock_baton,
 
554
                    apr_pool_t *scratch_pool)
 
555
{
 
556
  svn_ra_serf__session_t *session = ra_session->priv;
 
557
  apr_hash_index_t *hi;
 
558
  apr_pool_t *iterpool;
 
559
  apr_array_header_t *lock_requests;
 
560
 
 
561
  iterpool = svn_pool_create(scratch_pool);
 
562
 
 
563
  /* If we are stealing locks we need the lock tokens */
 
564
  if (force)
 
565
    {
 
566
      /* Theoretically this part can be improved (for performance) by using
 
567
         svn_ra_get_locks() to obtain all the locks in a single request, but
 
568
         do we really want to improve the performance of
 
569
            $ svn unlock --force *
 
570
       */
 
571
 
 
572
      for (hi = apr_hash_first(scratch_pool, path_tokens);
 
573
       hi;
 
574
       hi = apr_hash_next(hi))
 
575
        {
 
576
          const char *path;
 
577
          const char *token;
 
578
          svn_lock_t *existing_lock;
 
579
          svn_error_t *err;
 
580
 
 
581
          svn_pool_clear(iterpool);
 
582
 
 
583
          path = apr_hash_this_key(hi);
 
584
          token = apr_hash_this_val(hi);
 
585
 
 
586
          if (token && token[0])
 
587
            continue;
 
588
 
 
589
          if (session->cancel_func)
 
590
            SVN_ERR(session->cancel_func(session->cancel_baton));
 
591
 
 
592
          err = svn_ra_serf__get_lock(ra_session, &existing_lock, path,
 
593
                                      iterpool);
 
594
 
 
595
          if (!err && existing_lock)
 
596
            {
 
597
              svn_hash_sets(path_tokens, path,
 
598
                            apr_pstrdup(scratch_pool, existing_lock->token));
 
599
              continue;
 
600
            }
 
601
 
 
602
          err = svn_error_createf(SVN_ERR_RA_NOT_LOCKED, err,
 
603
                                  _("'%s' is not locked in the repository"),
 
604
                                  path);
 
605
 
 
606
          if (lock_func)
 
607
            {
 
608
              svn_error_t *err2;
 
609
              err2 = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
 
610
              svn_error_clear(err);
 
611
 
 
612
              SVN_ERR(err2);
 
613
            }
 
614
          else
 
615
            {
 
616
              svn_error_clear(err);
 
617
            }
 
618
 
 
619
          svn_hash_sets(path_tokens, path, NULL);
 
620
        }
 
621
    }
 
622
 
 
623
  /* ### Perhaps we should open more connections than just one? See update.c */
 
624
 
 
625
  lock_requests = apr_array_make(scratch_pool, apr_hash_count(path_tokens),
 
626
                                 sizeof(lock_ctx_t*));
 
627
 
 
628
  for (hi = apr_hash_first(scratch_pool, path_tokens);
 
629
       hi;
 
630
       hi = apr_hash_next(hi))
 
631
    {
 
632
      svn_ra_serf__handler_t *handler;
 
633
      const char *req_url, *token;
 
634
      lock_ctx_t *lock_ctx;
 
635
      apr_pool_t *lock_pool;
 
636
 
 
637
      svn_pool_clear(iterpool);
 
638
 
 
639
      lock_pool = svn_pool_create(scratch_pool);
 
640
      lock_ctx = apr_pcalloc(lock_pool, sizeof(*lock_ctx));
 
641
 
 
642
      lock_ctx->pool = lock_pool;
 
643
 
 
644
      lock_ctx->path = apr_hash_this_key(hi);
 
645
      token = apr_hash_this_val(hi);
 
646
 
 
647
      lock_ctx->force = force;
 
648
      lock_ctx->token = apr_pstrcat(lock_pool, "<", token, ">", SVN_VA_NULL);
 
649
 
 
650
      req_url = svn_path_url_add_component2(session->session_url.path, lock_ctx->path,
 
651
                                            lock_pool);
 
652
 
 
653
      handler = svn_ra_serf__create_handler(session, lock_pool);
 
654
 
 
655
      handler->method = "UNLOCK";
 
656
      handler->path = req_url;
 
657
 
 
658
      handler->header_delegate = set_unlock_headers;
 
659
      handler->header_delegate_baton = lock_ctx;
 
660
 
 
661
      handler->response_handler = svn_ra_serf__expect_empty_body;
 
662
      handler->response_baton = handler;
 
663
 
 
664
      handler->no_fail_on_http_failure_status = TRUE;
 
665
 
 
666
      lock_ctx->handler = handler;
 
667
 
 
668
      APR_ARRAY_PUSH(lock_requests, lock_ctx_t *) = lock_ctx;
 
669
 
 
670
      svn_ra_serf__request_create(handler);
 
671
    }
 
672
 
 
673
  SVN_ERR(run_locks(session, lock_requests, FALSE, lock_func, lock_baton,
 
674
                    iterpool));
 
675
 
 
676
  svn_pool_destroy(iterpool);
 
677
 
 
678
  return SVN_NO_ERROR;
 
679
}