dav4rack license #940
This commit is contained in:
parent
07d272daa4
commit
dcc5341cae
42
lib/dav4rack/LICENSE
Normal file
42
lib/dav4rack/LICENSE
Normal file
@ -0,0 +1,42 @@
|
||||
Current DAV4Rack license:
|
||||
|
||||
Copyright (c) 2010 Chris Roberts <chrisroberts.code@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
Original rack_dav source license:
|
||||
|
||||
Copyright (c) 2009 Matthias Georgi <http://www.matthias-georgi.de>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
467
lib/dav4rack/README.md
Normal file
467
lib/dav4rack/README.md
Normal file
@ -0,0 +1,467 @@
|
||||
# DAV4Rack - Web Authoring for Rack[](https://travis-ci.org/planio-gmbh/dav4rack)
|
||||
|
||||
DAV4Rack is a framework for providing WebDAV via Rack allowing content
|
||||
authoring over HTTP. It is based off the [original RackDAV
|
||||
framework](http://github.com/georgi/rack_dav) adding some useful new features:
|
||||
|
||||
- Better resource support for building fully virtualized resource structures
|
||||
- Generic locking as well as Resource level specific locking
|
||||
- Interceptor middleware to provide virtual mapping to resources
|
||||
- Mapped resource paths
|
||||
- Authentication support
|
||||
- Resource callbacks
|
||||
- Remote file proxying (including sendfile support for remote files)
|
||||
- Nokogiri based document parsing
|
||||
- Ox based XML document building (for performance reasons)
|
||||
|
||||
If you find issues, please create a new issue on github. If you have fixes,
|
||||
please fork the repo and send me a pull request with your modifications. If you
|
||||
are just here to use the library, enjoy!
|
||||
|
||||
|
||||
## About this fork
|
||||
|
||||
This is the [Planio](https://plan.io/redmine-hosting) fork of DAV4Rack. The
|
||||
master branch includes improvements and fixes done by @djgraham and
|
||||
@tim-vandecasteele in their respective forks on Github.
|
||||
|
||||
It also incorporates various fixes that were made as part of the redmine\_dmsf
|
||||
plugin, as well as improvements done by ourselves during development of an
|
||||
upcoming redmine document management plugin.
|
||||
|
||||
Several core APIs were changed in the process so it will not be a straight
|
||||
upgrade for applications that were developed with DAV4Rack 0.3 (the last
|
||||
released Gem version).
|
||||
|
||||
## Install
|
||||
|
||||
### Bundler
|
||||
|
||||
To use this fork, include in your Gemfile:
|
||||
|
||||
gem 'dav4rack', git: 'https://github.com/planio-gmbh/dav4rack.git', branch: 'master'
|
||||
|
||||
|
||||
### Via RubyGems
|
||||
|
||||
gem install dav4rack
|
||||
|
||||
This will give you the last officially released version, which is *very* old.
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
- [DAV4Rack documentation](http://chrisroberts.github.com/dav4rack)
|
||||
|
||||
## Quickstart
|
||||
|
||||
If you just want to share a folder over WebDAV, you can just start a
|
||||
simple server with:
|
||||
|
||||
dav4rack
|
||||
|
||||
This will start a Unicorn, Mongrel or WEBrick server on port 3000, which you
|
||||
can connect to without authentication. Unicorn and Mongrel will be much more
|
||||
responsive than WEBrick, so if you are having slowness issues, install one of
|
||||
them and restart the dav4rack process. The simple file resource allows very
|
||||
basic authentication which is used for an example. To enable it:
|
||||
|
||||
dav4rack --username=user --password=pass
|
||||
|
||||
|
||||
## Rack Handler
|
||||
|
||||
Using DAV4Rack within a rack application is pretty simple. A very slim
|
||||
rackup script would look something like this:
|
||||
|
||||
|
||||
```ruby
|
||||
require 'rubygems'
|
||||
require 'dav4rack'
|
||||
|
||||
use Rack::CommonLogger
|
||||
run DAV4Rack::Handler.new(root: '/path/to/public/fileshare')
|
||||
```
|
||||
|
||||
This will use the included FileResource and set the share path. However,
|
||||
DAV4Rack has some nifty little extras that can be enabled in the rackup script.
|
||||
First, an example of how to use a custom resource:
|
||||
|
||||
```ruby
|
||||
run DAV4Rack::Handler.new(resource_class: CustomResource,
|
||||
custom: 'options',
|
||||
passed: 'to resource')
|
||||
```
|
||||
|
||||
Next, lets venture into mapping a path for our WebDAV access. In this example,
|
||||
we will use default FileResource like in the first example, but instead of the
|
||||
WebDAV content being available at the root directory, we will map it to a
|
||||
specific directory: `/webdav/share/`
|
||||
|
||||
```ruby
|
||||
require 'rubygems'
|
||||
require 'dav4rack'
|
||||
|
||||
use Rack::CommonLogger
|
||||
|
||||
app = Rack::Builder.new{
|
||||
map '/webdav/share/' do
|
||||
run DAV4Rack::Handler.new(root: '/path/to/public/fileshare')
|
||||
end
|
||||
}.to_app
|
||||
run app
|
||||
```
|
||||
|
||||
Aside from the `Builder#map` block, notice the new option passed to the Handler's
|
||||
initialization, `:root_uri_path`. When DAV4Rack receives a request, it will
|
||||
automatically convert the request to the proper path and pass it to the
|
||||
resource.
|
||||
|
||||
Another tool available when building the rackup script is the Interceptor. The
|
||||
Interceptor's job is to simply intercept WebDAV requests received up the path
|
||||
hierarchy where no resources are currently mapped. For example, lets continue
|
||||
with the last example but this time include the interceptor:
|
||||
|
||||
|
||||
```ruby
|
||||
require 'rubygems'
|
||||
require 'dav4rack'
|
||||
|
||||
use Rack::CommonLogger
|
||||
app = Rack::Builder.new{
|
||||
map '/webdav/share/' do
|
||||
run DAV4Rack::Handler.new(root: '/path/to/public/fileshare')
|
||||
end
|
||||
map '/webdav/share2/' do
|
||||
run DAV4Rack::Handler.new(resource_class: CustomResource)
|
||||
end
|
||||
map '/' do
|
||||
use DAV4Rack::Interceptor, mappings: {
|
||||
'/webdav/share/' => {resource_class: FileResource, custom: 'option'},
|
||||
'/webdav/share2/' => {resource_class: CustomResource}
|
||||
}
|
||||
use Rails::Rack::Static
|
||||
run ActionController::Dispatcher.new
|
||||
end
|
||||
}.to_app
|
||||
run app
|
||||
```
|
||||
|
||||
In this example we have two WebDAV resources restricted by path. This means
|
||||
those resources will handle requests to `/webdav/share/* and /webdav/share2/*`
|
||||
but nothing above that. To allow webdav to respond, we provide the Interceptor.
|
||||
The Interceptor does not provide any authentication support. It simply creates
|
||||
a virtual file system view to the provided mapped paths. Once the actual
|
||||
resources have been reached, authentication will be enforced based on the
|
||||
requirements defined by the individual resource. Also note in the root map you
|
||||
can see we are running a Rails application. This is how you can easily enable
|
||||
DAV4Rack with your Rails application.
|
||||
|
||||
|
||||
## Custom Middleware
|
||||
|
||||
This is an alternative way to integrate one or more webdav handlers into a
|
||||
Rails app. It uses a custom middleware dispatching to any number of mounted
|
||||
Dav4Rack handlers, handles OPTIONS requests outside the webdav namespaces for
|
||||
interoperability with microsoft windows and lastly dispatches any remaining
|
||||
requests to the main (Rails) application.
|
||||
|
||||
```ruby
|
||||
|
||||
class CustomMiddleware
|
||||
|
||||
def initialize(app)
|
||||
@rails_app = app
|
||||
|
||||
@dav_app = Rack::Builder.new{
|
||||
map '/dav/' do
|
||||
run DAV4Rack::Handler.new(resource_class: CustomResource)
|
||||
end
|
||||
|
||||
map '/other/dav' do
|
||||
run CustomDavHandler.new
|
||||
end
|
||||
}.to_app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @dav_app.call env
|
||||
|
||||
# If the URL map generated by Rack::Builder did not find a matching path,
|
||||
# it will return a 404 along with the X-Cascade header set to 'pass'.
|
||||
if status == 404 and headers['X-Cascade'] == 'pass'
|
||||
|
||||
# The MS web redirector webdav client likes to go up a level and try
|
||||
# OPTIONS there. We catch that here and respond telling it that just
|
||||
# plain HTTP is going on.
|
||||
if 'OPTIONS'.casecmp(env['REQUEST_METHOD'].to_s) == 0
|
||||
[ '200', { 'Allow' => 'OPTIONS,HEAD,GET,PUT,POST,DELETE' }, [''] ]
|
||||
else
|
||||
# let Rails handle the request
|
||||
@rails_app.call env
|
||||
end
|
||||
|
||||
else
|
||||
[status, headers, body]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
```
|
||||
|
||||
You can add this middleware to your Rails app using
|
||||
|
||||
```ruby
|
||||
Rails.configuration.middleware.insert_before ActionDispatch::Cookies, CustomMiddleware
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
DAV4Rack provides some simple logging in a Rails style format (simply for
|
||||
consistency) so the output should look somewhat familiar.
|
||||
|
||||
DAV4Rack::Handler.new(resource_class: CustomResource, log_to: '/my/log/file')
|
||||
|
||||
You can even specify the level of logging:
|
||||
|
||||
DAV4Rack::Handler.new(resource_class: CustomResource, log_to: ['/my/log/file', Logger::DEBUG])
|
||||
|
||||
In order to use the Rails logger, just specify `log_to: Rails.logger`.
|
||||
|
||||
## Custom Resources
|
||||
|
||||
Creating your own resource is easy. Simply inherit the DAV4Rack::Resource
|
||||
class, and start redefining all the methods you want to customize. The
|
||||
DAV4Rack::Resource class only has implementations for methods that can be
|
||||
provided extremely generically. This means that most things will require at
|
||||
least some sort of implementation. However, because the Resource is defined so
|
||||
generically, and the Controller simply passes the request on to the Resource,
|
||||
it is easy to create fully virtualized resources.
|
||||
|
||||
## Helpers
|
||||
|
||||
There are some helpers worth mentioning that make things a little easier.
|
||||
|
||||
First of all, take note that the `request` object will be an instance of `DAV4Rack::Request`, which extends `Rack::Request` with some useful helpers.
|
||||
|
||||
### Redirects and sending remote files
|
||||
|
||||
If `request.client_allows_redirect?` is true, the currently connected client
|
||||
will accept and properly use a 302 redirect for a GET request. Most clients do
|
||||
not properly support this, which can be a real pain when working with
|
||||
virtualized files that may be located some where else, like S3. To deal with
|
||||
those clients that don't support redirects, a helper has been provided so
|
||||
resources don't have to deal with proxying themselves. The DAV4Rack::RemoteFile
|
||||
is a modified Rack::File that can do some interesting things. First, lets look
|
||||
at its most basic use:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
def setup
|
||||
@item = method_to_fill_this_properly
|
||||
end
|
||||
|
||||
def get
|
||||
if(request.client_allows_redirect?)
|
||||
response.redirect item[:url]
|
||||
else
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type)
|
||||
OK
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
This is a simple proxy. When Rack receives the RemoteFile, it will pull a chunk of data from object, which in turn pulls it from the socket, and
|
||||
sends it to the user over and over again until the EOF is reached. This much the same method that Rack::File uses but instead we are pulling
|
||||
from a socket rather than an actual file. Now, instead of proxying these files from a remote server every time, lets cache them:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :cache_directory => '/tmp')
|
||||
|
||||
Providing the `:cache_directory` will let RemoteFile cache the items locally,
|
||||
and then search for them on subsequent requests before heading out to the
|
||||
network. The cached file name is based off the SHA1 hash of the file path, size
|
||||
and last modified time. It is important to note that for services like S3, the
|
||||
path will often change, making this cache pretty worthless. To combat this, we
|
||||
can provide a reference to use instead:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :cache_directory => '/tmp', :cache_ref => item[:static_url])
|
||||
|
||||
These methods will work just fine, but it would be really nice to just let
|
||||
someone else deal with the proxying and let the process get back to dealing
|
||||
with actual requests. RemoteFile will happily do that as long as the frontend
|
||||
server is setup correctly. Using the sendfile approach will tell the RemoteFile
|
||||
to simply pass the headers on and let the server deal with doing the actual
|
||||
proxying. First, lets look at an implementation using all the features, and
|
||||
then degrade that down to the bare minimum. These examples are NGINX specific,
|
||||
but are based off the Rack::Sendfile implementation and as such should be
|
||||
applicable to other servers. First, a simplified NGINX server block:
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
location / {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Sendfile-Type X-Accel-Redirect;
|
||||
proxy_set_header X-Accel-Remote-Mapping webdav_redirect
|
||||
proxy_pass http://my_app_server;
|
||||
}
|
||||
|
||||
location ~* /webdav_redirect {
|
||||
internal;
|
||||
resolver 127.0.0.1;
|
||||
set $r_host $upstream_http_redirect_host;
|
||||
set $r_url $upstream_http_redirect_url;
|
||||
proxy_set_header Authorization '';
|
||||
proxy_set_header Host $r_host;
|
||||
proxy_max_temp_file_size 0;
|
||||
proxy_pass $r_url;
|
||||
}
|
||||
}
|
||||
|
||||
With this in place, the parameters for the RemoteFile change slightly:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => true)
|
||||
|
||||
The RemoteFile will automatically take care of building out the correct path and sending the proper headers. If the X-Accel-Remote-Mapping header
|
||||
is not available, you can simply pass the value:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => true, :sendfile_prefix => 'webdav_redirect')
|
||||
|
||||
And if you don't have the X-Sendfile-Type header set, you can fix that by changing the value of :sendfile:
|
||||
|
||||
response.body = DAV4Rack::RemoteFile.new(item[:url], :size => content_length, :mime_type => content_type, :sendfile => 'X-Accel-Redirect', :sendfile_prefix => 'webdav_redirect')
|
||||
|
||||
And if you have none of the above because your server hasn't been configured for sendfile support, you're out of luck until it's configured.
|
||||
|
||||
## Authentication
|
||||
|
||||
Authentication is performed on a per Resource basis. The Controller object will
|
||||
call `#authenticate` on any Resources it handles requests for. Basic
|
||||
Authentication information from the request will be passed to the method.
|
||||
Depending on the result, the Controller will either continue on with the
|
||||
request, or send a 401 Unauthorized response.
|
||||
|
||||
Override `Resource#authentication_realm` and `Resource#authentication_error_msg` to customize the realm name and response content for authentication failures.
|
||||
|
||||
Authentication can also be implemented using callbacks, as discussed below.
|
||||
|
||||
## Callbacks
|
||||
|
||||
*Deprecated*. This feature will most probably be removed in the future.
|
||||
|
||||
If you want to implement general before/after logic for every request, use a
|
||||
custom controller class and override `#process`.
|
||||
|
||||
|
||||
Resources can make use of callbacks to easily apply permissions, authentication or any other action that needs to be performed before or after any or all
|
||||
actions. Callbacks are applied to all publicly available methods. This is important for methods used internally within the resource. Methods not meant
|
||||
to be called by the Controller, or anyone else, should be scoped protected or private to reduce the interaction with callbacks.
|
||||
|
||||
Callbacks can be called before or after a method call. For example:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
before do |resource, method_name|
|
||||
resource.send(:my_authentication_method)
|
||||
end
|
||||
|
||||
after do |resource, method_name|
|
||||
puts "#{Time.now} -> Completed: #{resource}##{method_name}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def my_authentication_method
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
In this example MyResource#my_authentication_method will be called before any public method is called. After any method has been called a status
|
||||
line will be printed to STDOUT. Running callbacks before/after every method call is a bit much in most cases, so callbacks can be applied to specific
|
||||
methods:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
before_get do |resource|
|
||||
puts "#{Time.now} -> Received GET request from resource: #{resource}"
|
||||
end
|
||||
end
|
||||
|
||||
In this example, a simple status line will be printed to STDOUT before the MyResource#get method is called. The current resource object is always
|
||||
provided to callbacks. The method name is only provided to the generic before/after callbacks.
|
||||
|
||||
Something very handy for dealing with the mess of files OS X leaves on the system:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
after_unlock do |resource|
|
||||
resource.delete if resource.name[0,1] == '.'
|
||||
end
|
||||
end
|
||||
|
||||
Because OS X implements locking correctly, we can wait until it releases the lock on the file, and remove it if it's a hidden file.
|
||||
|
||||
Callbacks are called in the order they are defined, so you can easily build callbacks off each other. Like this example:
|
||||
|
||||
class MyResource < DAV4Rack::Resource
|
||||
before do |resource, method_name|
|
||||
resource.DAV_authenticate unless resource.user.is_a?(User)
|
||||
raise Unauthorized unless resource.user.is_a?(User)
|
||||
end
|
||||
before do |resource, method_name|
|
||||
resource.user.allowed?(method_name)
|
||||
end
|
||||
end
|
||||
|
||||
In this example, the second block checking User#allowed? can count on Resource#user being defined because the blocks are called in
|
||||
order, and if the Resource#user is not a User type, an exception is raised.
|
||||
|
||||
### Avoiding callbacks
|
||||
|
||||
Something special to notice in the last example is the DAV_ prefix on authenticate. Providing the DAV_ prefix will prevent
|
||||
any callbacks being applied to the given method. This allows us to provide a public method that the callback can access on the resource
|
||||
without getting stuck in a loop.
|
||||
|
||||
## Software using DAV4Rack!
|
||||
|
||||
* {meishi}[https://github.com/inferiorhumanorgans/meishi] - Lightweight CardDAV implementation in Rails
|
||||
* {dav4rack_ext}[https://github.com/schmurfy/dav4rack_ext] - CardDAV extension. (CalDAV planned)
|
||||
|
||||
## Issues/Bugs/Questions
|
||||
|
||||
### Known Issues
|
||||
|
||||
- OS X Finder PUT fails when using NGINX (this is due to NGINX's lack of
|
||||
chunked transfer encoding in earlier versions). Use a recent version of
|
||||
NGINX.
|
||||
- Windows WebDAV mini-redirector - this client is very broken. Windows from
|
||||
version 7 onwards however should work fine with the `OPTIONS` handling
|
||||
addition demonstrated above.
|
||||
- Lots of unimplemented parts of the webdav spec (patches always welcome). Run
|
||||
`test/litmus_all.sh` to see what works and what doesnt.
|
||||
|
||||
|
||||
### Unknown Issues
|
||||
|
||||
Please report issues at github: http://github.com/planio-gmbh/dav4rack/issues
|
||||
Include as much information about the environment as possible (especially client OS / software).
|
||||
|
||||
## Contributors
|
||||
|
||||
A big thanks to everyone contributing to help make this project better.
|
||||
|
||||
* [clyfe](http://github.com/clyfe)
|
||||
* [antiloopgmbh](http://github.com/antiloopgmbh)
|
||||
* [krug](http://github.com/krug)
|
||||
* [teefax](http://github.com/teefax)
|
||||
* [buffym](https://github.com/buffym)
|
||||
* [jbangert](https://github.com/jbangert)
|
||||
* [doxavore](https://github.com/doxavore)
|
||||
* [spicyj](https://github.com/spicyj)
|
||||
* [TurchenkoAlex](https://github.com/TurchenkoAlex)
|
||||
* [exabugs](https://github.com/exabugs)
|
||||
* [inferiorhumanorgans](https://github.com/inferiorhumanorgans)
|
||||
* [schmurfy](https://github.com/schmurfy)
|
||||
* [pifleo](https://github.com/pifleo)
|
||||
* [mlmorg](https://github.com/mlmorg)
|
||||
|
||||
## License
|
||||
|
||||
Just like RackDAV before it, this software is distributed under the MIT license.
|
||||
Loading…
x
Reference in New Issue
Block a user