Microsoft WebDav opens document as Read-Only when using RailsDav
This post was written by Joseph Jakuta. Read other posts by Joseph Jakuta.
I had been working on a project in which we wanted to utilize WebDAV (namely for editing Word & Excel Documents that were saved in our application). In order to do this we decided to use a plugin from liverail.net that can be found here. It was pretty easy to hook up after a little direction from a guy over at Benryan Inc [apologies I cannot find a link for them], but there was a major issue. When opening a document through the ActiveX controller for editing it was opening in Read-Only mode.
After a few starts and stops, many hours of reading through the webdav documentation, and browsing through the http traffic using Fiddler - it was determined that locking was the issue.
What I found in the RailsDav plugin is that the implementation of locking is as follows (lines 92-100 of railsdav/act_as_railsdav.rb):
def webdav_lock() #TODO implementation for now return a 200 OK render :nothing => true, :status => 200 and return end def webdav_unlock() #TODO implementation for now return a 200 OK render :nothing => true, :status => 200 and return end
However, Microsoft actually won’t let you edit a document with such lax locking. So I rewrote the lock method so that it returned a valid lock.
def webdav_lock() @lock = get_lock(@path_info) if @lock response.headers["Lock-Token"] = "<#{@lock.token}>" response.headers["Content-Type"] = 'text/xml; charset="utf-8"' render :inline => self.class.lock_xml, :layout => false, :type => :rxml, :status => 200 and return else render :nothing => true, :status => WebDavErrors::ForbiddenError and return end end
There were a few other pieces of code that were needed to get this working though. Namely the .lock_xml method, and get_lock. Let’s tackle them each in turn.
In railsdav/propxml.rb you can find the XML structures for the PROPFIND and the PROPPATCH methods for WebDAV. However both locking and unlocking require their own structures. So I added one for locking. It is as follows:
def lock_xml <<EOPROPFIND_XML xml.D(:multistatus, {"xmlns:D" => "DAV:"}) do xml.D :lockdiscovery do xml.D :activelock do xml.D :locktype do xml.D @lock.type.to_sym end xml.D :lockscope do xml.D @lock.scope.to_sym end xml.D :depth, @lock.depth xml.D :timeout, @lock.timeout_full xml.D :locktoken do xml.D :href, @lock.token end xml.D :lockroot do xml.D :href, @lock.href end end end end EOPROPFIND_XML end
Then there is the get_lock method. Writing get_lock was a two step process. If you look at (lines 269-287 of railsdav/act_as_railsdav.rb) you can see several methods that are very similar in function to their respective purposes. So I began by simply adding the following in this section:
def get_lock(path) raise WebDavErrors::ForbiddenError end
Then in my application’s controller that overrides all of the acts_as_railsdav methods I did just that:
def get_lock(path) return false unless get_resource_for_path(path).locked? return ActiveRecordLock.new({:type => 'write', :scope => 'exclusive', :timeout => 60*60*5, :href => path, :id =>4}) end
So now you’ll notice two new things that need coding here. The .locked? method for the resource and ActiveRecordLock. We decided to not worry about multiple users editing the same document at the same time on this project. Because of this and the time constraints .locked? simply returns true. If one were to deal with unlocking at some point this would have to be come a check to the database or something to make sure that the document is indeed unlocked. The same goes for ActiveRecordLock. It really just deals with the flat information needed by the Lock XML and not saving it to check if it is locked later on. Maybe someday, somebody will undertake that task I suppose. Anyway, here is what I had for the portion of the ActiveRecordLock model that I needed.
class ActiveRecordLock attr_accessor :type, :scope, :timeout, :depth, :timeout_units, :href, :token def initialize(args) @type = args[:type] @scope = args[:scope] @timeout = args[:timeout] @href = args[:href] @depth = 'Infinity' @timeout_units = 'Second' @token = build_token(args[:id]) end def timeout_full "#{self.timeout_units}-#{self.timeout}" end protected def build_token(text) require 'digest/md5' md5 = Digest::MD5.hexdigest(text.to_s).to_s 'opaquelocktoken:'+md5[0,7]+'-'+md5[8,11]+'-'+md5[12,15]+'-'+md5[16,19]+'-'+md5[20,31] end end
There is still plenty that needs to be done to get the full lock/unlock, but hopefully this will get someone started and save a few headaches.
About the author:
August 21st, 2008 at 12:00 am
[...] - bookmarked by 1 members originally found by TraXStarMoney on 2008-07-27 Microsoft WebDav opens document as Read-Only when using RailsDav [...]