railsdav 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +185 -0
- data/init.rb +0 -0
- data/lib/railsdav/controller_extensions.rb +55 -0
- data/lib/railsdav/renderer/response_collector.rb +32 -0
- data/lib/railsdav/renderer/response_type_selector.rb +62 -0
- data/lib/railsdav/renderer.rb +191 -0
- data/lib/railsdav/request_extensions.rb +26 -0
- data/lib/railsdav/routing_extensions.rb +213 -0
- data/lib/railsdav.rb +32 -0
- metadata +57 -0
data/README.md
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
# RailsDAV
|
2
|
+
|
3
|
+
Make your Rails 3 resources accessible via WebDAV
|
4
|
+
|
5
|
+
ProvidesThis gem provides basic Rails 3 extensions for making your business resources accessible via WebDAV. This gem does by no means by no means implement the full WebDAV semantics, but it suffices to access your app with client(-libs) such as Konqueror, cadaver, davfs2 or NetDrive.
|
6
|
+
|
7
|
+
## Compatibility
|
8
|
+
|
9
|
+
Definitely works with Rails 3.0.9 and 3.2.13, but should also work with versions in between.
|
10
|
+
This is due to some hacking done "under the hood" in the Rails routing implementation as well as a simple method override in ActionController.
|
11
|
+
|
12
|
+
f you encounter any problems with other Rails versions, please feel free to report an issue or send me a pull request on github.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Just at the following to your Gemfile
|
17
|
+
|
18
|
+
gem 'railsdav', :git => 'https://github.com/wvk/railsdav.git'
|
19
|
+
|
20
|
+
and then run
|
21
|
+
|
22
|
+
bundle install
|
23
|
+
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
TODO: Context
|
28
|
+
|
29
|
+
### Controller
|
30
|
+
|
31
|
+
#### Doing the Set-Up
|
32
|
+
|
33
|
+
Tell your controller which actions should be available as WebDAV resources, what media types should be advertised and whether or not they should be also available as collection resources (i.e. "folders"):
|
34
|
+
|
35
|
+
class FoosController < ApplicationController
|
36
|
+
enable_webdav_for :index,
|
37
|
+
:accept => :json
|
38
|
+
|
39
|
+
enable_webdav_for :show,
|
40
|
+
:accept => [:xml, :json],
|
41
|
+
:collection => false
|
42
|
+
|
43
|
+
# ...
|
44
|
+
|
45
|
+
This stores some metadata about your actions that can be accessed from another controller, in case this one acts as a subresource of another resource. You may provide an arbitrary list of media types ("formats") with the :accept options. This causes RailsDAV to advertise that resource as separate files, one for each provided media type.
|
46
|
+
By default, RailsDAV also advertises each resource as a collection ("folder"), containing a resource's subresources. You may choose not to let it do so by setting the :collection option to false (nil won't do, it really must be /false/).
|
47
|
+
|
48
|
+
# Handling PROPFIND Requests
|
49
|
+
|
50
|
+
As for the actions themselves they, too, need some attention. Collection resources usually contain subresources (otherwise, why bother?) that are specified in the format.webdav responder block:
|
51
|
+
|
52
|
+
# ...
|
53
|
+
|
54
|
+
def index
|
55
|
+
@foos = Foo.limit(100)
|
56
|
+
respond_to do |format|
|
57
|
+
# you already know this...
|
58
|
+
format.html
|
59
|
+
format.xml { render :json => @foos }
|
60
|
+
format.json { render :xml => @foos }
|
61
|
+
|
62
|
+
# and this one is new...
|
63
|
+
format.webdav do |dav|
|
64
|
+
@foos.each |foo|
|
65
|
+
dav.subresource foo_path(foo)
|
66
|
+
end
|
67
|
+
dav.format :xml, :size => @foos.to_json.size, :updated_at => @foos.maximum(:updated_at)
|
68
|
+
dav.format :json, :size => @foos.to_xml.size, :updated_at => @foos.maximum(:updated_at)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# ...
|
74
|
+
|
75
|
+
The webdav responder block is called when a PROPFIND request comes in. Within the block, we define a metadata set for each possible mime type that will be served via WebDAV. If the index resource is accessed as a collection (PROPFIND /foos), a multi-status response is rendered with the foos collection containting up to 100 foo entries as separate XML and JSON files:
|
76
|
+
|
77
|
+
PROPFIND /foos
|
78
|
+
|
79
|
+
will return a directory layout similar to:
|
80
|
+
|
81
|
+
/foos
|
82
|
+
-> /foos/1.xml
|
83
|
+
-> /foos/1.json
|
84
|
+
-> /foos/2.xml
|
85
|
+
-> /foos/2.json
|
86
|
+
...
|
87
|
+
|
88
|
+
If the index resource is accessed as XML (PROPFIND /foos.xml) or JSON (PROPFIND /foos.json), size and last-update of the XML/JSON "file" are included in the response. You can include that metatdata for each subresource, too:
|
89
|
+
|
90
|
+
format.webdav do |dav|
|
91
|
+
@foos.each do |foo|
|
92
|
+
dav.subresource foo_path(foo), :updated_at => foo.updated_at
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
the "dav.format" statements are entirely optional if you do not wish to include any metadata such as updated-at, created-at, or size. There's no need to be redundant here, since you already specified the most important metadata with "enable_webdav_for".
|
97
|
+
|
98
|
+
The show action looks similar.
|
99
|
+
|
100
|
+
def show
|
101
|
+
@foo = Foo.find(params[:id])
|
102
|
+
respond_to do |format|
|
103
|
+
format.html
|
104
|
+
format.json { render :json => @foo }
|
105
|
+
format.xml { render :xml => @foo }
|
106
|
+
|
107
|
+
format.webdav do |dav|
|
108
|
+
dav.format :xml, :size => @foo.to_xml.size, :updated_at => @foo.updated_at
|
109
|
+
dav.format :json, :size => @foo.to_json.size, :updated_at => @foo.updated_at
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
TODO: explain why respond_to is always needed
|
115
|
+
|
116
|
+
TODO: explain difference between PROPFIND /foos/1 and PROPFIND /foos/1.:format and medadata defined on class level and in responder block
|
117
|
+
|
118
|
+
### Routing
|
119
|
+
|
120
|
+
To make the rails routing mechanism webdav aware, RailsDAV extends it by means of some new DSL methods. Let's take the above example again and what the default routing would look like:
|
121
|
+
|
122
|
+
MyAwesomeApp::Application.routes.draw do
|
123
|
+
resources :foos
|
124
|
+
end
|
125
|
+
|
126
|
+
In order to accept PROPFIND and other WebDAV requests, you'll have to change it to look rather this way:
|
127
|
+
|
128
|
+
MyAwesomeApp::Application.routes.draw do
|
129
|
+
webdav_resources :foos
|
130
|
+
end
|
131
|
+
|
132
|
+
Easy enough. As you may have guessed, there exists also a "webdav_resource" counterpart for singleton resources and some more. By the way: the above also produces a route for OPTIONS requests that matches the capabilities of the resource. In case you only want the show and update action to be present, the following would also adapt the OPTIONS responder to only contain PROPFIND, GET and PUT:
|
133
|
+
|
134
|
+
webdav_resource :bar,
|
135
|
+
:only => %w(show update)
|
136
|
+
|
137
|
+
To enable propfind for a single action somewhere, use:
|
138
|
+
dav_propfind '/my-foo', :to => 'my_foos#show'
|
139
|
+
|
140
|
+
the other available helpers are: dav_copy, dav_move, dav_mkcol, dav_lock, dav_unlock, dav_proppatch.
|
141
|
+
|
142
|
+
### Authentication
|
143
|
+
|
144
|
+
RailsDAV does not do any authentication whatsoever, nor is there any sugar to go nicely with $your_favourite_authentication_gem. However, since cookie/session based authentication does not like to be friends with WebDAV, it's up to you to ensure Basic or Digest Authentication is used when a request from a WebDAV client comes in.
|
145
|
+
|
146
|
+
Assuming you have an application where resources are normally accessed as text/html but never so for WebDAV, a simple means of providing access control using HTTP Basic might look like this:
|
147
|
+
|
148
|
+
class ApplicationController < ActionController::Base
|
149
|
+
before_filter :authenticate_unless_session
|
150
|
+
|
151
|
+
protected
|
152
|
+
|
153
|
+
def authenticate_unless_session
|
154
|
+
# Always use Basic Authentication if the request method is one of WebDAV's
|
155
|
+
if is_webdav_request?
|
156
|
+
basic_auth
|
157
|
+
elsif request.format == Mime::HTML
|
158
|
+
# skip Basic Authentication and use anoher way
|
159
|
+
else
|
160
|
+
basic_auth # or whatever...
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def basic_auth
|
165
|
+
session = authenticate_with_http_basic {|usr, pwd| Session.new :login => usr, :password => pwd }
|
166
|
+
if session and session.valid?
|
167
|
+
@current_user = session.user
|
168
|
+
else
|
169
|
+
request_http_basic_authentication 'My Awesome App'
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
is_webdav_request? checks whether an Incoming request is issued by a WebDAV client using any specific HTTP verbs (such as PROPFIND, MKCOL, COPY, MOVE, etc.) or a media type other than text/html is requested.
|
175
|
+
|
176
|
+
## Changelog
|
177
|
+
|
178
|
+
* 0.0.5: Rails 3.1.x compatibility, add encoding hints, fix ResourceDescriptor load error
|
179
|
+
* 0.0.4: Basic support for allprop in PROPFIND
|
180
|
+
* 0.0.3: Change the API within the responder block to a more concise one
|
181
|
+
* 0.0.2: More or less a complete rewrite: Use more sensible API, modularize the renderer code, get rid of controller monkey patching
|
182
|
+
* 0.0.1: Initial Release: Basic support for PROPFIND and webdav_resource(s) based routing
|
183
|
+
|
184
|
+
Copyright (c) 2012 Willem van Kerkhof <wvk@consolving.de>, released under the MIT license
|
185
|
+
|
data/init.rb
ADDED
File without changes
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Railsdav
|
4
|
+
module ControllerExtensions
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :webdav_metadata
|
9
|
+
|
10
|
+
alias_method_chain :respond_to, :webdav
|
11
|
+
alias_method_chain :respond_with, :webdav
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def enable_webdav_for(*names_and_options, &block)
|
16
|
+
options = names_and_options.extract_options!
|
17
|
+
names = names_and_options
|
18
|
+
self.webdav_metadata ||= {}
|
19
|
+
|
20
|
+
options[:collection] = true unless options.has_key?(:collection)
|
21
|
+
|
22
|
+
names.each do |name|
|
23
|
+
self.webdav_metadata[name] = options
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def webdav_metadata_for_action(action)
|
28
|
+
webdav_metadata[action.to_sym]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# decorate behaviour defined in ActionController::MimeResponds
|
33
|
+
def respond_to_with_webdav(*mimes, &block)
|
34
|
+
if request.propfind?
|
35
|
+
render :webdav => :propstat, :respond_to_block => block
|
36
|
+
else
|
37
|
+
respond_to_without_webdav *mimes, &block
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# decorate behaviour defined in ActionController::MimeResponds
|
42
|
+
def respond_with_with_webdav(*resources, &block)
|
43
|
+
if request.propfind?
|
44
|
+
render :webdav => :propstat, :respond_to_block => block
|
45
|
+
else
|
46
|
+
respond_with_without_webdav *resources, &block
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def webdav_metadata_for_current_action
|
51
|
+
self.class.webdav_metadata_for_action params[:action]
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Railsdav
|
4
|
+
class Renderer
|
5
|
+
class ResponseCollector
|
6
|
+
attr_writer :controller
|
7
|
+
|
8
|
+
delegate :subresources,
|
9
|
+
:to => :@selector
|
10
|
+
|
11
|
+
def initialize(controller, request_format)
|
12
|
+
@controller, @request_format = controller, request_format
|
13
|
+
@selector = ResponseTypeSelector.new(controller, request_format)
|
14
|
+
end
|
15
|
+
|
16
|
+
def resource
|
17
|
+
@resource ||= Renderer::ResourceDescriptor.new(@controller.request.url, @selector.resource_options)
|
18
|
+
end
|
19
|
+
|
20
|
+
# responds to calls like html, xml, json by ignoring them
|
21
|
+
def method_missing(name, *args)
|
22
|
+
super unless Mime::EXTENSION_LOOKUP[name.to_s]
|
23
|
+
end
|
24
|
+
|
25
|
+
def webdav
|
26
|
+
if block_given?
|
27
|
+
yield @selector
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Railsdav
|
4
|
+
class Renderer
|
5
|
+
class ResponseTypeSelector
|
6
|
+
attr_reader :subresources, :resource_options
|
7
|
+
|
8
|
+
def initialize(controller, request_format)
|
9
|
+
@controller, @request_format = controller, request_format
|
10
|
+
@subresources = []
|
11
|
+
|
12
|
+
# TODO: somehow allow passing more options to current resource
|
13
|
+
@resource_options = {:format => @request_format.to_sym}
|
14
|
+
end
|
15
|
+
|
16
|
+
def format(name, options)
|
17
|
+
if Mime::EXTENSION_LOOKUP[name.to_s]
|
18
|
+
if @request_format.to_sym == name
|
19
|
+
# TODO: somehow get the attributes (size, updated-at, ...) from the actual Mime responder block here
|
20
|
+
@resource_options.merge! options
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise UnknownMimeTypeExtension, "#{name} is not a valid MIME type file extension."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# loop over all urls marked as subresources in webdav responder block
|
28
|
+
# and add them with all their acceptable mime types
|
29
|
+
def subresource(*subresources)
|
30
|
+
return unless @request_format == :collection
|
31
|
+
|
32
|
+
options = subresources.extract_options!
|
33
|
+
subresources.each do |resource_url|
|
34
|
+
route = Rails.application.routes.recognize_path(resource_url)
|
35
|
+
|
36
|
+
if meta = Renderer.webdav_metadata_for_url(route)
|
37
|
+
# show the resource as a collection unless disabled
|
38
|
+
if meta[:collection]
|
39
|
+
@subresources << Renderer::ResourceDescriptor.new(resource_url, options.merge(:format => :collection))
|
40
|
+
elsif meta[:accept].blank?
|
41
|
+
# show the resource without .:format suffix, but not as a collection
|
42
|
+
@subresources << Renderer::ResourceDescriptor.new(resource_url, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
# show the resource with all the specified format suffixes
|
46
|
+
if meta[:accept]
|
47
|
+
[meta[:accept]].flatten.each do |type_name|
|
48
|
+
mime_type = Mime::Type.lookup_by_extension(type_name)
|
49
|
+
subresource_url = @controller.url_for(route.merge(:format => type_name))
|
50
|
+
@subresources << Renderer::ResourceDescriptor.new(subresource_url, options.merge(:format => mime_type))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
raise MissingWebDAVMetadata, "no WebDAV metadata found for #{resource_url}, please specify it using #enable_webdav_for"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@resource_options[:size] = @subresources.size
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'builder'
|
4
|
+
|
5
|
+
module Railsdav
|
6
|
+
class Renderer
|
7
|
+
autoload :ResourceDescriptor, 'railsdav/renderer/resource_descriptor'
|
8
|
+
autoload :ResponseCollector, 'railsdav/renderer/response_collector'
|
9
|
+
autoload :ResponseTypeSelector, 'railsdav/renderer/response_type_selector'
|
10
|
+
|
11
|
+
# Return the webdav metadata for a given URL/path
|
12
|
+
#
|
13
|
+
# If the given route is recognized, a controller and action can be identified.
|
14
|
+
# To get the properties for that URI, we lookup the metadata
|
15
|
+
# specified in the target controller (see #enable_webdav_for)
|
16
|
+
#
|
17
|
+
def self.webdav_metadata_for_url(routing_data)
|
18
|
+
controller = "#{routing_data[:controller]}_controller".camelize.constantize
|
19
|
+
controller ? controller.webdav_metadata_for_action(routing_data[:action]) : nil
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :depth
|
23
|
+
|
24
|
+
# Create a new Renderer instance that will render an XML multistatus response.
|
25
|
+
#
|
26
|
+
# Arguments:
|
27
|
+
# controller: the current controller instance
|
28
|
+
#
|
29
|
+
def initialize(controller)
|
30
|
+
@controller = controller
|
31
|
+
@request = controller.request
|
32
|
+
@depth = (@request.headers['Depth'].to_i > 0) ? 1 : 0 # depth "infinite" is not yet supported
|
33
|
+
end
|
34
|
+
|
35
|
+
# Render the requested response_type.
|
36
|
+
#
|
37
|
+
# Arguments:
|
38
|
+
# response_type: currently either :propstat or :response.
|
39
|
+
# options:
|
40
|
+
# - for response: :href, :status, :error (see #response)
|
41
|
+
# - for propstat: :size, :format, :created_at, :updated_at, :resource_layout, ... (see #propstat)
|
42
|
+
def respond_with(response_type, options = {})
|
43
|
+
self.send response_type, options
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def request_format
|
49
|
+
if @controller.params[:format].blank?
|
50
|
+
if @controller.webdav_metadata_for_current_action[:collection]
|
51
|
+
return :collection
|
52
|
+
else
|
53
|
+
return @controller.request_format
|
54
|
+
end
|
55
|
+
else
|
56
|
+
return @controller.params[:format]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def render
|
61
|
+
@dav = Builder::XmlMarkup.new :indent => 2
|
62
|
+
@dav.instruct!
|
63
|
+
@dav.multistatus :xmlns => 'DAV:' do
|
64
|
+
yield @dav
|
65
|
+
end
|
66
|
+
@dav.target!
|
67
|
+
end
|
68
|
+
|
69
|
+
# Render a WebDAV multistatus response with a single "response" element.
|
70
|
+
# This is primarily intended vor responding to single resource errors.
|
71
|
+
#
|
72
|
+
# Arguments:
|
73
|
+
# options:
|
74
|
+
# - href: the requested resource URL, usually request.url
|
75
|
+
# - status: the response status, something like :unprocessable_entity or 204.
|
76
|
+
# - error: an Error description, if any.
|
77
|
+
#
|
78
|
+
def response(options = {})
|
79
|
+
elements = options.slice(:error)
|
80
|
+
|
81
|
+
render do
|
82
|
+
response_for options[:href] do |dav|
|
83
|
+
elements.each do |name, value|
|
84
|
+
status_for options[:status]
|
85
|
+
dav.__send__ name, value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def status_for(status)
|
92
|
+
code = Rack::Utils.status_code(status || :ok)
|
93
|
+
status = "HTTP/1.1 #{code} #{Rack::Utils::HTTP_STATUS_CODES[code]}"
|
94
|
+
@dav.status status
|
95
|
+
end
|
96
|
+
|
97
|
+
# Render a WebDAV multistatus response with a "response" element per resource
|
98
|
+
# TODO: explain the magic implemented here
|
99
|
+
# Arguments:
|
100
|
+
# TODO: describe valid arguments
|
101
|
+
#
|
102
|
+
def propstat(options = {})
|
103
|
+
response_collector = ResponseCollector.new(@controller, self.request_format)
|
104
|
+
# retrieve properties for this resource and all subresources if this is a collection
|
105
|
+
if options[:respond_to_block]
|
106
|
+
options[:respond_to_block].call(response_collector)
|
107
|
+
end
|
108
|
+
|
109
|
+
render do |dav|
|
110
|
+
propstat_for response_collector.resource
|
111
|
+
propstat_for response_collector.subresources if @depth > 0
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def propstat_for(*resources)
|
118
|
+
params = @controller.params
|
119
|
+
params[:propfind] ||= {:prop => []}
|
120
|
+
|
121
|
+
if params[:propfind].has_key? 'allprop'
|
122
|
+
requested_properties = nil # fill it later, see below.
|
123
|
+
else
|
124
|
+
requested_properties = params[:propfind][:prop]
|
125
|
+
end
|
126
|
+
|
127
|
+
resources.flatten.each do |resource|
|
128
|
+
hash = resource.props
|
129
|
+
|
130
|
+
case hash[:updated_at]
|
131
|
+
when Time, DateTime
|
132
|
+
updated_at = hash[:updated_at]
|
133
|
+
when String
|
134
|
+
updated_at = Time.parse(hash[:updated_at])
|
135
|
+
else
|
136
|
+
updated_at = Time.now
|
137
|
+
end
|
138
|
+
|
139
|
+
response_hash = {
|
140
|
+
:quota_used_bytes => 0,
|
141
|
+
:quota_available_bytes => 10.gigabytes,
|
142
|
+
:creationdate => updated_at.rfc2822,
|
143
|
+
:getlastmodified => updated_at.rfc2822,
|
144
|
+
:getcontentlength => hash[:size],
|
145
|
+
:getcontenttype => hash[:format].to_s
|
146
|
+
}
|
147
|
+
|
148
|
+
if resource.collection?
|
149
|
+
response_hash[:resourcetype] = proc { @dav.tag! :collection } # must be block to render <collection></collection> instead of "collection"
|
150
|
+
response_hash[:getcontenttype] = nil
|
151
|
+
end
|
152
|
+
|
153
|
+
requested_properties ||= response_hash.keys
|
154
|
+
|
155
|
+
response_for(resource.url) do |dav|
|
156
|
+
dav.propstat do
|
157
|
+
status_for hash[:status]
|
158
|
+
dav.prop do
|
159
|
+
requested_properties.each do |prop_name, opts|
|
160
|
+
if prop_val = response_hash[prop_name.to_sym]
|
161
|
+
if prop_val.respond_to? :call
|
162
|
+
if opts
|
163
|
+
dav.tag! prop_name, opts, &prop_val
|
164
|
+
else
|
165
|
+
dav.tag! prop_name, &prop_val
|
166
|
+
end
|
167
|
+
else
|
168
|
+
dav.tag! prop_name, prop_val, opts
|
169
|
+
end
|
170
|
+
elsif opts
|
171
|
+
dav.tag! prop_name, opts
|
172
|
+
else
|
173
|
+
dav.tag! prop_name
|
174
|
+
end
|
175
|
+
end # requested_properties.each
|
176
|
+
end # dav.prop
|
177
|
+
end # dav.propstat
|
178
|
+
end # dav.response_for
|
179
|
+
end # resource_layout.keys.each
|
180
|
+
end # def propstat_for
|
181
|
+
|
182
|
+
def response_for(href)
|
183
|
+
@dav.response do
|
184
|
+
@dav.href href
|
185
|
+
yield @dav
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end # class Railsdav
|
190
|
+
end # module Railsdav
|
191
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Railsdav
|
4
|
+
module RequestExtensions
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
Railsdav::WEBDAV_HTTP_VERBS.each do |verb|
|
9
|
+
method_name = "#{verb.underscore}?"
|
10
|
+
|
11
|
+
# just to make sure we don't accidentally break things...
|
12
|
+
raise "#{method_name} is already defined in #{self.class}!" if respond_to? method_name
|
13
|
+
|
14
|
+
define_method method_name do
|
15
|
+
ActionDispatch::Request::HTTP_METHOD_LOOKUP[request_method] == verb.underscore.to_sym
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def webdav?
|
22
|
+
Railsdav::WEBDAV_HTTP_VERBS.include? request_method
|
23
|
+
end
|
24
|
+
|
25
|
+
end # module RequestExtensions
|
26
|
+
end # module Railsdav
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Allow usage of WebDAV specific HTTP verbs
|
4
|
+
# userinfo is not a standard webdav verb, bur davfs2 uses it
|
5
|
+
# and we want to be prepared to ignore it gracefully (e.g.
|
6
|
+
# by sending a :not_implemented response)
|
7
|
+
verbs = %w(propfind proppatch mkcol copy move lock unlock userinfo)
|
8
|
+
verbs.each do |method|
|
9
|
+
ActionDispatch::Request::HTTP_METHODS << method.upcase
|
10
|
+
ActionDispatch::Request::HTTP_METHOD_LOOKUP[method.upcase] = method.to_sym
|
11
|
+
end
|
12
|
+
|
13
|
+
# Extend routing s.t. webdav_resource and webdav_resources can be used,
|
14
|
+
# enabling things like PROPFIND /foo/index.(:format) and such.
|
15
|
+
#
|
16
|
+
# ATTENTION: adapt this to newer rails version if upgrading the framework!
|
17
|
+
class ActionDispatch::Routing::Mapper
|
18
|
+
module HttpHelpers
|
19
|
+
def dav_propfind(*args, &block)
|
20
|
+
map_method(:propfind, *args, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def dav_options(*args, &block)
|
24
|
+
map_method(:options, *args, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def dav_copy(*args, &block)
|
28
|
+
map_method(:copy, *args, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def dav_move(*args, &block)
|
32
|
+
map_method(:move, *args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def dav_mkcol(*args, &block)
|
36
|
+
map_method(:mkcol, *args, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def dav_lock(*args, &block)
|
40
|
+
map_method(:lock, *args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def dav_unlock(*args, &block)
|
44
|
+
map_method(:unlock, *args, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def dav_proppatch(*args, &block)
|
48
|
+
map_method(:proppatch, *args, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module Resources
|
53
|
+
CANONICAL_ACTIONS << :update_all
|
54
|
+
|
55
|
+
class WebDAVResource < Resource
|
56
|
+
DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit, :update_all]
|
57
|
+
end
|
58
|
+
|
59
|
+
class WebDAVSingletonResource < SingletonResource
|
60
|
+
DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
|
61
|
+
end
|
62
|
+
|
63
|
+
def resource_scope?
|
64
|
+
[:webdav_resource, :webdav_resources, :resource, :resources].include?(@scope[:scope_level])
|
65
|
+
end
|
66
|
+
|
67
|
+
if Rails.version <= '3.1.0'
|
68
|
+
# Rails versions after 3.1 expect two arguments here, the first being :resource, :resources,
|
69
|
+
# :webdav_resource etc.so we don't need the inferring logic anymore in newer versions.
|
70
|
+
def resource_scope(resource)
|
71
|
+
case resource
|
72
|
+
when WebDAVSingletonResource
|
73
|
+
scope_level = :webdav_resource
|
74
|
+
when WebDAVResource
|
75
|
+
scope_level = :webdav_resources
|
76
|
+
when SingletonResource
|
77
|
+
scope_level = :resource
|
78
|
+
when Resource
|
79
|
+
scope_level = :resources
|
80
|
+
end
|
81
|
+
with_scope_level(scope_level, resource) do
|
82
|
+
scope(parent_resource.resource_scope) do
|
83
|
+
yield
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def dav_options_response(*allowed_http_verbs)
|
90
|
+
lambda { [200, {'Allow' => allowed_http_verbs.flatten.map{|s| s.to_s.upcase}.join(' '), 'DAV' => '1'}, ''] }
|
91
|
+
end
|
92
|
+
|
93
|
+
def dav_match(*args)
|
94
|
+
get *args
|
95
|
+
dav_propfind *args
|
96
|
+
end
|
97
|
+
|
98
|
+
def webdav_resource(*resources, &block)
|
99
|
+
options = resources.extract_options!
|
100
|
+
|
101
|
+
if apply_common_behavior_for(:webdav_resource, resources, options, &block)
|
102
|
+
return self
|
103
|
+
end
|
104
|
+
|
105
|
+
sub_block = lambda do
|
106
|
+
yield if block_given?
|
107
|
+
|
108
|
+
if parent_resource.actions.include?(:create)
|
109
|
+
collection do
|
110
|
+
post :create
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
if parent_resource.actions.include?(:new)
|
115
|
+
new do
|
116
|
+
get :new
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
member do
|
121
|
+
if parent_resource.actions.include?(:show)
|
122
|
+
dav_match :show
|
123
|
+
end
|
124
|
+
get :edit if parent_resource.actions.include?(:edit)
|
125
|
+
put :update if parent_resource.actions.include?(:update)
|
126
|
+
delete :destroy if parent_resource.actions.include?(:destroy)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
if Rails.version <= '3.1'
|
131
|
+
resource_scope(WebDAVSingletonResource.new(resources.pop, options), &sub_block)
|
132
|
+
else
|
133
|
+
resource_scope(:webdav_resource, WebDAVSingletonResource.new(resources.pop, options), &sub_block)
|
134
|
+
end
|
135
|
+
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
def webdav_resources(*resources, &block)
|
140
|
+
options = resources.extract_options!
|
141
|
+
|
142
|
+
if apply_common_behavior_for(:webdav_resources, resources, options, &block)
|
143
|
+
return self
|
144
|
+
end
|
145
|
+
|
146
|
+
sub_block = lambda do
|
147
|
+
yield if block_given?
|
148
|
+
|
149
|
+
opts = []
|
150
|
+
collection do
|
151
|
+
if parent_resource.actions.include?(:index)
|
152
|
+
dav_match :index
|
153
|
+
opts << [:get, :propfind]
|
154
|
+
end
|
155
|
+
|
156
|
+
if parent_resource.actions.include?(:create)
|
157
|
+
post :create
|
158
|
+
opts << :post
|
159
|
+
end
|
160
|
+
|
161
|
+
if parent_resource.actions.include?(:update_all)
|
162
|
+
put :index, :action => :update_all
|
163
|
+
opts << :put
|
164
|
+
end
|
165
|
+
dav_options :index, :to => dav_options_response(opts)
|
166
|
+
end
|
167
|
+
|
168
|
+
if parent_resource.actions.include?(:new)
|
169
|
+
new do
|
170
|
+
dav_match :new
|
171
|
+
put :new, :action => :create
|
172
|
+
dav_options :new, :to => dav_options_response(:get, :put, :propfind, :options)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
member do
|
177
|
+
opts = []
|
178
|
+
if parent_resource.actions.include?(:show)
|
179
|
+
dav_match :show
|
180
|
+
opts << :get
|
181
|
+
opts << :propfind
|
182
|
+
end
|
183
|
+
|
184
|
+
if parent_resource.actions.include?(:update)
|
185
|
+
put :update
|
186
|
+
opts << :put
|
187
|
+
end
|
188
|
+
|
189
|
+
if parent_resource.actions.include?(:destroy)
|
190
|
+
delete :destroy
|
191
|
+
opts << :delete
|
192
|
+
end
|
193
|
+
|
194
|
+
dav_options :show, :to => dav_options_response(opts)
|
195
|
+
|
196
|
+
if parent_resource.actions.include?(:edit)
|
197
|
+
dav_match :edit
|
198
|
+
dav_options :edit, :to => dav_options_response(:get, :propfind)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
if Rails.version <= '3.1'
|
204
|
+
resource_scope(WebDAVResource.new(resources.pop, options), &sub_block)
|
205
|
+
else
|
206
|
+
resource_scope(:webdav_resources, WebDAVResource.new(resources.pop, options), &sub_block)
|
207
|
+
end
|
208
|
+
|
209
|
+
self
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
data/lib/railsdav.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Railsdav
|
4
|
+
autoload :ControllerExtensions, 'railsdav/controller_extensions'
|
5
|
+
autoload :RoutingExtensions, 'railsdav/routing_extensions'
|
6
|
+
autoload :RequestExtensions, 'railsdav/request_extensions'
|
7
|
+
autoload :Renderer, 'railsdav/renderer'
|
8
|
+
|
9
|
+
class MissingWebDAVMetadata < StandardError; end
|
10
|
+
|
11
|
+
WEBDAV_HTTP_VERBS = ActionDispatch::Request::RFC2518
|
12
|
+
|
13
|
+
Mime::Type.register_alias 'application/xml', :webdav
|
14
|
+
|
15
|
+
ActionController::Renderers.add :webdav do |response_type, options|
|
16
|
+
renderer = Railsdav::Renderer.new(self)
|
17
|
+
xml_str = renderer.respond_with response_type, options
|
18
|
+
|
19
|
+
Rails.logger.debug "WebDAV response:\n#{xml_str}"
|
20
|
+
|
21
|
+
response.headers['Depth'] = renderer.depth.to_s
|
22
|
+
response.headers['DAV'] = '1'
|
23
|
+
|
24
|
+
send_data xml_str,
|
25
|
+
:content_type => Mime::XML,
|
26
|
+
:status => :multi_status
|
27
|
+
end
|
28
|
+
|
29
|
+
ActionController::Base.send :include, Railsdav::ControllerExtensions
|
30
|
+
ActionDispatch::Request.send :include, Railsdav::RequestExtensions
|
31
|
+
|
32
|
+
end # module Railsdav
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: railsdav
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Willem van Kerkhof
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-18 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Provides basic Rails 3/Rails 4 extensions for making your business resources
|
15
|
+
accessible via WebDAV. This gem does by no means by no means implement the full
|
16
|
+
WebDAV semantics, but it suffices to access your app with client(-libs) such as
|
17
|
+
Konqueror, cadaver, davfs2 or NetDrive
|
18
|
+
email: wvk@consolving.de
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- README.md
|
24
|
+
- init.rb
|
25
|
+
- lib/railsdav.rb
|
26
|
+
- lib/railsdav/routing_extensions.rb
|
27
|
+
- lib/railsdav/request_extensions.rb
|
28
|
+
- lib/railsdav/renderer/response_collector.rb
|
29
|
+
- lib/railsdav/renderer/response_type_selector.rb
|
30
|
+
- lib/railsdav/controller_extensions.rb
|
31
|
+
- lib/railsdav/renderer.rb
|
32
|
+
homepage: http://github.com/wvk/railsdav
|
33
|
+
licenses: []
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.8.23
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: Make your Rails 3/4 resources accessible via WebDAV
|
56
|
+
test_files: []
|
57
|
+
has_rdoc:
|