My Secret Life as a Spaghetti Coder
home | about | contact | privacy statement
I recently made a proof-of-concept to get MS Word to edit documents in a Rails app and save them back to the server, using Devise for authentication. It wasn't straightforward, so I thought I'd mention the steps I took to get it done.

I used Rails 3.2.12 with rack_dav providing the WebDAV functionality. There's also dav4rack which was based on rack_dav, and adds a few features.

I started with rack_dav, and had all of the kinks worked out except for the authentication, when I noticed dav4rack included authentication. So I switched to dav4rack, but I had some trouble with it, so I switched back to rack_dav because I feared I'd get into another day-or-two-long yak shaving session.

You might check it out though, and see if it works just as well.

Anyway, here's the basis of what I did.

1. Add the gem to my Gemfile (and run bundle install):

gem 'rack_dav'

2. Mount it in my routes.rb, wrapped with basic authentication:

    webdav = RackDAV::Handler.new(:root => 'private/docs/', :resource_class => LockableFileResource)
    webdav_with_auth = Rack::Auth::Basic.new(webdav) do |username, password|
      user = User.find_for_authentication(:email => username)
      user && user.valid_password?(password)
    end    
    mount webdav_with_auth, at: "/webdav_docs"

3. Create my lockable file resource, which doesn't really do any locking (since this was just a PoC), so you probably want to fix that:

class AuthenticableFileResource < RackDAV::FileResource
  def lock(locktoken, timeout, lockscope=nil, locktype=nil, owner=nil)
    puts "token: #{locktoken}"
    puts "timeout: #{timeout}"
    puts "scope: #{lockscope}"
    puts "type: #{locktype}"
    puts "owner: #{owner}"
    @@owner ||= owner
    @@locktype ||= 'exclusive'
    @@lockscope ||= 'all day'
    return [60, @@lockscope, @@locktype, @@owner]
  end
  
  def unlock(a)
    @@owner = nil
    @@locktype = nil
    @@lockscope = nil
    return true
  end
end

4. Tell the browser to instruct Word to open the file: For users on the web, we don't want the default rendering of the WebDAV root, because we'd like these docs to be editable in Word, which means you have to tell Word to open it. While you could add this to your own resource collection, I just created an action to list the links to the files with the code to open it in Word. Here's the basis of what you need in the view:

<script>
  function officelink(link) {
    try {
      new ActiveXObject("SharePoint.OpenDocuments.4").EditDocument(link.href);
      return false;
    }
    catch(e) {
      try {
        document.getElementById("winFirefoxPlugin").EditDocument(link.href);
        return false;
      }
      catch(e2) {
        return true;
      }
    }
    
  }
</script>
  
<a href="/webdav_docs/inetfilter.doc" onclick="return officelink(this)">inetfilter.doc

<object id="winFirefoxPlugin" type="application/x-sharepoint" width="0" height="0" style="visibility: hidden;"></object>

Users who have Office 2010 installed on their system will also have those 2 plugins installed on their system, so we try to use them and return false (preventing the browser from following the link) if they have it. For those who don't, the JS provides a way to just download the file. See OpenDocuments Control API and FFWinPlugin Plug-in API for more info about what you can do with those plugins.

5. Tell Rails not to parse params for our mounted rack_dav app. For whatever reason, Word will try to save the file back to the server, PUTting it with the mime type application/xml, but it doesn't wrap in XML. I don't suppose it's supposed to, but it would be nice if it played well with Rails. Unfortunately, Rails understandably barfs when you tell it to parse parameters from XML and didn't send any XML. So, I had to create a patch and stuck it in an initializer. All we do is figure out if the request is for where we mounted rack_dav: if so, let rack_dav handle it without parsing the params. If not, just parse the params as normal:

module ActionDispatch
  class ParamsParser
    old_call = instance_method(:call)
    define_method(:call) do |env|
      if env["PATH_INFO"] =~ /\/webdav_docs\//
        @app.call(env)
      else
        old_call.bind(self).(env)
      end
    end
  end
end

There may be some security problems with this if rack_dav parses request parameters like Rails did before they put out their security updates in January and February 2013. I plan to review the rack_dav code to make sure it doesn't expose the same problems Rails had before I put this in production, and I'd recommend you do the same before you do.

