Dav4rack sources replaces with correspondig gem
This commit is contained in:
parent
58eda96713
commit
4350868cef
1
Gemfile
1
Gemfile
@ -4,6 +4,7 @@ gem 'rubyzip', '>= 1.0.0'
|
|||||||
gem 'zip-zip' # Just to avoid 'cannot load such file -- zip/zip' error
|
gem 'zip-zip' # Just to avoid 'cannot load such file -- zip/zip' error
|
||||||
gem 'simple_enum'
|
gem 'simple_enum'
|
||||||
gem 'uuidtools', '~> 2.1.1'
|
gem 'uuidtools', '~> 2.1.1'
|
||||||
|
gem 'dav4rack', '=0.2.11'
|
||||||
|
|
||||||
group :production do
|
group :production do
|
||||||
gem 'nokogiri', '>= 1.5.10'
|
gem 'nokogiri', '>= 1.5.10'
|
||||||
|
|||||||
9
lib/redmine_dmsf/vendor/dav4rack.rb
vendored
9
lib/redmine_dmsf/vendor/dav4rack.rb
vendored
@ -1,9 +0,0 @@
|
|||||||
require 'time'
|
|
||||||
require 'uri'
|
|
||||||
require 'nokogiri'
|
|
||||||
|
|
||||||
require 'rack'
|
|
||||||
require 'dav4rack/http_status'
|
|
||||||
require 'dav4rack/resource'
|
|
||||||
require 'dav4rack/handler'
|
|
||||||
require 'dav4rack/controller'
|
|
||||||
545
lib/redmine_dmsf/vendor/dav4rack/controller.rb
vendored
545
lib/redmine_dmsf/vendor/dav4rack/controller.rb
vendored
@ -1,545 +0,0 @@
|
|||||||
require 'uri'
|
|
||||||
|
|
||||||
module DAV4Rack
|
|
||||||
|
|
||||||
class Controller
|
|
||||||
include DAV4Rack::HTTPStatus
|
|
||||||
|
|
||||||
attr_reader :request, :response, :resource
|
|
||||||
|
|
||||||
# request:: Rack::Request
|
|
||||||
# response:: Rack::Response
|
|
||||||
# options:: Options hash
|
|
||||||
# Create a new Controller.
|
|
||||||
# NOTE: options will be passed to Resource
|
|
||||||
def initialize(request, response, options={})
|
|
||||||
raise Forbidden if request.path_info.include?('..')
|
|
||||||
@request = request
|
|
||||||
@response = response
|
|
||||||
@options = options
|
|
||||||
@resource = resource_class.new(actual_path, implied_path, @request, @response, @options)
|
|
||||||
end
|
|
||||||
|
|
||||||
# s:: string
|
|
||||||
# Escape URL string
|
|
||||||
def url_format(resource)
|
|
||||||
ret = URI.escape(resource.public_path)
|
|
||||||
if resource.collection? and ret[-1,1] != '/'
|
|
||||||
ret += '/'
|
|
||||||
end
|
|
||||||
ret
|
|
||||||
end
|
|
||||||
|
|
||||||
# s:: string
|
|
||||||
# Unescape URL string
|
|
||||||
def url_unescape(s)
|
|
||||||
URI.unescape(s)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return response to OPTIONS
|
|
||||||
def options
|
|
||||||
response["Allow"] = 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
|
|
||||||
response["Dav"] = "1, 2"
|
|
||||||
response["Ms-Author-Via"] = "DAV"
|
|
||||||
OK
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return response to HEAD
|
|
||||||
def head
|
|
||||||
if(resource.exist?)
|
|
||||||
response['Etag'] = resource.etag
|
|
||||||
response['Content-Type'] = resource.content_type
|
|
||||||
response['Last-Modified'] = resource.last_modified.httpdate
|
|
||||||
OK
|
|
||||||
else
|
|
||||||
NotFound
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return response to GET
|
|
||||||
def get
|
|
||||||
if(resource.exist?)
|
|
||||||
res = resource.get(request, response)
|
|
||||||
if(res == OK && !resource.collection?)
|
|
||||||
response['Etag'] = resource.etag
|
|
||||||
response['Content-Type'] = resource.content_type
|
|
||||||
response['Content-Length'] = resource.content_length.to_s
|
|
||||||
response['Last-Modified'] = resource.last_modified.httpdate
|
|
||||||
end
|
|
||||||
res
|
|
||||||
else
|
|
||||||
NotFound
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return response to PUT
|
|
||||||
def put
|
|
||||||
if(resource.collection?)
|
|
||||||
Forbidden
|
|
||||||
elsif(!resource.parent_exists? || !resource.parent.collection?)
|
|
||||||
Conflict
|
|
||||||
else
|
|
||||||
resource.lock_check
|
|
||||||
status = resource.put(request, response)
|
|
||||||
response['Location'] = "#{scheme}://#{host}:#{port}#{url_format(resource)}" if status == Created
|
|
||||||
response.body = response['Location']
|
|
||||||
status
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return response to POST
|
|
||||||
def post
|
|
||||||
resource.post(request, response)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return response to DELETE
|
|
||||||
def delete
|
|
||||||
if(resource.exist?)
|
|
||||||
resource.lock_check
|
|
||||||
resource.delete
|
|
||||||
else
|
|
||||||
NotFound
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return response to MKCOL
|
|
||||||
def mkcol
|
|
||||||
resource.lock_check
|
|
||||||
status = resource.make_collection
|
|
||||||
gen_url = "#{scheme}://#{host}:#{port}#{url_format(resource)}" if status == Created
|
|
||||||
if(resource.use_compat_mkcol_response?)
|
|
||||||
multistatus do |xml|
|
|
||||||
xml.response do
|
|
||||||
xml.href gen_url
|
|
||||||
xml.status "#{http_version} #{status.status_line}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
response['Location'] = gen_url
|
|
||||||
status
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return response to COPY
|
|
||||||
def copy
|
|
||||||
move(:copy)
|
|
||||||
end
|
|
||||||
|
|
||||||
# args:: Only argument used: :copy
|
|
||||||
# Move Resource to new location. If :copy is provided,
|
|
||||||
# Resource will be copied (implementation ease)
|
|
||||||
def move(*args)
|
|
||||||
unless(resource.exist?)
|
|
||||||
NotFound
|
|
||||||
else
|
|
||||||
resource.lock_check unless args.include?(:copy)
|
|
||||||
destination = url_unescape(env['HTTP_DESTINATION'].sub(%r{https?://([^/]+)}, ''))
|
|
||||||
dest_host = $1
|
|
||||||
if(dest_host && dest_host.gsub(/:\d{2,5}$/, '') != request.host)
|
|
||||||
BadGateway
|
|
||||||
elsif(destination == resource.public_path)
|
|
||||||
Forbidden
|
|
||||||
else
|
|
||||||
collection = resource.collection?
|
|
||||||
dest = resource_class.new(destination, clean_path(destination), @request, @response, @options.merge(:user => resource.user))
|
|
||||||
status = nil
|
|
||||||
if(args.include?(:copy))
|
|
||||||
status = resource.copy(dest, overwrite)
|
|
||||||
else
|
|
||||||
return Conflict unless depth.is_a?(Symbol) || depth > 1
|
|
||||||
status = resource.move(dest, overwrite)
|
|
||||||
end
|
|
||||||
response['Location'] = "#{scheme}://#{host}:#{port}#{url_format(dest)}" if status == Created
|
|
||||||
# RFC 2518
|
|
||||||
if collection
|
|
||||||
multistatus do |xml|
|
|
||||||
xml.response do
|
|
||||||
xml.href "#{scheme}://#{host}:#{port}#{url_format(status == Created ? dest : resource)}"
|
|
||||||
xml.status "#{http_version} #{status.status_line}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
status
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return respoonse to PROPFIND
|
|
||||||
def propfind
|
|
||||||
unless(resource.exist?)
|
|
||||||
NotFound
|
|
||||||
else
|
|
||||||
unless(request_document.xpath("//#{ns}propfind/#{ns}allprop").empty?)
|
|
||||||
names = resource.property_names
|
|
||||||
else
|
|
||||||
names = (
|
|
||||||
ns.empty? ? request_document.remove_namespaces! : request_document
|
|
||||||
).xpath(
|
|
||||||
"//#{ns}propfind/#{ns}prop"
|
|
||||||
).children.find_all{ |item|
|
|
||||||
item.element? && item.name.start_with?(ns)
|
|
||||||
}.map{ |item|
|
|
||||||
item.name.sub("#{ns}::", '')
|
|
||||||
}
|
|
||||||
raise BadRequest if names.empty?
|
|
||||||
names = resource.property_names if names.empty?
|
|
||||||
end
|
|
||||||
multistatus do |xml|
|
|
||||||
find_resources.each do |resource|
|
|
||||||
xml.response do
|
|
||||||
unless(resource.propstat_relative_path)
|
|
||||||
xml.href "#{scheme}://#{host}:#{port}#{url_format(resource)}"
|
|
||||||
else
|
|
||||||
xml.href url_format(resource)
|
|
||||||
end
|
|
||||||
propstats(xml, get_properties(resource, names))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return response to PROPPATCH
|
|
||||||
def proppatch
|
|
||||||
unless(resource.exist?)
|
|
||||||
NotFound
|
|
||||||
else
|
|
||||||
resource.lock_check
|
|
||||||
prop_rem = request_match('/propertyupdate/remove/prop').children.map{|n| [n.name] }
|
|
||||||
prop_set = request_match('/propertyupdate/set/prop').children.map{|n| [n.name, n.text] }
|
|
||||||
multistatus do |xml|
|
|
||||||
find_resources.each do |resource|
|
|
||||||
xml.response do
|
|
||||||
xml.href "#{scheme}://#{host}:#{port}#{url_format(resource)}"
|
|
||||||
propstats(xml, set_properties(resource, prop_set))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Lock current resource
|
|
||||||
# NOTE: This will pass an argument hash to Resource#lock and
|
|
||||||
# wait for a success/failure response.
|
|
||||||
def lock
|
|
||||||
lockinfo = request_document.xpath("//#{ns}lockinfo")
|
|
||||||
asked = {}
|
|
||||||
asked[:timeout] = request.env['Timeout'].split(',').map{|x|x.strip} if request.env['Timeout']
|
|
||||||
asked[:depth] = depth
|
|
||||||
unless([0, :infinity].include?(asked[:depth]))
|
|
||||||
BadRequest
|
|
||||||
else
|
|
||||||
asked[:scope] = lockinfo.xpath("//#{ns}lockscope").children.find_all{|n|n.element?}.map{|n|n.name}.first
|
|
||||||
asked[:type] = lockinfo.xpath("#{ns}locktype").children.find_all{|n|n.element?}.map{|n|n.name}.first
|
|
||||||
asked[:owner] = lockinfo.xpath("//#{ns}owner/#{ns}href").children.map{|n|n.text}.first
|
|
||||||
begin
|
|
||||||
lock_time, locktoken = resource.lock(asked)
|
|
||||||
render_xml(:prop) do |xml|
|
|
||||||
xml.lockdiscovery do
|
|
||||||
xml.activelock do
|
|
||||||
if(asked[:scope])
|
|
||||||
xml.lockscope do
|
|
||||||
xml.send(asked[:scope])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if(asked[:type])
|
|
||||||
xml.locktype do
|
|
||||||
xml.send(asked[:type])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
xml.depth asked[:depth].to_s
|
|
||||||
xml.timeout lock_time ? "Second-#{lock_time}" : 'infinity'
|
|
||||||
xml.locktoken do
|
|
||||||
xml.href locktoken
|
|
||||||
end
|
|
||||||
if(asked[:owner])
|
|
||||||
xml.owner asked[:owner]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
response.status = resource.exist? ? OK : Created
|
|
||||||
rescue LockFailure => e
|
|
||||||
multistatus do |xml|
|
|
||||||
e.path_status.each_pair do |path, status|
|
|
||||||
xml.response do
|
|
||||||
xml.href path
|
|
||||||
xml.status "#{http_version} #{status.status_line}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Unlock current resource
|
|
||||||
def unlock
|
|
||||||
resource.unlock(lock_token)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Perform authentication
|
|
||||||
# NOTE: Authentication will only be performed if the Resource
|
|
||||||
# has defined an #authenticate method
|
|
||||||
def authenticate
|
|
||||||
authed = true
|
|
||||||
if(resource.respond_to?(:authenticate, true))
|
|
||||||
authed = false
|
|
||||||
uname = nil
|
|
||||||
password = nil
|
|
||||||
if(request.env['HTTP_AUTHORIZATION'])
|
|
||||||
auth = Rack::Auth::Basic::Request.new(request.env)
|
|
||||||
if(auth.basic? && auth.credentials)
|
|
||||||
uname = auth.credentials[0]
|
|
||||||
password = auth.credentials[1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
authed = resource.send(:authenticate, uname, password)
|
|
||||||
end
|
|
||||||
raise Unauthorized unless authed
|
|
||||||
end
|
|
||||||
|
|
||||||
# ************************************************************
|
|
||||||
# private methods
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Request environment variables
|
|
||||||
def env
|
|
||||||
@request.env
|
|
||||||
end
|
|
||||||
|
|
||||||
# Current request scheme (http/https)
|
|
||||||
def scheme
|
|
||||||
request.scheme
|
|
||||||
end
|
|
||||||
|
|
||||||
# Request host
|
|
||||||
def host
|
|
||||||
request.host
|
|
||||||
end
|
|
||||||
|
|
||||||
# Request port
|
|
||||||
def port
|
|
||||||
request.port
|
|
||||||
end
|
|
||||||
|
|
||||||
# Class of the resource in use
|
|
||||||
def resource_class
|
|
||||||
@options[:resource_class]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Root URI path for the resource
|
|
||||||
def root_uri_path
|
|
||||||
@options[:root_uri_path]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns Resource path with root URI removed
|
|
||||||
def implied_path
|
|
||||||
|
|
||||||
return clean_path(@request.path_info.dup) unless @request.path_info.empty?
|
|
||||||
c_path = clean_path(@request.path.dup)
|
|
||||||
return c_path if c_path.length != @request.path.length
|
|
||||||
|
|
||||||
#if we're here then it's probably down to thin
|
|
||||||
return @request.path.dup.gsub!(/^#{Regexp.escape(@request.script_name)}/, '') unless @request.script_name.empty?
|
|
||||||
|
|
||||||
return c_path #This will probably result in a processing error if we hit here
|
|
||||||
end
|
|
||||||
|
|
||||||
# x:: request path
|
|
||||||
# Unescapes path and removes root URI if applicable
|
|
||||||
def clean_path(x)
|
|
||||||
ip = url_unescape(x)
|
|
||||||
ip.gsub!(/^#{Regexp.escape(root_uri_path)}/, '') if root_uri_path
|
|
||||||
ip
|
|
||||||
end
|
|
||||||
|
|
||||||
# Unescaped request path
|
|
||||||
def actual_path
|
|
||||||
url_unescape(@request.path.dup)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Lock token if provided by client
|
|
||||||
def lock_token
|
|
||||||
env['HTTP_LOCK_TOKEN'] || nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Requested depth
|
|
||||||
def depth
|
|
||||||
d = env['HTTP_DEPTH']
|
|
||||||
if(d =~ /^\d+$/)
|
|
||||||
d = d.to_i
|
|
||||||
else
|
|
||||||
d = :infinity
|
|
||||||
end
|
|
||||||
d
|
|
||||||
end
|
|
||||||
|
|
||||||
# Current HTTP version being used
|
|
||||||
def http_version
|
|
||||||
env['HTTP_VERSION'] || env['SERVER_PROTOCOL'] || 'HTTP/1.0'
|
|
||||||
end
|
|
||||||
|
|
||||||
# Overwrite is allowed
|
|
||||||
def overwrite
|
|
||||||
env['HTTP_OVERWRITE'].to_s.upcase != 'F'
|
|
||||||
end
|
|
||||||
|
|
||||||
# Find resources at depth requested
|
|
||||||
def find_resources(with_current_resource=true)
|
|
||||||
ary = nil
|
|
||||||
case depth
|
|
||||||
when 0
|
|
||||||
ary = []
|
|
||||||
when 1
|
|
||||||
ary = resource.children
|
|
||||||
else
|
|
||||||
ary = resource.descendants
|
|
||||||
end
|
|
||||||
with_current_resource ? [resource] + ary : ary
|
|
||||||
end
|
|
||||||
|
|
||||||
# XML parsed request
|
|
||||||
def request_document
|
|
||||||
@request_document ||= Nokogiri.XML(request.body.read)
|
|
||||||
rescue
|
|
||||||
raise BadRequest
|
|
||||||
end
|
|
||||||
|
|
||||||
# Namespace being used within XML document
|
|
||||||
# TODO: Make this better
|
|
||||||
def ns
|
|
||||||
_ns = ''
|
|
||||||
if(request_document && request_document.root && request_document.root.namespace_definitions.size > 0)
|
|
||||||
_ns = request_document.root.namespace_definitions.first.prefix.to_s
|
|
||||||
_ns += ':' unless _ns.empty?
|
|
||||||
end
|
|
||||||
_ns
|
|
||||||
end
|
|
||||||
|
|
||||||
# pattern:: XPath pattern
|
|
||||||
# Search XML document for given XPath
|
|
||||||
# TODO: Stripping namespaces not so great
|
|
||||||
def request_match(pattern)
|
|
||||||
request_document.remove_namespaces!.xpath(pattern, request_document.root.namespaces)
|
|
||||||
end
|
|
||||||
|
|
||||||
# root_type:: Root tag name
|
|
||||||
# Render XML and set Rack::Response#body= to final XML
|
|
||||||
def render_xml(root_type)
|
|
||||||
raise ArgumentError.new 'Expecting block' unless block_given?
|
|
||||||
doc = Nokogiri::XML::Builder.new do |xml_base|
|
|
||||||
xml_base.send(root_type.to_s, {'xmlns:D' => 'DAV:'}.merge(resource.root_xml_attributes)) do
|
|
||||||
xml_base.parent.namespace = xml_base.parent.namespace_definitions.first
|
|
||||||
xml = xml_base['D']
|
|
||||||
yield xml
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if(@options[:pretty_xml])
|
|
||||||
response.body = doc.to_xml
|
|
||||||
else
|
|
||||||
response.body = doc.to_xml(
|
|
||||||
:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML
|
|
||||||
)
|
|
||||||
end
|
|
||||||
response["Content-Type"] = 'text/xml; charset="utf-8"'
|
|
||||||
response["Content-Length"] = response.body.size.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
# block:: block
|
|
||||||
# Creates a multistatus response using #render_xml and
|
|
||||||
# returns the correct status
|
|
||||||
def multistatus(&block)
|
|
||||||
render_xml(:multistatus, &block)
|
|
||||||
MultiStatus
|
|
||||||
end
|
|
||||||
|
|
||||||
# xml:: Nokogiri::XML::Builder
|
|
||||||
# errors:: Array of errors
|
|
||||||
# Crafts responses for errors
|
|
||||||
def response_errors(xml, errors)
|
|
||||||
for path, status in errors
|
|
||||||
xml.response do
|
|
||||||
xml.href "#{scheme}://#{host}:#{port}#{URI.escape(path)}"
|
|
||||||
xml.status "#{http_version} #{status.status_line}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# resource:: Resource
|
|
||||||
# names:: Property names
|
|
||||||
# Returns array of property values for given names
|
|
||||||
def get_properties(resource, names)
|
|
||||||
stats = Hash.new { |h, k| h[k] = [] }
|
|
||||||
for name in names
|
|
||||||
begin
|
|
||||||
val = resource.get_property(name)
|
|
||||||
stats[OK].push [name, val]
|
|
||||||
rescue Unauthorized => u
|
|
||||||
raise u
|
|
||||||
rescue Status
|
|
||||||
stats[$!.class] << name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
stats
|
|
||||||
end
|
|
||||||
|
|
||||||
# resource:: Resource
|
|
||||||
# pairs:: name value pairs
|
|
||||||
# Sets the given properties
|
|
||||||
def set_properties(resource, pairs)
|
|
||||||
stats = Hash.new { |h, k| h[k] = [] }
|
|
||||||
for name, value in pairs
|
|
||||||
begin
|
|
||||||
stats[OK] << [name, resource.set_property(name, value)]
|
|
||||||
rescue Unauthorized => u
|
|
||||||
raise u
|
|
||||||
rescue Status
|
|
||||||
stats[$!.class] << name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
stats
|
|
||||||
end
|
|
||||||
|
|
||||||
# xml:: Nokogiri::XML::Builder
|
|
||||||
# stats:: Array of stats
|
|
||||||
# Build propstats response
|
|
||||||
def propstats(xml, stats)
|
|
||||||
return if stats.empty?
|
|
||||||
for status, props in stats
|
|
||||||
xml.propstat do
|
|
||||||
xml.prop do
|
|
||||||
for name, value in props
|
|
||||||
if(value.is_a?(Nokogiri::XML::DocumentFragment))
|
|
||||||
xml.__send__ :insert, value
|
|
||||||
elsif(value.is_a?(Nokogiri::XML::Node))
|
|
||||||
xml.send(name) do
|
|
||||||
xml_convert(xml, value)
|
|
||||||
end
|
|
||||||
elsif(value.is_a?(Symbol))
|
|
||||||
xml.send(name) do
|
|
||||||
xml.send(value)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
xml.send(name, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
xml.status "#{http_version} #{status.status_line}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# xml:: Nokogiri::XML::Builder
|
|
||||||
# element:: Nokogiri::XML::Element
|
|
||||||
# Converts element into proper text
|
|
||||||
def xml_convert(xml, element)
|
|
||||||
xml.doc.root.add_child(element)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
37
lib/redmine_dmsf/vendor/dav4rack/file.rb
vendored
37
lib/redmine_dmsf/vendor/dav4rack/file.rb
vendored
@ -1,37 +0,0 @@
|
|||||||
require 'time'
|
|
||||||
require 'rack/utils'
|
|
||||||
require 'rack/mime'
|
|
||||||
|
|
||||||
module DAV4Rack
|
|
||||||
# DAV4Rack::File simply allows us to use Rack::File but with the
|
|
||||||
# specific location we deem appropriate
|
|
||||||
class File < Rack::File
|
|
||||||
attr_accessor :path
|
|
||||||
|
|
||||||
alias :to_path :path
|
|
||||||
|
|
||||||
def initialize(path)
|
|
||||||
@path = path
|
|
||||||
end
|
|
||||||
|
|
||||||
def _call(env)
|
|
||||||
begin
|
|
||||||
if F.file?(@path) && F.readable?(@path)
|
|
||||||
serving
|
|
||||||
else
|
|
||||||
raise Errno::EPERM
|
|
||||||
end
|
|
||||||
rescue SystemCallError
|
|
||||||
not_found
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def not_found
|
|
||||||
body = "File not found: #{Rack::Utils.unescape(env["PATH_INFO"])}\n"
|
|
||||||
[404, {"Content-Type" => "text/plain",
|
|
||||||
"Content-Length" => body.size.to_s,
|
|
||||||
"X-Cascade" => "pass"},
|
|
||||||
[body]]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
257
lib/redmine_dmsf/vendor/dav4rack/file_resource.rb
vendored
257
lib/redmine_dmsf/vendor/dav4rack/file_resource.rb
vendored
@ -1,257 +0,0 @@
|
|||||||
require 'webrick/httputils'
|
|
||||||
|
|
||||||
module DAV4Rack
|
|
||||||
|
|
||||||
class FileResource < Resource
|
|
||||||
|
|
||||||
include WEBrick::HTTPUtils
|
|
||||||
|
|
||||||
# If this is a collection, return the child resources.
|
|
||||||
def children
|
|
||||||
Dir[file_path + '/*'].map do |path|
|
|
||||||
child File.basename(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Is this resource a collection?
|
|
||||||
def collection?
|
|
||||||
File.directory?(file_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Does this recource exist?
|
|
||||||
def exist?
|
|
||||||
File.exist?(file_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the creation time.
|
|
||||||
def creation_date
|
|
||||||
stat.ctime
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the time of last modification.
|
|
||||||
def last_modified
|
|
||||||
stat.mtime
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set the time of last modification.
|
|
||||||
def last_modified=(time)
|
|
||||||
File.utime(Time.now, time, file_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return an Etag, an unique hash value for this resource.
|
|
||||||
def etag
|
|
||||||
sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the mime type of this resource.
|
|
||||||
def content_type
|
|
||||||
if stat.directory?
|
|
||||||
"text/html"
|
|
||||||
else
|
|
||||||
mime_type(file_path, DefaultMimeTypes)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the size in bytes for this resource.
|
|
||||||
def content_length
|
|
||||||
stat.size
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP GET request.
|
|
||||||
#
|
|
||||||
# Write the content of the resource to the response.body.
|
|
||||||
def get(request, response)
|
|
||||||
raise NotFound unless exist?
|
|
||||||
if stat.directory?
|
|
||||||
response.body = ""
|
|
||||||
Rack::Directory.new(root).call(request.env)[2].each do |line|
|
|
||||||
response.body << line
|
|
||||||
end
|
|
||||||
response['Content-Length'] = response.body.bytesize.to_s
|
|
||||||
else
|
|
||||||
file = Rack::File.new(root)
|
|
||||||
response.body = file
|
|
||||||
end
|
|
||||||
OK
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP PUT request.
|
|
||||||
#
|
|
||||||
# Save the content of the request.body.
|
|
||||||
def put(request, response)
|
|
||||||
write(request.body)
|
|
||||||
Created
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP POST request.
|
|
||||||
#
|
|
||||||
# Usually forbidden.
|
|
||||||
def post(request, response)
|
|
||||||
raise HTTPStatus::Forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP DELETE request.
|
|
||||||
#
|
|
||||||
# Delete this resource.
|
|
||||||
def delete
|
|
||||||
if stat.directory?
|
|
||||||
FileUtils.rm_rf(file_path)
|
|
||||||
else
|
|
||||||
File.unlink(file_path)
|
|
||||||
end
|
|
||||||
NoContent
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP COPY request.
|
|
||||||
#
|
|
||||||
# Copy this resource to given destination resource.
|
|
||||||
# Copy this resource to given destination resource.
|
|
||||||
def copy(dest, overwrite)
|
|
||||||
if(collection?)
|
|
||||||
if(dest.exist?)
|
|
||||||
if(dest.collection? && overwrite)
|
|
||||||
FileUtils.cp_r(file_path, dest.send(:file_path))
|
|
||||||
Created
|
|
||||||
else
|
|
||||||
if(overwrite)
|
|
||||||
FileUtils.rm(dest.send(:file_path))
|
|
||||||
FileUtils.cp_r(file_path, dest.send(:file_path))
|
|
||||||
NoContent
|
|
||||||
else
|
|
||||||
PreconditionFailed
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
FileUtils.cp_r(file_path, dest.send(:file_path))
|
|
||||||
Created
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if(dest.exist? && !overwrite)
|
|
||||||
PreconditionFailed
|
|
||||||
else
|
|
||||||
if(File.directory?(File.dirname(dest.send(:file_path))))
|
|
||||||
new = !dest.exist?
|
|
||||||
if(dest.collection? && dest.exist?)
|
|
||||||
FileUtils.rm_rf(dest.send(:file_path))
|
|
||||||
end
|
|
||||||
FileUtils.cp(file_path, dest.send(:file_path).sub(/\/$/, ''))
|
|
||||||
new ? Created : NoContent
|
|
||||||
else
|
|
||||||
Conflict
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP MOVE request.
|
|
||||||
#
|
|
||||||
# Move this resource to given destination resource.
|
|
||||||
def move(*args)
|
|
||||||
result = copy(*args)
|
|
||||||
delete if [Created, NoContent].include?(result)
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP MKCOL request.
|
|
||||||
#
|
|
||||||
# Create this resource as collection.
|
|
||||||
def make_collection
|
|
||||||
if(request.body.read.to_s == '')
|
|
||||||
if(File.directory?(file_path))
|
|
||||||
MethodNotAllowed
|
|
||||||
else
|
|
||||||
if(File.directory?(File.dirname(file_path)))
|
|
||||||
Dir.mkdir(file_path)
|
|
||||||
Created
|
|
||||||
else
|
|
||||||
Conflict
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
UnsupportedMediaType
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Write to this resource from given IO.
|
|
||||||
def write(io)
|
|
||||||
tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
|
|
||||||
|
|
||||||
open(tempfile, "wb") do |file|
|
|
||||||
while part = io.read(8192)
|
|
||||||
file << part
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
File.rename(tempfile, file_path)
|
|
||||||
ensure
|
|
||||||
File.unlink(tempfile) rescue nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# name:: String - Property name
|
|
||||||
# Returns the value of the given property
|
|
||||||
def get_property(name)
|
|
||||||
super || custom_props(name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# name:: String - Property name
|
|
||||||
# value:: New value
|
|
||||||
# Set the property to the given value
|
|
||||||
def set_property(name, value)
|
|
||||||
super || set_custom_props(name,value)
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def set_custom_props(key,val)
|
|
||||||
prop_hash[key.to_sym] = val
|
|
||||||
File.open(prop_path, 'w') do |file|
|
|
||||||
file.write(YAML.dump(prop_hash))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def custom_props(key)
|
|
||||||
prop_hash[key.to_sym]
|
|
||||||
end
|
|
||||||
|
|
||||||
def prop_path
|
|
||||||
path = File.join(root, '.props', File.dirname(file_path), File.basename(file_path))
|
|
||||||
unless(File.directory?(File.dirname(path)))
|
|
||||||
FileUtils.mkdir_p(File.dirname(path))
|
|
||||||
end
|
|
||||||
path
|
|
||||||
end
|
|
||||||
|
|
||||||
def prop_hash
|
|
||||||
unless(@_prop_hash)
|
|
||||||
if(File.exists?(prop_path))
|
|
||||||
@_prop_hash = YAML.load(File.read(prop_path))
|
|
||||||
else
|
|
||||||
@_prop_hash = {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@_prop_hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def authenticate(user, pass)
|
|
||||||
if(options[:username])
|
|
||||||
options[:username] == user && options[:password] == pass
|
|
||||||
else
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def root
|
|
||||||
@options[:root]
|
|
||||||
end
|
|
||||||
|
|
||||||
def file_path
|
|
||||||
File.join(root, path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def stat
|
|
||||||
@stat ||= File.stat(file_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
64
lib/redmine_dmsf/vendor/dav4rack/handler.rb
vendored
64
lib/redmine_dmsf/vendor/dav4rack/handler.rb
vendored
@ -1,64 +0,0 @@
|
|||||||
require 'dav4rack/logger'
|
|
||||||
|
|
||||||
module DAV4Rack
|
|
||||||
|
|
||||||
class Handler
|
|
||||||
include DAV4Rack::HTTPStatus
|
|
||||||
def initialize(options={})
|
|
||||||
@options = options.dup
|
|
||||||
unless(@options[:resource_class])
|
|
||||||
require 'dav4rack/file_resource'
|
|
||||||
@options[:resource_class] = FileResource
|
|
||||||
@options[:root] ||= Dir.pwd
|
|
||||||
end
|
|
||||||
Logger.set(*@options[:log_to])
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(env)
|
|
||||||
begin
|
|
||||||
start = Time.now
|
|
||||||
request = Rack::Request.new(env)
|
|
||||||
response = Rack::Response.new
|
|
||||||
|
|
||||||
Logger.info "Processing WebDAV request: #{request.path} (for #{request.ip} at #{Time.now}) [#{request.request_method}]"
|
|
||||||
|
|
||||||
controller = nil
|
|
||||||
begin
|
|
||||||
controller_class = @options[:controller_class] || Controller
|
|
||||||
controller = controller_class.new(request, response, @options.dup)
|
|
||||||
controller.authenticate
|
|
||||||
res = controller.send(request.request_method.downcase)
|
|
||||||
response.status = res.code if res.respond_to?(:code)
|
|
||||||
rescue HTTPStatus::Unauthorized => status
|
|
||||||
response.body = controller.resource.respond_to?(:authentication_error_msg) ? controller.resource.authentication_error_msg : 'Not Authorized'
|
|
||||||
response['WWW-Authenticate'] = "Basic realm=\"#{controller.resource.respond_to?(:authentication_realm) ? controller.resource.authentication_realm : 'Locked content'}\""
|
|
||||||
response.status = status.code
|
|
||||||
rescue HTTPStatus::Status => status
|
|
||||||
response.status = status.code
|
|
||||||
end
|
|
||||||
|
|
||||||
# Strings in Ruby 1.9 are no longer enumerable. Rack still expects the response.body to be
|
|
||||||
# enumerable, however.
|
|
||||||
|
|
||||||
response['Content-Length'] = response.body.to_s.length unless response['Content-Length'] || !response.body.is_a?(String)
|
|
||||||
response.body = [response.body] unless response.body.respond_to? :each
|
|
||||||
response.status = response.status ? response.status.to_i : 200
|
|
||||||
response.headers.keys.each{|k| response.headers[k] = response[k].to_s}
|
|
||||||
|
|
||||||
# Apache wants the body dealt with, so just read it and junk it
|
|
||||||
buf = true
|
|
||||||
buf = request.body.read(8192) while buf
|
|
||||||
|
|
||||||
Logger.debug "Response in string form. Outputting contents: \n#{response.body}" if response.body.is_a?(String)
|
|
||||||
Logger.info "Completed in: #{((Time.now.to_f - start.to_f) * 1000).to_i} ms | #{response.status} [#{request.url}]"
|
|
||||||
|
|
||||||
response.body.is_a?(Rack::File) ? response.body.call(env) : response.finish
|
|
||||||
rescue Exception => e
|
|
||||||
Logger.error "WebDAV Error: #{e}\n#{e.backtrace.join("\n")}"
|
|
||||||
raise e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
108
lib/redmine_dmsf/vendor/dav4rack/http_status.rb
vendored
108
lib/redmine_dmsf/vendor/dav4rack/http_status.rb
vendored
@ -1,108 +0,0 @@
|
|||||||
module DAV4Rack
|
|
||||||
|
|
||||||
module HTTPStatus
|
|
||||||
|
|
||||||
class Status < Exception
|
|
||||||
|
|
||||||
class << self
|
|
||||||
attr_accessor :code, :reason_phrase
|
|
||||||
alias_method :to_i, :code
|
|
||||||
|
|
||||||
def status_line
|
|
||||||
"#{code} #{reason_phrase}"
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def code
|
|
||||||
self.class.code
|
|
||||||
end
|
|
||||||
|
|
||||||
def reason_phrase
|
|
||||||
self.class.reason_phrase
|
|
||||||
end
|
|
||||||
|
|
||||||
def status_line
|
|
||||||
self.class.status_line
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_i
|
|
||||||
self.class.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
StatusMessage = {
|
|
||||||
100 => 'Continue',
|
|
||||||
101 => 'Switching Protocols',
|
|
||||||
102 => 'Processing',
|
|
||||||
200 => 'OK',
|
|
||||||
201 => 'Created',
|
|
||||||
202 => 'Accepted',
|
|
||||||
203 => 'Non-Authoritative Information',
|
|
||||||
204 => 'No Content',
|
|
||||||
205 => 'Reset Content',
|
|
||||||
206 => 'Partial Content',
|
|
||||||
207 => 'Multi-Status',
|
|
||||||
300 => 'Multiple Choices',
|
|
||||||
301 => 'Moved Permanently',
|
|
||||||
302 => 'Found',
|
|
||||||
303 => 'See Other',
|
|
||||||
304 => 'Not Modified',
|
|
||||||
305 => 'Use Proxy',
|
|
||||||
307 => 'Temporary Redirect',
|
|
||||||
400 => 'Bad Request',
|
|
||||||
401 => 'Unauthorized',
|
|
||||||
402 => 'Payment Required',
|
|
||||||
403 => 'Forbidden',
|
|
||||||
404 => 'Not Found',
|
|
||||||
405 => 'Method Not Allowed',
|
|
||||||
406 => 'Not Acceptable',
|
|
||||||
407 => 'Proxy Authentication Required',
|
|
||||||
408 => 'Request Timeout',
|
|
||||||
409 => 'Conflict',
|
|
||||||
410 => 'Gone',
|
|
||||||
411 => 'Length Required',
|
|
||||||
412 => 'Precondition Failed',
|
|
||||||
413 => 'Request Entity Too Large',
|
|
||||||
414 => 'Request-URI Too Large',
|
|
||||||
415 => 'Unsupported Media Type',
|
|
||||||
416 => 'Request Range Not Satisfiable',
|
|
||||||
417 => 'Expectation Failed',
|
|
||||||
422 => 'Unprocessable Entity',
|
|
||||||
423 => 'Locked',
|
|
||||||
424 => 'Failed Dependency',
|
|
||||||
500 => 'Internal Server Error',
|
|
||||||
501 => 'Not Implemented',
|
|
||||||
502 => 'Bad Gateway',
|
|
||||||
503 => 'Service Unavailable',
|
|
||||||
504 => 'Gateway Timeout',
|
|
||||||
505 => 'HTTP Version Not Supported',
|
|
||||||
507 => 'Insufficient Storage'
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusMessage.each do |code, reason_phrase|
|
|
||||||
klass = Class.new(Status)
|
|
||||||
klass.code = code
|
|
||||||
klass.reason_phrase = reason_phrase
|
|
||||||
klass_name = reason_phrase.gsub(/[ \-]/,'')
|
|
||||||
const_set(klass_name, klass)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
module Rack
|
|
||||||
class Response
|
|
||||||
module Helpers
|
|
||||||
DAV4Rack::HTTPStatus::StatusMessage.each do |code, reason_phrase|
|
|
||||||
name = reason_phrase.gsub(/[ \-]/,'_').downcase
|
|
||||||
define_method(name + '?') do
|
|
||||||
@status == code
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
22
lib/redmine_dmsf/vendor/dav4rack/interceptor.rb
vendored
22
lib/redmine_dmsf/vendor/dav4rack/interceptor.rb
vendored
@ -1,22 +0,0 @@
|
|||||||
require 'dav4rack/interceptor_resource'
|
|
||||||
module DAV4Rack
|
|
||||||
class Interceptor
|
|
||||||
def initialize(app, args={})
|
|
||||||
@roots = args[:mappings].keys
|
|
||||||
@args = args
|
|
||||||
@app = app
|
|
||||||
@intercept_methods = %w(OPTIONS PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
|
|
||||||
@intercept_methods -= args[:ignore_methods] if args[:ignore_methods]
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(env)
|
|
||||||
path = env['PATH_INFO'].downcase
|
|
||||||
method = env['REQUEST_METHOD'].upcase
|
|
||||||
app = nil
|
|
||||||
if(@roots.detect{|x| path =~ /^#{Regexp.escape(x.downcase)}\/?/}.nil? && @intercept_methods.include?(method))
|
|
||||||
app = DAV4Rack::Handler.new(:resource_class => InterceptorResource, :mappings => @args[:mappings], :log_to => @args[:log_to])
|
|
||||||
end
|
|
||||||
app ? app.call(env) : @app.call(env)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
require 'digest/sha1'
|
|
||||||
|
|
||||||
module DAV4Rack
|
|
||||||
|
|
||||||
class InterceptorResource < Resource
|
|
||||||
attr_reader :path, :options
|
|
||||||
|
|
||||||
def initialize(*args)
|
|
||||||
super
|
|
||||||
@root_paths = @options[:mappings].keys
|
|
||||||
@mappings = @options[:mappings]
|
|
||||||
end
|
|
||||||
|
|
||||||
def children
|
|
||||||
childs = @root_paths.find_all{|x|x =~ /^#{Regexp.escape(@path)}/}
|
|
||||||
childs = childs.map{|a| child a.gsub(/^#{Regexp.escape(@path)}/, '').split('/').delete_if{|x|x.empty?}.first }.flatten
|
|
||||||
end
|
|
||||||
|
|
||||||
def collection?
|
|
||||||
true if exist?
|
|
||||||
end
|
|
||||||
|
|
||||||
def exist?
|
|
||||||
!@root_paths.find_all{|x| x =~ /^#{Regexp.escape(@path)}/}.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def creation_date
|
|
||||||
Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def last_modified
|
|
||||||
Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def last_modified=(time)
|
|
||||||
Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def etag
|
|
||||||
Digest::SHA1.hexdigest(@path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def content_type
|
|
||||||
'text/html'
|
|
||||||
end
|
|
||||||
|
|
||||||
def content_length
|
|
||||||
0
|
|
||||||
end
|
|
||||||
|
|
||||||
def get(request, response)
|
|
||||||
raise Forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
def put(request, response)
|
|
||||||
raise Forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
def post(request, response)
|
|
||||||
raise Forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete
|
|
||||||
raise Forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
def copy(dest)
|
|
||||||
raise Forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
def move(dest)
|
|
||||||
raise Forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_collection
|
|
||||||
raise Forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
def ==(other)
|
|
||||||
path == other.path
|
|
||||||
end
|
|
||||||
|
|
||||||
def name
|
|
||||||
::File.basename(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def display_name
|
|
||||||
::File.basename(path.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def child(name, option={})
|
|
||||||
new_path = path.dup
|
|
||||||
new_path = '/' + new_path unless new_path[0,1] == '/'
|
|
||||||
new_path.slice!(-1) if new_path[-1,1] == '/'
|
|
||||||
name = '/' + name unless name[-1,1] == '/'
|
|
||||||
new_path = "#{new_path}#{name}"
|
|
||||||
new_public = public_path.dup
|
|
||||||
new_public = '/' + new_public unless new_public[0,1] == '/'
|
|
||||||
new_public.slice!(-1) if new_public[-1,1] == '/'
|
|
||||||
new_public = "#{new_public}#{name}"
|
|
||||||
if(key = @root_paths.find{|x| new_path =~ /^#{Regexp.escape(x.downcase)}\/?/})
|
|
||||||
@mappings[key][:resource_class].new(new_public, new_path.gsub(key, ''), request, response, {:root_uri_path => key, :user => @user}.merge(options).merge(@mappings[key]))
|
|
||||||
else
|
|
||||||
self.class.new(new_public, new_path, request, response, {:user => @user}.merge(options))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def descendants
|
|
||||||
list = []
|
|
||||||
children.each do |child|
|
|
||||||
list << child
|
|
||||||
list.concat(child.descendants)
|
|
||||||
end
|
|
||||||
list
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
40
lib/redmine_dmsf/vendor/dav4rack/lock.rb
vendored
40
lib/redmine_dmsf/vendor/dav4rack/lock.rb
vendored
@ -1,40 +0,0 @@
|
|||||||
module DAV4Rack
|
|
||||||
class Lock
|
|
||||||
|
|
||||||
def initialize(args={})
|
|
||||||
@args = args
|
|
||||||
@store = nil
|
|
||||||
@args[:created_at] = Time.now
|
|
||||||
@args[:updated_at] = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
def store
|
|
||||||
@store
|
|
||||||
end
|
|
||||||
|
|
||||||
def store=(s)
|
|
||||||
raise TypeError.new 'Expecting LockStore' unless s.respond_to? :remove
|
|
||||||
@store = s
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
if(@store)
|
|
||||||
@store.remove(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def remaining_timeout
|
|
||||||
@args[:timeout].to_i - (Time.now.to_i - @args[:created_at].to_i)
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(*args)
|
|
||||||
if(@args.has_key?(args.first.to_sym))
|
|
||||||
@args[args.first.to_sym]
|
|
||||||
elsif(args.first.to_s[-1,1] == '=')
|
|
||||||
@args[args.first.to_s[0, args.first.to_s.length - 1].to_sym] = args[1]
|
|
||||||
else
|
|
||||||
raise NoMethodError.new "Undefined method #{args.first} for #{self}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
61
lib/redmine_dmsf/vendor/dav4rack/lock_store.rb
vendored
61
lib/redmine_dmsf/vendor/dav4rack/lock_store.rb
vendored
@ -1,61 +0,0 @@
|
|||||||
require 'dav4rack/lock'
|
|
||||||
module DAV4Rack
|
|
||||||
class LockStore
|
|
||||||
class << self
|
|
||||||
def create
|
|
||||||
@locks_by_path = {}
|
|
||||||
@locks_by_token = {}
|
|
||||||
end
|
|
||||||
def add(lock)
|
|
||||||
@locks_by_path[lock.path] = lock
|
|
||||||
@locks_by_token[lock.token] = lock
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove(lock)
|
|
||||||
@locks_by_path.delete(lock.path)
|
|
||||||
@locks_by_token.delete(lock.token)
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_by_path(path)
|
|
||||||
@locks_by_path.map do |lpath, lock|
|
|
||||||
lpath == path && lock.remaining_timeout > 0 ? lock : nil
|
|
||||||
end.compact.first
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_by_token(token)
|
|
||||||
@locks_by_token.map do |ltoken, lock|
|
|
||||||
ltoken == token && lock.remaining_timeout > 0 ? lock : nil
|
|
||||||
end.compact.first
|
|
||||||
end
|
|
||||||
|
|
||||||
def explicit_locks(path)
|
|
||||||
@locks_by_path.map do |lpath, lock|
|
|
||||||
lpath == path && lock.remaining_timeout > 0 ? lock : nil
|
|
||||||
end.compact
|
|
||||||
end
|
|
||||||
|
|
||||||
def implicit_locks(path)
|
|
||||||
@locks_by_path.map do |lpath, lock|
|
|
||||||
lpath =~ /^#{Regexp.escape(path)}/ && lock.remaining_timeout > 0 && lock.depth > 0 ? lock : nil
|
|
||||||
end.compact
|
|
||||||
end
|
|
||||||
|
|
||||||
def explicitly_locked?(path)
|
|
||||||
self.explicit_locks(path).size > 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def implicitly_locked?(path)
|
|
||||||
self.implicit_locks(path).size > 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate(path, user, token)
|
|
||||||
l = Lock.new(:path => path, :user => user, :token => token)
|
|
||||||
l.store = self
|
|
||||||
add(l)
|
|
||||||
l
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
DAV4Rack::LockStore.create
|
|
||||||
30
lib/redmine_dmsf/vendor/dav4rack/logger.rb
vendored
30
lib/redmine_dmsf/vendor/dav4rack/logger.rb
vendored
@ -1,30 +0,0 @@
|
|||||||
require 'logger'
|
|
||||||
|
|
||||||
module DAV4Rack
|
|
||||||
# This is a simple wrapper for the Logger class. It allows easy access
|
|
||||||
# to log messages from the library.
|
|
||||||
class Logger
|
|
||||||
class << self
|
|
||||||
# args:: Arguments for Logger -> [path, level] (level is optional) or a Logger instance
|
|
||||||
# Set the path to the log file.
|
|
||||||
def set(*args)
|
|
||||||
if(%w(info debug warn fatal).all?{|meth| args.first.respond_to?(meth)})
|
|
||||||
@@logger = args.first
|
|
||||||
elsif(args.first.respond_to?(:to_s) && !args.first.to_s.empty?)
|
|
||||||
@@logger = ::Logger.new(args.first.to_s, 'weekly')
|
|
||||||
elsif(args.first)
|
|
||||||
raise 'Invalid type specified for logger'
|
|
||||||
end
|
|
||||||
if(args.size > 1)
|
|
||||||
@@logger.level = args[1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(*args)
|
|
||||||
if(defined? @@logger)
|
|
||||||
@@logger.send *args
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
148
lib/redmine_dmsf/vendor/dav4rack/remote_file.rb
vendored
148
lib/redmine_dmsf/vendor/dav4rack/remote_file.rb
vendored
@ -1,148 +0,0 @@
|
|||||||
require 'net/http'
|
|
||||||
require 'uri'
|
|
||||||
require 'digest/sha1'
|
|
||||||
require 'rack/file'
|
|
||||||
|
|
||||||
module DAV4Rack
|
|
||||||
|
|
||||||
class RemoteFile < Rack::File
|
|
||||||
|
|
||||||
attr_accessor :path
|
|
||||||
|
|
||||||
alias :to_path :path
|
|
||||||
|
|
||||||
# path:: path to file (Actual path, preferably a URL since this is a *REMOTE* file)
|
|
||||||
# args:: Hash of arguments:
|
|
||||||
# :size -> Integer - number of bytes
|
|
||||||
# :mime_type -> String - mime type
|
|
||||||
# :last_modified -> String/Time - Time of last modification
|
|
||||||
# :sendfile -> True or String to define sendfile header variation
|
|
||||||
# :cache_directory -> Where to store cached files
|
|
||||||
# :cache_ref -> Reference to be used for cache file name (useful for changing URLs like S3)
|
|
||||||
# :sendfile_prefix -> String directory prefix. Eg: 'webdav' will result in: /wedav/#{path.sub('http://', '')}
|
|
||||||
# :sendfile_fail_gracefully -> Boolean if true will simply proxy if unable to determine proper sendfile
|
|
||||||
def initialize(path, args={})
|
|
||||||
@path = path
|
|
||||||
@args = args
|
|
||||||
@heads = {}
|
|
||||||
@cache_file = args[:cache_directory] ? cache_file_path : nil
|
|
||||||
@redefine_prefix = nil
|
|
||||||
if(@cache_file && File.exists?(@cache_file))
|
|
||||||
@root = ''
|
|
||||||
@path_info = @cache_file
|
|
||||||
@path = @path_info
|
|
||||||
elsif(args[:sendfile])
|
|
||||||
@redefine_prefix = 'sendfile'
|
|
||||||
@sendfile_header = args[:sendfile].is_a?(String) ? args[:sendfile] : nil
|
|
||||||
else
|
|
||||||
setup_remote
|
|
||||||
end
|
|
||||||
do_redefines(@redefine_prefix) if @redefine_prefix
|
|
||||||
end
|
|
||||||
|
|
||||||
# env:: Environment variable hash
|
|
||||||
# Process the call
|
|
||||||
def call(env)
|
|
||||||
serving(env)
|
|
||||||
end
|
|
||||||
|
|
||||||
# env:: Environment variable hash
|
|
||||||
# Return an empty result with the proper header information
|
|
||||||
def sendfile_serving(env)
|
|
||||||
header = @sendfile_header || env['sendfile.type'] || env['HTTP_X_SENDFILE_TYPE']
|
|
||||||
unless(header)
|
|
||||||
raise 'Failed to determine proper sendfile header value' unless @args[:sendfile_fail_gracefully]
|
|
||||||
setup_remote
|
|
||||||
do_redefines('remote')
|
|
||||||
call(env)
|
|
||||||
end
|
|
||||||
prefix = (@args[:sendfile_prefix] || env['HTTP_X_ACCEL_REMOTE_MAPPING']).to_s.sub(/^\//, '').sub(/\/$/, '')
|
|
||||||
[200, {
|
|
||||||
"Last-Modified" => last_modified,
|
|
||||||
"Content-Type" => content_type,
|
|
||||||
"Content-Length" => size,
|
|
||||||
"Redirect-URL" => @path,
|
|
||||||
"Redirect-Host" => @path.scan(%r{^https?://([^/\?]+)}).first.first,
|
|
||||||
header => "/#{prefix}"
|
|
||||||
},
|
|
||||||
['']]
|
|
||||||
end
|
|
||||||
|
|
||||||
# env:: Environment variable hash
|
|
||||||
# Return self to be processed
|
|
||||||
def remote_serving(e)
|
|
||||||
[200, {
|
|
||||||
"Last-Modified" => last_modified,
|
|
||||||
"Content-Type" => content_type,
|
|
||||||
"Content-Length" => size
|
|
||||||
}, self]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Get the remote file
|
|
||||||
def remote_each
|
|
||||||
if(@store)
|
|
||||||
yield @store
|
|
||||||
else
|
|
||||||
@con.request_get(@call_path) do |res|
|
|
||||||
res.read_body(@store) do |part|
|
|
||||||
@cf.write part if @cf
|
|
||||||
yield part
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Size based on remote headers or given size
|
|
||||||
def size
|
|
||||||
@heads['content-length'] || @size
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Content type based on provided or remote headers
|
|
||||||
def content_type
|
|
||||||
@mime_type || @heads['content-type']
|
|
||||||
end
|
|
||||||
|
|
||||||
# Last modified type based on provided, remote headers or current time
|
|
||||||
def last_modified
|
|
||||||
@heads['last-modified'] || @modified || Time.now.httpdate
|
|
||||||
end
|
|
||||||
|
|
||||||
# Builds the path for the cached file
|
|
||||||
def cache_file_path
|
|
||||||
raise IOError.new 'Write permission is required for cache directory' unless File.writable?(@args[:cache_directory])
|
|
||||||
"#{@args[:cache_directory]}/#{Digest::SHA1.hexdigest((@args[:cache_ref] || @path).to_s + size.to_s + last_modified.to_s)}.cache"
|
|
||||||
end
|
|
||||||
|
|
||||||
# prefix:: prefix of methods to be redefined
|
|
||||||
# Redefine methods to do what we want in the proper situation
|
|
||||||
def do_redefines(prefix)
|
|
||||||
self.public_methods.each do |method|
|
|
||||||
m = method.to_s.dup
|
|
||||||
next unless m.slice!(0, prefix.to_s.length + 1) == "#{prefix}_"
|
|
||||||
self.class.class_eval "undef :'#{m}'"
|
|
||||||
self.class.class_eval "alias :'#{m}' :'#{method}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets up all the requirements for proxying a remote file
|
|
||||||
def setup_remote
|
|
||||||
if(@cache_file)
|
|
||||||
begin
|
|
||||||
@cf = File.open(@cache_file, 'w+')
|
|
||||||
rescue
|
|
||||||
@cf = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@uri = URI.parse(@path)
|
|
||||||
@con = Net::HTTP.new(@uri.host, @uri.port)
|
|
||||||
@call_path = @uri.path + (@uri.query ? "?#{@uri.query}" : '')
|
|
||||||
res = @con.request_get(@call_path)
|
|
||||||
@heads = res.to_hash
|
|
||||||
res.value
|
|
||||||
@store = nil
|
|
||||||
@redefine_prefix = 'remote'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
469
lib/redmine_dmsf/vendor/dav4rack/resource.rb
vendored
469
lib/redmine_dmsf/vendor/dav4rack/resource.rb
vendored
@ -1,469 +0,0 @@
|
|||||||
require 'uuidtools'
|
|
||||||
require 'dav4rack/http_status'
|
|
||||||
|
|
||||||
module DAV4Rack
|
|
||||||
|
|
||||||
class LockFailure < RuntimeError
|
|
||||||
attr_reader :path_status
|
|
||||||
def initialize(*args)
|
|
||||||
super(*args)
|
|
||||||
@path_status = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_failure(path, status)
|
|
||||||
@path_status[path] = status
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Resource
|
|
||||||
attr_reader :path, :options, :public_path, :request,
|
|
||||||
:response, :propstat_relative_path, :root_xml_attributes
|
|
||||||
attr_accessor :user
|
|
||||||
@@blocks = {}
|
|
||||||
|
|
||||||
class << self
|
|
||||||
|
|
||||||
# This lets us define a bunch of before and after blocks that are
|
|
||||||
# either called before all methods on the resource, or only specific
|
|
||||||
# methods on the resource
|
|
||||||
def method_missing(*args, &block)
|
|
||||||
class_sym = self.name.to_sym
|
|
||||||
@@blocks[class_sym] ||= {:before => {}, :after => {}}
|
|
||||||
m = args.shift
|
|
||||||
parts = m.to_s.split('_')
|
|
||||||
type = parts.shift.to_s.to_sym
|
|
||||||
method = parts.empty? ? nil : parts.join('_').to_sym
|
|
||||||
if(@@blocks[class_sym][type] && block_given?)
|
|
||||||
if(method)
|
|
||||||
@@blocks[class_sym][type][method] ||= []
|
|
||||||
@@blocks[class_sym][type][method] << block
|
|
||||||
else
|
|
||||||
@@blocks[class_sym][type][:'__all__'] ||= []
|
|
||||||
@@blocks[class_sym][type][:'__all__'] << block
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise NoMethodError.new("Undefined method #{m} for class #{self}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
include DAV4Rack::HTTPStatus
|
|
||||||
|
|
||||||
# public_path:: Path received via request
|
|
||||||
# path:: Internal resource path (Only different from public path when using root_uri's for webdav)
|
|
||||||
# request:: Rack::Request
|
|
||||||
# options:: Any options provided for this resource
|
|
||||||
# Creates a new instance of the resource.
|
|
||||||
# NOTE: path and public_path will only differ if the root_uri has been set for the resource. The
|
|
||||||
# controller will strip out the starting path so the resource can easily determine what
|
|
||||||
# it is working on. For example:
|
|
||||||
# request -> /my/webdav/directory/actual/path
|
|
||||||
# public_path -> /my/webdav/directory/actual/path
|
|
||||||
# path -> /actual/path
|
|
||||||
# NOTE: Customized Resources should not use initialize for setup. Instead
|
|
||||||
# use the #setup method
|
|
||||||
def initialize(public_path, path, request, response, options)
|
|
||||||
@skip_alias = [
|
|
||||||
:authenticate, :authentication_error_msg,
|
|
||||||
:authentication_realm, :path, :options,
|
|
||||||
:public_path, :request, :response, :user,
|
|
||||||
:user=, :setup
|
|
||||||
]
|
|
||||||
@public_path = public_path.dup
|
|
||||||
@path = path.dup
|
|
||||||
@propstat_relative_path = !!options.delete(:propstat_relative_path)
|
|
||||||
@root_xml_attributes = options.delete(:root_xml_attributes) || {}
|
|
||||||
@request = request
|
|
||||||
@response = response
|
|
||||||
unless(options.has_key?(:lock_class))
|
|
||||||
require 'dav4rack/lock_store'
|
|
||||||
@lock_class = LockStore
|
|
||||||
else
|
|
||||||
@lock_class = options[:lock_class]
|
|
||||||
raise NameError.new("Unknown lock type constant provided: #{@lock_class}") unless @lock_class.nil? || defined?(@lock_class)
|
|
||||||
end
|
|
||||||
@options = options.dup
|
|
||||||
@max_timeout = options[:max_timeout] || 86400
|
|
||||||
@default_timeout = options[:default_timeout] || 60
|
|
||||||
@user = @options[:user] || request.ip
|
|
||||||
setup if respond_to?(:setup)
|
|
||||||
public_methods(false).each do |method|
|
|
||||||
next if @skip_alias.include?(method.to_sym) || method[0,4] == 'DAV_' || method[0,5] == '_DAV_'
|
|
||||||
self.class.class_eval "alias :'_DAV_#{method}' :'#{method}'"
|
|
||||||
self.class.class_eval "undef :'#{method}'"
|
|
||||||
end
|
|
||||||
@runner = lambda do |class_sym, kind, method_name|
|
|
||||||
[:'__all__', method_name.to_sym].each do |sym|
|
|
||||||
if(@@blocks[class_sym] && @@blocks[class_sym][kind] && @@blocks[class_sym][kind][sym])
|
|
||||||
@@blocks[class_sym][kind][sym].each do |b|
|
|
||||||
args = [self, sym == :'__all__' ? method_name : nil].compact
|
|
||||||
b.call(*args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This allows us to call before and after blocks
|
|
||||||
def method_missing(*args)
|
|
||||||
result = nil
|
|
||||||
orig = args.shift
|
|
||||||
class_sym = self.class.name.to_sym
|
|
||||||
m = orig.to_s[0,5] == '_DAV_' ? orig : "_DAV_#{orig}" # If hell is doing the same thing over and over and expecting a different result this is a hell preventer
|
|
||||||
raise NoMethodError.new("Undefined method: #{orig} for class #{self}.") unless respond_to?(m)
|
|
||||||
@runner.call(class_sym, :before, orig)
|
|
||||||
result = send m, *args
|
|
||||||
@runner.call(class_sym, :after, orig)
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
# If this is a collection, return the child resources.
|
|
||||||
def children
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# Is this resource a collection?
|
|
||||||
def collection?
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# Does this resource exist?
|
|
||||||
def exist?
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# Does the parent resource exist?
|
|
||||||
def parent_exists?
|
|
||||||
parent.exist?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the creation time.
|
|
||||||
def creation_date
|
|
||||||
raise NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the time of last modification.
|
|
||||||
def last_modified
|
|
||||||
raise NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set the time of last modification.
|
|
||||||
def last_modified=(time)
|
|
||||||
# Is this correct?
|
|
||||||
raise NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return an Etag, an unique hash value for this resource.
|
|
||||||
def etag
|
|
||||||
raise NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the resource type. Generally only used to specify
|
|
||||||
# resource is a collection.
|
|
||||||
def resource_type
|
|
||||||
:collection if collection?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the mime type of this resource.
|
|
||||||
def content_type
|
|
||||||
raise NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return the size in bytes for this resource.
|
|
||||||
def content_length
|
|
||||||
raise NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP GET request.
|
|
||||||
#
|
|
||||||
# Write the content of the resource to the response.body.
|
|
||||||
def get(request, response)
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP PUT request.
|
|
||||||
#
|
|
||||||
# Save the content of the request.body.
|
|
||||||
def put(request, response)
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP POST request.
|
|
||||||
#
|
|
||||||
# Usually forbidden.
|
|
||||||
def post(request, response)
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP DELETE request.
|
|
||||||
#
|
|
||||||
# Delete this resource.
|
|
||||||
def delete
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP COPY request.
|
|
||||||
#
|
|
||||||
# Copy this resource to given destination resource.
|
|
||||||
def copy(dest, overwrite=false)
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# HTTP MOVE request.
|
|
||||||
#
|
|
||||||
# Move this resource to given destination resource.
|
|
||||||
def move(dest, overwrite=false)
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# args:: Hash of lock arguments
|
|
||||||
# Request for a lock on the given resource. A valid lock should lock
|
|
||||||
# all descendents. Failures should be noted and returned as an exception
|
|
||||||
# using LockFailure.
|
|
||||||
# Valid args keys: :timeout -> requested timeout
|
|
||||||
# :depth -> lock depth
|
|
||||||
# :scope -> lock scope
|
|
||||||
# :type -> lock type
|
|
||||||
# :owner -> lock owner
|
|
||||||
# Should return a tuple: [lock_time, locktoken] where lock_time is the
|
|
||||||
# given timeout
|
|
||||||
# NOTE: See section 9.10 of RFC 4918 for guidance about
|
|
||||||
# how locks should be generated and the expected responses
|
|
||||||
# (http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10)
|
|
||||||
|
|
||||||
def lock(args)
|
|
||||||
unless(@lock_class)
|
|
||||||
NotImplemented
|
|
||||||
else
|
|
||||||
unless(parent_exists?)
|
|
||||||
Conflict
|
|
||||||
else
|
|
||||||
lock_check(args[:scope])
|
|
||||||
lock = @lock_class.explicit_locks(@path).find{|l| l.scope == args[:scope] && l.kind == args[:type] && l.user == @user}
|
|
||||||
unless(lock)
|
|
||||||
token = UUIDTools::UUID.random_create.to_s
|
|
||||||
lock = @lock_class.generate(@path, @user, token)
|
|
||||||
lock.scope = args[:scope]
|
|
||||||
lock.kind = args[:type]
|
|
||||||
lock.owner = args[:owner]
|
|
||||||
lock.depth = args[:depth].is_a?(Symbol) ? args[:depth] : args[:depth].to_i
|
|
||||||
if(args[:timeout])
|
|
||||||
lock.timeout = args[:timeout] <= @max_timeout && args[:timeout] > 0 ? args[:timeout] : @max_timeout
|
|
||||||
else
|
|
||||||
lock.timeout = @default_timeout
|
|
||||||
end
|
|
||||||
lock.save if lock.respond_to? :save
|
|
||||||
end
|
|
||||||
begin
|
|
||||||
lock_check(args[:type])
|
|
||||||
rescue DAV4Rack::LockFailure => lock_failure
|
|
||||||
lock.destroy
|
|
||||||
raise lock_failure
|
|
||||||
rescue HTTPStatus::Status => status
|
|
||||||
status
|
|
||||||
end
|
|
||||||
[lock.remaining_timeout, lock.token]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# lock_scope:: scope of lock
|
|
||||||
# Check if resource is locked. Raise DAV4Rack::LockFailure if locks are in place.
|
|
||||||
def lock_check(lock_scope=nil)
|
|
||||||
return unless @lock_class
|
|
||||||
if(@lock_class.explicitly_locked?(@path))
|
|
||||||
raise Locked if @lock_class.explicit_locks(@path).find_all{|l|l.scope == 'exclusive' && l.user != @user}.size > 0
|
|
||||||
elsif(@lock_class.implicitly_locked?(@path))
|
|
||||||
if(lock_scope.to_s == 'exclusive')
|
|
||||||
locks = @lock_class.implicit_locks(@path)
|
|
||||||
failure = DAV4Rack::LockFailure.new("Failed to lock: #{@path}")
|
|
||||||
locks.each do |lock|
|
|
||||||
failure.add_failure(@path, Locked)
|
|
||||||
end
|
|
||||||
raise failure
|
|
||||||
else
|
|
||||||
locks = @lock_class.implict_locks(@path).find_all{|l| l.scope == 'exclusive' && l.user != @user}
|
|
||||||
if(locks.size > 0)
|
|
||||||
failure = LockFailure.new("Failed to lock: #{@path}")
|
|
||||||
locks.each do |lock|
|
|
||||||
failure.add_failure(@path, Locked)
|
|
||||||
end
|
|
||||||
raise failure
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# token:: Lock token
|
|
||||||
# Remove the given lock
|
|
||||||
def unlock(token)
|
|
||||||
unless(@lock_class)
|
|
||||||
NotImplemented
|
|
||||||
else
|
|
||||||
token = token.slice(1, token.length - 2)
|
|
||||||
if(token.nil? || token.empty?)
|
|
||||||
BadRequest
|
|
||||||
else
|
|
||||||
lock = @lock_class.find_by_token(token)
|
|
||||||
if(lock.nil? || lock.user != @user)
|
|
||||||
Forbidden
|
|
||||||
elsif(lock.path !~ /^#{Regexp.escape(@path)}.*$/)
|
|
||||||
Conflict
|
|
||||||
else
|
|
||||||
lock.destroy
|
|
||||||
NoContent
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Create this resource as collection.
|
|
||||||
def make_collection
|
|
||||||
NotImplemented
|
|
||||||
end
|
|
||||||
|
|
||||||
# other:: Resource
|
|
||||||
# Returns if current resource is equal to other resource
|
|
||||||
def ==(other)
|
|
||||||
path == other.path
|
|
||||||
end
|
|
||||||
|
|
||||||
# Name of the resource
|
|
||||||
def name
|
|
||||||
File.basename(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Name of the resource to be displayed to the client
|
|
||||||
def display_name
|
|
||||||
name
|
|
||||||
end
|
|
||||||
|
|
||||||
# Available properties
|
|
||||||
def property_names
|
|
||||||
%w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength)
|
|
||||||
end
|
|
||||||
|
|
||||||
# name:: String - Property name
|
|
||||||
# Returns the value of the given property
|
|
||||||
def get_property(name)
|
|
||||||
case name
|
|
||||||
when 'resourcetype' then resource_type
|
|
||||||
when 'displayname' then display_name
|
|
||||||
when 'creationdate' then use_ms_compat_creationdate? ? creation_date.httpdate : creation_date.xmlschema
|
|
||||||
when 'getcontentlength' then content_length.to_s
|
|
||||||
when 'getcontenttype' then content_type
|
|
||||||
when 'getetag' then etag
|
|
||||||
when 'getlastmodified' then last_modified.httpdate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# name:: String - Property name
|
|
||||||
# value:: New value
|
|
||||||
# Set the property to the given value
|
|
||||||
def set_property(name, value)
|
|
||||||
case name
|
|
||||||
when 'resourcetype' then self.resource_type = value
|
|
||||||
when 'getcontenttype' then self.content_type = value
|
|
||||||
when 'getetag' then self.etag = value
|
|
||||||
when 'getlastmodified' then self.last_modified = Time.httpdate(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# name:: Property name
|
|
||||||
# Remove the property from the resource
|
|
||||||
def remove_property(name)
|
|
||||||
Forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
# name:: Name of child
|
|
||||||
# Create a new child with the given name
|
|
||||||
# NOTE:: Include trailing '/' if child is collection
|
|
||||||
def child(name)
|
|
||||||
new_public = public_path.dup
|
|
||||||
new_public = new_public + '/' unless new_public[-1,1] == '/'
|
|
||||||
new_public = '/' + new_public unless new_public[0,1] == '/'
|
|
||||||
new_path = path.dup
|
|
||||||
new_path = new_path + '/' unless new_path[-1,1] == '/'
|
|
||||||
new_path = '/' + new_path unless new_path[0,1] == '/'
|
|
||||||
self.class.new("#{new_public}#{name}", "#{new_path}#{name}", request, response, options.merge(:user => @user))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return parent of this resource
|
|
||||||
def parent
|
|
||||||
unless(@path.to_s.empty?)
|
|
||||||
self.class.new(
|
|
||||||
File.split(@public_path).first,
|
|
||||||
File.split(@path).first,
|
|
||||||
@request,
|
|
||||||
@response,
|
|
||||||
@options.merge(
|
|
||||||
:user => @user
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return list of descendants
|
|
||||||
def descendants
|
|
||||||
list = []
|
|
||||||
children.each do |child|
|
|
||||||
list << child
|
|
||||||
list.concat(child.descendants)
|
|
||||||
end
|
|
||||||
list
|
|
||||||
end
|
|
||||||
|
|
||||||
# Index page template for GETs on collection
|
|
||||||
def index_page
|
|
||||||
'<html><head> <title>%s</title>
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" /></head>
|
|
||||||
<body> <h1>%s</h1> <hr /> <table> <tr> <th class="name">Name</th>
|
|
||||||
<th class="size">Size</th> <th class="type">Type</th>
|
|
||||||
<th class="mtime">Last Modified</th> </tr> %s </table> <hr /> </body></html>'
|
|
||||||
end
|
|
||||||
|
|
||||||
# Does client allow GET redirection
|
|
||||||
# TODO: Get a comprehensive list in here.
|
|
||||||
# TODO: Allow this to be dynamic so users can add regexes to match if they know of a client
|
|
||||||
# that can be supported that is not listed.
|
|
||||||
def allows_redirect?
|
|
||||||
[
|
|
||||||
%r{cyberduck}i,
|
|
||||||
%r{konqueror}i
|
|
||||||
].any? do |regexp|
|
|
||||||
(request.respond_to?(:user_agent) ? request.user_agent : request.env['HTTP_USER_AGENT']).to_s =~ regexp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def use_compat_mkcol_response?
|
|
||||||
@options[:compat_mkcol] || @options[:compat_all]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns true if using an MS client
|
|
||||||
def use_ms_compat_creationdate?
|
|
||||||
if(@options[:compat_ms_mangled_creationdate] || @options[:compat_all])
|
|
||||||
is_ms_client?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Basic user agent testing for MS authored client
|
|
||||||
def is_ms_client?
|
|
||||||
[%r{microsoft-webdav}i, %r{microsoft office}i].any? do |regexp|
|
|
||||||
(request.respond_to?(:user_agent) ? request.user_agent : request.env['HTTP_USER_AGENT']).to_s =~ regexp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Returns authentication credentials if available in form of [username,password]
|
|
||||||
# TODO: Add support for digest
|
|
||||||
def auth_credentials
|
|
||||||
auth = Rack::Auth::Basic::Request.new(request.env)
|
|
||||||
auth.provided? && auth.basic? ? auth.credentials : [nil,nil]
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
17
lib/redmine_dmsf/vendor/dav4rack/version.rb
vendored
17
lib/redmine_dmsf/vendor/dav4rack/version.rb
vendored
@ -1,17 +0,0 @@
|
|||||||
module DAV4Rack
|
|
||||||
class Version
|
|
||||||
|
|
||||||
attr_reader :major, :minor, :tiny
|
|
||||||
|
|
||||||
def initialize(version)
|
|
||||||
version = version.split('.')
|
|
||||||
@major, @minor, @tiny = [version[0].to_i, version[1].to_i, version[2].to_i]
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
"#{@major}.#{@minor}.#{@tiny}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
VERSION = Version.new('0.2.11')
|
|
||||||
end
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
# Redmine plugin for Document Management System "Features"
|
# Redmine plugin for Document Management System "Features"
|
||||||
#
|
#
|
||||||
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
|
# Copyright (C) 2012 Daniel Munn <dan.munn@munnster.co.uk>
|
||||||
|
# Copyright (C) 2011-14 Karel Picman <karel.picman@kontron.com>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# modify it under the terms of the GNU General Public License
|
||||||
@ -20,12 +21,12 @@ module RedmineDmsf
|
|||||||
module Webdav
|
module Webdav
|
||||||
class Controller < DAV4Rack::Controller
|
class Controller < DAV4Rack::Controller
|
||||||
|
|
||||||
#Overload default options
|
# Overload default options
|
||||||
def options
|
def options
|
||||||
raise NotFound unless resource.exist?
|
raise NotFound unless resource.exist?
|
||||||
response["Allow"] = 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
|
response['Allow'] = 'OPTIONS,HEAD,GET,PUT,POST,DELETE,PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK'
|
||||||
response["Dav"] = "1,2,3"
|
response['Dav'] = '1,2,3'
|
||||||
response["Ms-Author-Via"] = "DAV"
|
response['Ms-Author-Via'] = 'DAV'
|
||||||
OK
|
OK
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ module RedmineDmsf
|
|||||||
begin
|
begin
|
||||||
request.env['Timeout'] = request.env['HTTP_TIMEOUT'].split('-',2).join(',') unless request.env['HTTP_TIMEOUT'].nil?
|
request.env['Timeout'] = request.env['HTTP_TIMEOUT'].split('-',2).join(',') unless request.env['HTTP_TIMEOUT'].nil?
|
||||||
rescue
|
rescue
|
||||||
#Nothing here
|
# Nothing here
|
||||||
end
|
end
|
||||||
|
|
||||||
request_document.remove_namespaces! if ns.empty?
|
request_document.remove_namespaces! if ns.empty?
|
||||||
@ -46,7 +47,7 @@ module RedmineDmsf
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
#Overload the default propfind function with this
|
# Overload the default propfind function with this
|
||||||
def propfind
|
def propfind
|
||||||
unless(resource.exist?)
|
unless(resource.exist?)
|
||||||
NotFound
|
NotFound
|
||||||
@ -94,10 +95,22 @@ module RedmineDmsf
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
response.body = doc.to_xml
|
response.body = doc.to_xml
|
||||||
response["Content-Type"] = 'application/xml; charset="utf-8"'
|
response['Content-Type'] = 'application/xml; charset="utf-8"'
|
||||||
response["Content-Length"] = response.body.bytesize.to_s
|
response['Content-Length'] = response.body.bytesize.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns Resource path with root URI removed
|
||||||
|
def implied_path
|
||||||
|
|
||||||
|
return clean_path(@request.path_info.dup) unless @request.path_info.empty?
|
||||||
|
c_path = clean_path(@request.path.dup)
|
||||||
|
return c_path if c_path.length != @request.path.length
|
||||||
|
|
||||||
|
# If we're here then it's probably down to thin
|
||||||
|
return @request.path.dup.gsub!(/^#{Regexp.escape(@request.script_name)}/, '') unless @request.script_name.empty?
|
||||||
|
|
||||||
|
return c_path # This will probably result in a processing error if we hit here
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def ns(opt_head = '')
|
def ns(opt_head = '')
|
||||||
@ -111,4 +124,4 @@ module RedmineDmsf
|
|||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user