Hi!

I've discovered another problem related to locks.

Environment:

  Server:
    Windows Server 2008 R2 SP1
    Subversion 1.8.8,
    Apache HTTP Server 2.2.25.

    Autoversioning is enabled in Apache HTTP Server's settings:
    http://svnbook.red-bean.com/en/1.8/svn.webdav.autoversioning.html

  Client:
    Windows 7 SP1 64-bit,
    Internet Explorer 11.0.9600.16428.
    Microsoft Office 2010 (14.0.6023.1000 32-bit)

Description:

A file that has been opened via WebDAV in Microsoft Office application (e.g.
Word, Excel, PowerPoint etc) can't be saved back after applying changes to
it.
For example, MS Excel errors-out with the error (see the attached
screenshot):

[[
Upload Failed
This file is locked for editing by another user.
]]

The file is locked indeed, however it's locked by the same user who runs
Excel
so it's unexpected for a user to see such error.

Apache's log:

[[
Could not LOCK /svn/NewRepo/Book1237.xlsx due to a failed precondition (e.g.
other locks).  [412, #0]
The precondition(s) specified by the "If:" header did not match this
resource.
At least one failure is because: a State-token was supplied, but it was not
found in the locks on this resource.  [412, #0]
Path '/Book1237.xlsx' is already locked by user 'user' in filesystem
'93d13ee0-0fdb-6045-8702-4113dc399b89'  [423, #160035]
Failed to create new lock.  [423, #160035]
]]

What actually happens here: Excel tries to refresh existing lock using an
"If"
header (see section 7.8 of RFC 2518 [1]).  The supplied lock token is valid
but server responds with '412 Precondition Failed' instead of '200 OK'.
 Excel
tries to obtain a new lock on opened file, but request expectedly fails.

After some digging, I found out that correct behaviour was broken in
r859583 [2]
when fixing issue 2275 [3].

There is a hack in mod_dav_svn needed for support concept of lock stealing:

[[[
/* The Big Lie: if the client ran 'svn lock', then we have
   to pretend that there's no existing lock.  Otherwise mod_dav will
   throw '403 Locked' without even attempting to create a new
   lock.  For the --force case, this is required and for the non-force case,
   we allow the filesystem to produce a better error for svn clients.
*/
if (info->r->method_number == M_LOCK)
  {
    *locks = NULL;
    return 0;
  }
]]]

Before changeset [2] the condition was 'if (info->lock_steal)' and it worked
correctly in case of lock refreshing.  Now mod_dav_svn pretends that there
is no
locks set on resource for *ANY* LOCK request and that's why client getting
status '412 Precondition failed'.

I've attached patch with failing test that demonstrates this problem but
I'm not
sure how to fix it.  I see two ways:

  1. Revert changeset [2] and look for another way to fix issue [3].
  2. Extend condition with checking if an 'If' header is present.

Do you have any suggestions about it?

  [1] https://tools.ietf.org/html/rfc2518
  [2] http://svn.apache.org/r859583
  [3] http://subversion.tigris.org/issues/show_bug.cgi?id=2275

Thanks and regards,
Sergey Raevskiy
Index: subversion/tests/cmdline/lock_tests.py
===================================================================
--- subversion/tests/cmdline/lock_tests.py      (revision 1583942)
+++ subversion/tests/cmdline/lock_tests.py      (working copy)
@@ -2178,6 +2178,48 @@ def non_root_locks(sbox):
   expected_status.tweak('A/D/G/pi', writelocked=None)
   svntest.actions.run_and_verify_status(wc_dir, expected_status)
 
+@XFail()
+@SkipUnless(svntest.main.is_ra_type_dav)
+def dav_lock_refresh(sbox):
+  "refresh timeout of DAV lock"
+
+  # Around trunk@1581815 refreshing of DAV lock fails with error
+  # '412 Precondition Failed'
+
+  import httplib
+  from urlparse import urlparse
+  import base64
+
+  sbox.build(create_wc = False)
+
+  # Acquire lock on 'iota'
+  svntest.main.run_lock_helper(sbox.repo_dir, 'iota', 'jconstant', 3600)
+
+  # Try to refresh lock using 'If' header
+  loc = urlparse(sbox.repo_url)
+
+  if loc.scheme == 'http':
+    h = httplib.HTTPConnection(loc.hostname, loc.port)
+  else:
+    h = httplib.HTTPSConnection(loc.hostname, loc.port)
+
+  lock_token = svntest.actions.run_and_parse_info(sbox.repo_url + 
'/iota')[0]['Lock Token']
+
+  lock_headers = {
+    'Authorization': 'Basic ' + base64.b64encode('jconstant:rayjandom'),
+    'If': '(<' + lock_token + '>)',
+    'Timeout': 'Second-7200'
+  }
+
+  # Enabling the following line makes this test easier to debug
+  h.set_debuglevel(9)
+
+  h.request('LOCK', sbox.repo_url + '/iota', '', lock_headers)
+
+  r = h.getresponse()
+  if r.status != httplib.OK:
+    raise svntest.Failure('Lock refresh failed: %d %s' % (r.status, r.reason))
+
 ########################################################################
 # Run the tests
 
@@ -2238,6 +2280,7 @@ test_list = [ None,
               dav_lock_timeout,
               create_dav_lock_timeout,
               non_root_locks,
+              dav_lock_refresh,
             ]
 
 if __name__ == '__main__':

Reply via email to