6. Tell Devise not to authenticate OPTIONS or PROPFIND requests. This is needed if you redirect your root url to a protected controller/action, because when Word makes requests using these methods, you'll get redirected and asked for authentication on a web page, which totally breaks the whole concept. This is probably in your application_controller.rb, unless you moved it. So if you moved it, you'll need to make the change there.

class ApplicationController < ActionController::Base
  protect_from_forgery :unless=>:options_request?
  before_filter :authenticate_user!, :unless=>:options_request?
  
  def options_request?
    request.method == "OPTIONS" || request.method == "PROPFIND"
  end
end

That's all. Feel free to ask any questions if something is unclear or not working for you!

Hey! Why don't you make your life easier and subscribe to the full post or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!


Comments
Leave a comment

Hi Sam,
I have a rails3 app. Got to implement the same functionality as you did. There are some uploaded word files. On click of "edit" link, the file need to be opened, give interface to edit and then save the file with proper formatting. Can you suggest any kind of gem or third-party service to do this. I have very less time to implement this functionality. Can you help me with short example.

Thanks in advance.

Posted by Rakesh Jha on Jul 02, 2013 at 01:19 AM UTC - 5 hrs

Rakesh,

Unfortunately, I don't know of any gems to do this directly. The instructions above would work, but you'll need to put in some more effort to correctly lock the files, and audit the potential security issue I mentioned above.

On the other hand, it sounds like maybe you could edit them in the browser, so you might consider just storing them in HTML format using a typical wysiwyg editor, provide a download link for Word, and use something like https://github.com/aquasync/ruby-ole to save as doc format (but this probably would only work well if you have a server that can run Windows, since it'll need word installed on it to do the automation, and even at that, you probably can't have more than a couple of people using it at the same time).

You might also have a look at Apache POI to see if it meets your needs: http://poi.apache.org/hwpf/index.html

Finally, Microsoft has explained how to build via XSLT, though I've not gone through it myself: http://msdn.microsoft.com/en-us/library/office/ee8...(v=office.12).aspx

Sam

Posted by Sammy Larbi on Jul 02, 2013 at 06:22 AM UTC - 5 hrs

Hello Sam. Nice article! Would you be so kind to consult me?
I'm working on office documents editing using WebDAV on ASP.NET application. We use plugins, that you have talken about.
And IE10, FireFox and Chrome browsers works fine, but it seems that FireFox and Chrome used cookies from IE. If IE cookies are cleared, FireFox and Chrome cann't open office documents and OPTIONS/PROPFIND/... requests have no cookies on it headers.
Maybe you have a clue how to fix it? why requests through FFWinPlugin have no cookies on it?

A big thanks in advance.

Posted by Onyx on Jul 12, 2013 at 11:25 AM UTC - 5 hrs

Sorry Onyx, I don't have any idea. Microsoft forums might be a better place to check. I've certainly seen some obscure questions answered there.

Posted by Sammy Larbi on Jul 13, 2013 at 06:11 PM UTC - 5 hrs

Leave a comment

Leave this field empty
Your Name
Email (not displayed, more info?)
Website

Comment:

Subcribe to this comment thread
Remember my details
Google
Web CodeOdor.com

Me
Picture of me

Topics
.NET (19)
AI/Machine Learning (14)
Answers To 100 Interview Questions (10)
Bioinformatics (2)
Business (1)
C and Cplusplus (6)
cfrails (22)
ColdFusion (78)
Customer Relations (15)
Databases (3)
DRY (18)
DSLs (11)
Future Tech (5)
Games (5)
Groovy/Grails (8)
Hardware (1)
IDEs (9)
Java (38)
JavaScript (4)
Linux (2)
Lisp (1)
Mac OS (4)
Management (15)
MediaServerX (1)
Miscellany (76)
OOAD (37)
Productivity (11)
Programming (168)
Programming Quotables (9)
Rails (31)
Ruby (67)
Save Your Job (58)
scriptaGulous (4)
Software Development Process (23)
TDD (41)
TDDing xorblog (6)
Tools (5)
Web Development (8)
Windows (1)
With (1)
YAGNI (10)

Resources
Agile Manifesto & Principles
Principles Of OOD
ColdFusion
CFUnit
Ruby
Ruby on Rails
JUnit



RSS 2.0: Full Post | Short Blurb
Subscribe by email:

Delivered by FeedBurner