ServeWebdav 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +4 -0
- data/Manifest.txt +10 -0
- data/README.txt +49 -0
- data/Rakefile +17 -0
- data/lib/serve_webdav/errors.rb +68 -0
- data/lib/serve_webdav/raw_data_patch.rb +77 -0
- data/lib/serve_webdav/resource.rb +66 -0
- data/lib/serve_webdav/serve_webdav.rb +419 -0
- data/lib/serve_webdav.rb +14 -0
- data/test/test_serve_webdav.rb +0 -0
- metadata +63 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
ServeWebdav
|
2
|
+
by Stuart Eccles and Adocca AB
|
3
|
+
http://www.adocca.com
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Based on the RailsDav plugin by Stuart Eccles, but somewhat altered. Provides a webdav interface
|
8
|
+
for rails applications.
|
9
|
+
|
10
|
+
== FEATURES/PROBLEMS:
|
11
|
+
|
12
|
+
TODO write better docs
|
13
|
+
|
14
|
+
== SYNOPSYS:
|
15
|
+
|
16
|
+
TODO write better docs...
|
17
|
+
|
18
|
+
== REQUIREMENTS:
|
19
|
+
|
20
|
+
* Rails 1.1.6
|
21
|
+
|
22
|
+
== INSTALL:
|
23
|
+
|
24
|
+
* gem install serve_webdav
|
25
|
+
|
26
|
+
== LICENSE:
|
27
|
+
|
28
|
+
(The MIT License)
|
29
|
+
|
30
|
+
Copyright (c) 2006 Stuart Eccles & Adocca AB
|
31
|
+
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
33
|
+
a copy of this software and associated documentation files (the
|
34
|
+
'Software'), to deal in the Software without restriction, including
|
35
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
36
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
37
|
+
permit persons to whom the Software is furnished to do so, subject to
|
38
|
+
the following conditions:
|
39
|
+
|
40
|
+
The above copyright notice and this permission notice shall be
|
41
|
+
included in all copies or substantial portions of the Software.
|
42
|
+
|
43
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
44
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
45
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
46
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
47
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
48
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
49
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
|
6
|
+
require './lib/serve_webdav.rb'
|
7
|
+
|
8
|
+
|
9
|
+
Hoe.new('ServeWebdav', ServeWebdav::VERSION) do |p|
|
10
|
+
p.rubyforge_name = 'adocca_plugins'
|
11
|
+
p.summary = 'Webdav protocol abstraction for rails controllers'
|
12
|
+
# p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
|
13
|
+
# p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
14
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
# vim: syntax=Ruby
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module WebDavErrors
|
4
|
+
|
5
|
+
class BaseError < Exception
|
6
|
+
end
|
7
|
+
|
8
|
+
class LockedError < WebDavErrors::BaseError
|
9
|
+
@@http_status = 423
|
10
|
+
cattr_accessor :http_status
|
11
|
+
end
|
12
|
+
|
13
|
+
class InsufficientStorageError < WebDavErrors::BaseError
|
14
|
+
@@http_status = 507
|
15
|
+
cattr_accessor :http_status
|
16
|
+
end
|
17
|
+
|
18
|
+
class ConflictError < WebDavErrors::BaseError
|
19
|
+
@@http_status = 405
|
20
|
+
cattr_accessor :http_status
|
21
|
+
end
|
22
|
+
|
23
|
+
class ForbiddenError < WebDavErrors::BaseError
|
24
|
+
@@http_status = 403
|
25
|
+
cattr_accessor :http_status
|
26
|
+
end
|
27
|
+
|
28
|
+
class BadGatewayError < WebDavErrors::BaseError
|
29
|
+
@@http_status = 502
|
30
|
+
cattr_accessor :http_status
|
31
|
+
end
|
32
|
+
|
33
|
+
class PreconditionFailsError < WebDavErrors::BaseError
|
34
|
+
@@http_status = 412
|
35
|
+
cattr_accessor :http_status
|
36
|
+
end
|
37
|
+
|
38
|
+
class NotFoundError < WebDavErrors::BaseError
|
39
|
+
@@http_status = 404
|
40
|
+
cattr_accessor :http_status
|
41
|
+
end
|
42
|
+
|
43
|
+
class UnSupportedTypeError < WebDavErrors::BaseError
|
44
|
+
@@http_status = 415
|
45
|
+
cattr_accessor :http_status
|
46
|
+
end
|
47
|
+
|
48
|
+
class UnknownWebDavMethodError < WebDavErrors::BaseError
|
49
|
+
@@http_status = 405
|
50
|
+
cattr_accessor :http_status
|
51
|
+
end
|
52
|
+
|
53
|
+
class ConflictWebDavError < WebDavErrors::BaseError
|
54
|
+
@@http_status = 409
|
55
|
+
cattr_accessor :http_status
|
56
|
+
end
|
57
|
+
|
58
|
+
class BadRequestBodyError < WebDavErrors::BaseError
|
59
|
+
@@http_status = 400
|
60
|
+
cattr_accessor :http_status
|
61
|
+
end
|
62
|
+
|
63
|
+
class EmptyPutError < WebDavErrors::BaseError
|
64
|
+
@@http_status = 200
|
65
|
+
cattr_accessor :http_status
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class CGI #:nodoc:
|
2
|
+
# Add @request.env['RAW_POST_DATA'] for the vegans.
|
3
|
+
module QueryExtension
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def read_query_params(method)
|
8
|
+
case method
|
9
|
+
when :get
|
10
|
+
read_params_from_query
|
11
|
+
when :post, :proppatch, :propfind
|
12
|
+
read_params_from_post
|
13
|
+
when :put
|
14
|
+
read_params_from_post
|
15
|
+
""
|
16
|
+
when :cmd
|
17
|
+
read_from_cmdline
|
18
|
+
else # when :head, :delete, :options
|
19
|
+
read_params_from_query
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end # module QueryExtension
|
23
|
+
end
|
24
|
+
|
25
|
+
class CGIMethods
|
26
|
+
def self.typecast_xml_value(value)
|
27
|
+
case value
|
28
|
+
when Hash
|
29
|
+
if value.has_key?("__content__")
|
30
|
+
content = translate_xml_entities(value["__content__"])
|
31
|
+
case value["type"]
|
32
|
+
when "integer" then content.to_i
|
33
|
+
when "boolean" then content == "true"
|
34
|
+
when "datetime" then Time.parse(content)
|
35
|
+
when "date" then Date.parse(content)
|
36
|
+
else content
|
37
|
+
end
|
38
|
+
else
|
39
|
+
value.empty? ? nil : value.inject({}) do |h,(k,v)|
|
40
|
+
h[k] = typecast_xml_value(v)
|
41
|
+
h
|
42
|
+
end
|
43
|
+
end
|
44
|
+
when Array
|
45
|
+
value.map! { |i| typecast_xml_value(i) }
|
46
|
+
case value.length
|
47
|
+
when 0 then nil
|
48
|
+
when 1 then value.first
|
49
|
+
else value
|
50
|
+
end
|
51
|
+
when String
|
52
|
+
value
|
53
|
+
else
|
54
|
+
raise "can't typecast #{value.inspect}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class CGIMethods
|
60
|
+
def CGIMethods.parse_request_parameters(params)
|
61
|
+
parsed_params = {}
|
62
|
+
|
63
|
+
for key, value in params
|
64
|
+
value = [value] if key =~ /.*\[\]$/
|
65
|
+
next if key.nil?
|
66
|
+
if !key.nil? && !key.include?('[')
|
67
|
+
# much faster to test for the most common case first (GET)
|
68
|
+
# and avoid the call to build_deep_hash
|
69
|
+
parsed_params[key] = get_typed_value(value[0])
|
70
|
+
else
|
71
|
+
build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
parsed_params
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright (c) 2006 Stuart Eccles
|
2
|
+
# Released under the MIT License. See the LICENSE file for more details.
|
3
|
+
class WebDavResource
|
4
|
+
|
5
|
+
def properties
|
6
|
+
Array.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def delete!
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def move! (dest_path, depth)
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy! (dest_path, depth)
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def status
|
22
|
+
gen_status(200, "OK").to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def collection?
|
26
|
+
return false
|
27
|
+
end
|
28
|
+
|
29
|
+
def children
|
30
|
+
return []
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_displayname
|
34
|
+
CGI::escape(self.displayname).gsub(/\+/, '%20')
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_href
|
38
|
+
self.href.gsub(/\+/, '%20')
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_properties
|
42
|
+
hsh = {}
|
43
|
+
self.properties.each do|meth|
|
44
|
+
if self.respond_to?('get_'+meth.to_s)
|
45
|
+
hsh[meth] = self.send(('get_'+meth.to_s).to_sym)
|
46
|
+
else
|
47
|
+
hsh[meth] = self.send(meth)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
hsh
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
def gen_element(elem, text = nil, attrib = {})
|
55
|
+
e = REXML::Element.new elem
|
56
|
+
text and e.text = text
|
57
|
+
attrib.each {|k, v| e.attributes[k] = v }
|
58
|
+
e
|
59
|
+
end
|
60
|
+
|
61
|
+
def gen_status(status_code, reason_phrase)
|
62
|
+
"HTTP/1.1 #{status_code} #{reason_phrase}"
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
@@ -0,0 +1,419 @@
|
|
1
|
+
require 'action_controller'
|
2
|
+
module ServeWebdav
|
3
|
+
|
4
|
+
def self.append_features(base)
|
5
|
+
super
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def serve_webdav
|
11
|
+
class_eval do
|
12
|
+
extend ServeWebdav::SingletonMethods
|
13
|
+
end
|
14
|
+
include ServeWebdav::InstanceMethods
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module SingletonMethods
|
19
|
+
|
20
|
+
def set_max_propfind_depth( value=nil)
|
21
|
+
@@max_propfind_depth = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_max_propfind_depth
|
25
|
+
@@max_propfind_depth
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_propfind_xml
|
29
|
+
<<EOPROPFIND_XML
|
30
|
+
xml.D(:multistatus, {"xmlns:D" => "DAV:"}) do
|
31
|
+
@resources.each do |resource|
|
32
|
+
xml.D :response do
|
33
|
+
xml.D :href, resource.get_href
|
34
|
+
xml.D :propstat do
|
35
|
+
xml.D :prop do
|
36
|
+
resource.get_properties.each do |property, value|
|
37
|
+
xml.D(property, value)
|
38
|
+
end
|
39
|
+
xml.D :resourcetype do
|
40
|
+
xml.D :collection if resource.collection?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
xml.D :status, resource.status
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
EOPROPFIND_XML
|
49
|
+
end
|
50
|
+
def get_proppatch_xml
|
51
|
+
<<EOPROPPATCH_XML
|
52
|
+
xml.D(:multistatus, {"xmlns:D" => "DAV:"}) do
|
53
|
+
xml.D :response do
|
54
|
+
xml.D :href, @resource.get_href
|
55
|
+
for remove_property in @remove_properties
|
56
|
+
xml.D :propstat do
|
57
|
+
xml.D :prop do
|
58
|
+
xml.tag! remove_property.name.to_sym, remove_property.attributes
|
59
|
+
end
|
60
|
+
if @resource.respond_to?(("remove_" + remove_property.name).to_sym)
|
61
|
+
xml.D(:status, @resource.__send__(("remove_" + remove_property.name).to_sym))
|
62
|
+
else
|
63
|
+
xml.D :status, "HTTP/1.1 200 OK"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
for set_property in @set_properties
|
68
|
+
xml.D :propstat do
|
69
|
+
xml.D :prop do
|
70
|
+
xml.D set_property.name.to_sym, set_property.attributes
|
71
|
+
end
|
72
|
+
if @resource.respond_to?(("set_" + set_property.name).to_sym)
|
73
|
+
xml.D(:status, @resource.__send__(("set_" + set_property.name).to_sym))
|
74
|
+
else
|
75
|
+
xml.D :status, "HTTP/1.1 200 OK"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
xml.D :responsedescription
|
80
|
+
end
|
81
|
+
end
|
82
|
+
EOPROPPATCH_XML
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def define_attr_method(name, value=nil, &block)
|
87
|
+
sing = class << self; self; end
|
88
|
+
#sing.send :alias_method, "original_#{name}", name
|
89
|
+
if block_given?
|
90
|
+
sing.send :define_method, name, &block
|
91
|
+
else
|
92
|
+
# use eval instead of a block to work around a memory leak in dev
|
93
|
+
# mode in fcgi
|
94
|
+
sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end # SingletonMethods
|
98
|
+
|
99
|
+
module InstanceMethods
|
100
|
+
def index
|
101
|
+
webdav
|
102
|
+
end
|
103
|
+
|
104
|
+
def webdav
|
105
|
+
#we can get the method (as webdav has a lot of new methods) from the request
|
106
|
+
webdav_method = request.method.to_s
|
107
|
+
@path_info = convert_path_info(@request, params[:path_info].to_s)
|
108
|
+
|
109
|
+
begin
|
110
|
+
#going to call the method for this webdav method
|
111
|
+
if respond_to?("webdav_#{webdav_method}", true)
|
112
|
+
__send__("webdav_#{webdav_method}")
|
113
|
+
else
|
114
|
+
raise WebDavErrors::UnknownWebDavMethodError
|
115
|
+
end
|
116
|
+
|
117
|
+
rescue WebDavErrors::BaseError => webdav_error
|
118
|
+
logger.debug("ERROR : #{webdav_error.to_s}")
|
119
|
+
logger.debug("STATUS : #{webdav_error.http_status}")
|
120
|
+
render :nothing => true, :status => webdav_error.http_status and return
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def webdav_options()
|
125
|
+
@response.headers['DAV'] = "1,2"
|
126
|
+
@response.headers['MS-Author-Via'] = "DAV"
|
127
|
+
@response.headers["Allow"] = "COPY,DELETE,GET,HEAD,MKCOL,MOVE,OPTIONS,POST,PROPFIND,PROPPATCH,PUT"
|
128
|
+
render :nothing => true, :status => 200 and return
|
129
|
+
end
|
130
|
+
|
131
|
+
def webdav_lock()
|
132
|
+
#TODO implementation for now return a 200 OK
|
133
|
+
render :nothing => true, :status => 200 and return
|
134
|
+
end
|
135
|
+
|
136
|
+
def webdav_unlock()
|
137
|
+
#TODO implementation for now return a 200 OK
|
138
|
+
render :nothing => true, :status => 200 and return
|
139
|
+
end
|
140
|
+
|
141
|
+
def webdav_propfind()
|
142
|
+
logger.info("PATH INFO: " + @path_info)
|
143
|
+
@depth = get_depth
|
144
|
+
|
145
|
+
#default a big depth if infinity
|
146
|
+
@depth = 500 if @depth.nil?
|
147
|
+
|
148
|
+
|
149
|
+
unless request.raw_post.blank?
|
150
|
+
begin
|
151
|
+
req_doc = REXML::Document.new @request.raw_post
|
152
|
+
rescue REXML::ParseException
|
153
|
+
raise WebDavErrors::BadRequestBodyError
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
@resources = get_dav_resource_props(@path_info)
|
158
|
+
|
159
|
+
raise WebDavErrors::NotFoundError if @resources.blank?
|
160
|
+
|
161
|
+
response.headers["Content-Type"] = 'text/xml; charset="utf-8"'
|
162
|
+
#render the Multistatus XML
|
163
|
+
render :inline => self.class.get_propfind_xml, :type => :rxml, :status => 207 and return
|
164
|
+
end
|
165
|
+
|
166
|
+
def webdav_proppatch()
|
167
|
+
|
168
|
+
@resource = get_resource_for_path(@path_info)
|
169
|
+
|
170
|
+
ns = {""=>"DAV:"}
|
171
|
+
begin
|
172
|
+
req_doc = REXML::Document.new request.raw_post
|
173
|
+
rescue REXML::ParseException
|
174
|
+
raise WebDavErrors::BadRequestBodyError
|
175
|
+
end
|
176
|
+
|
177
|
+
@remove_properties = []
|
178
|
+
@set_properties = []
|
179
|
+
REXML::XPath.each(req_doc, "/propertyupdate/remove/prop/*", ns){|e|
|
180
|
+
@remove_properties << e
|
181
|
+
}
|
182
|
+
REXML::XPath.each(req_doc, "/propertyupdate/set/prop/*", ns){|e|
|
183
|
+
@set_properties << e
|
184
|
+
}
|
185
|
+
|
186
|
+
response.headers["Content-Type"] = 'text/xml; charset="utf-8"'
|
187
|
+
|
188
|
+
#render the Multistatus XML
|
189
|
+
render :inline => self.class.get_proppatch_xml, :type => :rxml, :status => 207 # and return
|
190
|
+
end
|
191
|
+
|
192
|
+
def webdav_mkcol
|
193
|
+
# need to check the content-type header to not allow invalid content types
|
194
|
+
mkcol_ct = request.env['CONTENT_TYPE']
|
195
|
+
|
196
|
+
logger.info(request.inspect)
|
197
|
+
|
198
|
+
if (!mkcol_ct.blank? && (mkcol_ct != "httpd/unix-directory")) || !request.raw_post.nil?
|
199
|
+
raise WebDavErrors::UnSupportedTypeError
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
raise WebDavErrors::UnknownWebDavMethodError if file_exist?(@path_info)
|
204
|
+
|
205
|
+
mkcol_for_path(@path_info)
|
206
|
+
|
207
|
+
render :nothing => true, :status => 201
|
208
|
+
end
|
209
|
+
|
210
|
+
def webdav_delete()
|
211
|
+
resource = get_resource_for_path(@path_info)
|
212
|
+
unless resource.nil?
|
213
|
+
logger.debug('RESOURCE IS' + resource.inspect)
|
214
|
+
resource.delete!
|
215
|
+
else
|
216
|
+
# Delete on a non-existant resource
|
217
|
+
raise WebDavErrors::NotFoundError
|
218
|
+
end
|
219
|
+
|
220
|
+
render :nothing => true, :status => 204
|
221
|
+
end
|
222
|
+
|
223
|
+
def webdav_put()
|
224
|
+
write_content_to_path(@path_info, request.raw_post)
|
225
|
+
|
226
|
+
render :nothing => true, :status => 201 and return
|
227
|
+
end
|
228
|
+
|
229
|
+
def webdav_copy()
|
230
|
+
@depth = get_depth
|
231
|
+
|
232
|
+
#Check the destination URI
|
233
|
+
begin
|
234
|
+
dest_uri = get_destination_uri
|
235
|
+
dest_path = convert_path_info(@request, get_dest_path)
|
236
|
+
# Bad Gateway it if the servers arnt the same
|
237
|
+
raise WebDavErrors::BadGatewayError unless request.host_with_port == "#{dest_uri.host}:#{dest_uri.port}"
|
238
|
+
rescue URI::InvalidURIError
|
239
|
+
raise WebDavErrors::BadGatewayError
|
240
|
+
end
|
241
|
+
|
242
|
+
resource = get_resource_for_path(@path_info)
|
243
|
+
|
244
|
+
# Not found if the source doesnt exist
|
245
|
+
raise WebDavErrors::NotFoundError if resource.nil?
|
246
|
+
|
247
|
+
file_exist = file_exist?(dest_path)
|
248
|
+
|
249
|
+
raise WebDavErrors::PreconditionFailsError if file_exist && !get_overwrite
|
250
|
+
|
251
|
+
copy_to_path(resource, dest_path, @depth)
|
252
|
+
|
253
|
+
file_exist ? render(:nothing => true, :status => 204) : render(:nothing => true, :status => 201)
|
254
|
+
end
|
255
|
+
|
256
|
+
def webdav_move()
|
257
|
+
@depth = get_depth
|
258
|
+
|
259
|
+
#Check the destination URI
|
260
|
+
begin
|
261
|
+
dest_uri = get_destination_uri
|
262
|
+
dest_path = convert_path_info(@request, get_dest_path)
|
263
|
+
#Bad Gateway it if the servers arnt the same
|
264
|
+
raise WebDavErrors::BadGatewayError unless request.host_with_port == "#{dest_uri.host}:#{dest_uri.port}"
|
265
|
+
rescue URI::InvalidURIError => e
|
266
|
+
logger.debug("INVALID URI: " + e.to_s)
|
267
|
+
logger.debug(request.env['HTTP_DESTINATION'])
|
268
|
+
raise WebDavErrors::BadGatewayError
|
269
|
+
end
|
270
|
+
|
271
|
+
resource = get_resource_for_path(@path_info)
|
272
|
+
|
273
|
+
#Not found if the source doesnt exist
|
274
|
+
raise WebDavErrors::NotFoundError if resource.nil?
|
275
|
+
|
276
|
+
file_exist = file_exist?(dest_path)
|
277
|
+
|
278
|
+
raise WebDavErrors::PreconditionFailsError if file_exist && !get_overwrite
|
279
|
+
|
280
|
+
move_to_path(resource, dest_path, @depth)
|
281
|
+
file_exist ? render(:nothing => true, :status => 204) : render(:nothing => true, :status => 201)
|
282
|
+
end
|
283
|
+
|
284
|
+
def webdav_get
|
285
|
+
resource = get_resource_for_path(@path_info)
|
286
|
+
send_data resource.data, :filename => resource.get_displayname
|
287
|
+
end
|
288
|
+
|
289
|
+
def webdav_head
|
290
|
+
resource = get_resource_for_path(@path_info)
|
291
|
+
raise WebDavErrors::NotFoundError if resource.blank?
|
292
|
+
@response.headers["Last-Modified"] = resource.getlastmodified
|
293
|
+
render(:nothing => true, :status => 200) and return
|
294
|
+
end
|
295
|
+
|
296
|
+
protected
|
297
|
+
##############################################################################################################
|
298
|
+
#To be overidden by implementing controller
|
299
|
+
def mkcol_for_path(path)
|
300
|
+
raise "implement me"
|
301
|
+
end
|
302
|
+
|
303
|
+
def write_content_to_path(path, content)
|
304
|
+
raise "implement me"
|
305
|
+
end
|
306
|
+
|
307
|
+
def file_exist?(dest_path)
|
308
|
+
raise "implement me"
|
309
|
+
end
|
310
|
+
|
311
|
+
def copy_to_path(resource, dest_path, depth)
|
312
|
+
raise "implement me"
|
313
|
+
end
|
314
|
+
|
315
|
+
def move_to_path(resource, dest_path, depth)
|
316
|
+
raise "implement me"
|
317
|
+
end
|
318
|
+
|
319
|
+
def get_resource_for_path(file_path)
|
320
|
+
raise "implement me" # WebDavResource.new
|
321
|
+
end
|
322
|
+
##############################################################################################################
|
323
|
+
|
324
|
+
def get_depth
|
325
|
+
depth_head = request.env['HTTP_DEPTH']
|
326
|
+
|
327
|
+
if (depth_head.nil?)
|
328
|
+
depth = 1
|
329
|
+
else
|
330
|
+
depth = (depth_head == "infinity") ? nil : depth_head.to_i
|
331
|
+
end
|
332
|
+
return depth
|
333
|
+
end
|
334
|
+
|
335
|
+
def get_destination_uri
|
336
|
+
URI.parse(request.env['HTTP_DESTINATION'])
|
337
|
+
end
|
338
|
+
|
339
|
+
def get_dest_path
|
340
|
+
if /^#{Regexp.escape(url_for(:only_path => true, :path_info => ""))}/ =~ get_destination_uri.path
|
341
|
+
return $'
|
342
|
+
else
|
343
|
+
raise WebDavErrors::ForbiddenError
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def get_overwrite
|
348
|
+
ov = request.env['HTTP_OVERWRITE']
|
349
|
+
return (!ov.blank? and ov == 'T')
|
350
|
+
end
|
351
|
+
|
352
|
+
def get_dav_resource_props(path)
|
353
|
+
ret_set = Array.new
|
354
|
+
@depth = self.class.get_max_propfind_depth if @depth > self.class.get_max_propfind_depth
|
355
|
+
@depth -= 1
|
356
|
+
resource = get_resource_for_path(path)
|
357
|
+
ret_set << resource
|
358
|
+
ret_set.concat resource.children unless @depth < 0
|
359
|
+
|
360
|
+
return ret_set
|
361
|
+
end
|
362
|
+
|
363
|
+
def href_for_path(path)
|
364
|
+
unless path.nil?
|
365
|
+
new_path = path.clone
|
366
|
+
new_path = new_path[1..-1] if (new_path.first == "/")
|
367
|
+
url_for(:only_path => true, :path_info => new_path)
|
368
|
+
else
|
369
|
+
url_for(:only_path => true)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def basic_auth_required(realm='Web Password', error_message="Could not authenticate you")
|
374
|
+
username, passwd = get_auth_data
|
375
|
+
# check if authorized
|
376
|
+
# try to get user
|
377
|
+
unless yield username, passwd
|
378
|
+
# the user does not exist or the password was wrong
|
379
|
+
headers["Status"] = "Unauthorized"
|
380
|
+
headers["WWW-Authenticate"] = "Basic realm=\"#{realm}\""
|
381
|
+
render :nothing => true, :status => 401
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def get_auth_data
|
386
|
+
user, pass = '', ''
|
387
|
+
# extract authorisation credentials
|
388
|
+
if request.env.has_key? 'X-HTTP_AUTHORIZATION'
|
389
|
+
# try to get it where mod_rewrite might have put it
|
390
|
+
authdata = request.env['X-HTTP_AUTHORIZATION'].to_s.split
|
391
|
+
elsif request.env.has_key? 'HTTP_AUTHORIZATION'
|
392
|
+
# this is the regular location
|
393
|
+
authdata = request.env['HTTP_AUTHORIZATION'].to_s.split
|
394
|
+
end
|
395
|
+
|
396
|
+
# at the moment we only support basic authentication
|
397
|
+
if authdata and authdata[0] == 'Basic'
|
398
|
+
user, pass = Base64.decode64(authdata[1]).split(':')[0..1]
|
399
|
+
end
|
400
|
+
return [user, pass]
|
401
|
+
end
|
402
|
+
|
403
|
+
def convert_path_info(req, path)
|
404
|
+
logger.info("PATH; " + path)
|
405
|
+
if req.env["HTTP_USER_AGENT"].match(/Microsoft|Windows/)
|
406
|
+
logger.info("CONVERTED: " + Iconv.iconv('UTF-8', 'latin1', CGI::unescape(path)).first)
|
407
|
+
Iconv.iconv('UTF-8', 'latin1', CGI::unescape(path)).first
|
408
|
+
elsif req.env["HTTP_USER_AGENT"].match(/cadaver/)
|
409
|
+
CGI::unescape(CGI::unescape(path))
|
410
|
+
elsif req.env["HTTP_USER_AGENT"].match(/Darwin|Macintosh/)
|
411
|
+
Unicode::normalize_KC(CGI::unescape(path))
|
412
|
+
else
|
413
|
+
CGI::unescape(path)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
ActionController::Base.send(:include, ServeWebdav)
|
data/lib/serve_webdav.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# = About lib/serve_webdav.rb
|
2
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
# Blah blah
|
5
|
+
|
6
|
+
module ServeWebdav
|
7
|
+
VERSION = '0.0.1'
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'serve_webdav/errors'
|
11
|
+
require 'serve_webdav/resource'
|
12
|
+
require 'serve_webdav/raw_data_patch'
|
13
|
+
require 'serve_webdav/serve_webdav'
|
14
|
+
|
File without changes
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: ServeWebdav
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2006-11-13 00:00:00 +01:00
|
8
|
+
summary: Webdav protocol abstraction for rails controllers
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: ryand-ruby@zenspider.com
|
12
|
+
homepage: http://www.zenspider.com/ZSS/Products/ServeWebdav/
|
13
|
+
rubyforge_project: adocca_plugins
|
14
|
+
description: Ryan Davis is too lazy to write a description
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Ryan Davis
|
31
|
+
files:
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
34
|
+
- README.txt
|
35
|
+
- Rakefile
|
36
|
+
- lib/serve_webdav.rb
|
37
|
+
- lib/serve_webdav/errors.rb
|
38
|
+
- lib/serve_webdav/raw_data_patch.rb
|
39
|
+
- lib/serve_webdav/resource.rb
|
40
|
+
- lib/serve_webdav/serve_webdav.rb
|
41
|
+
- test/test_serve_webdav.rb
|
42
|
+
test_files:
|
43
|
+
- test/test_serve_webdav.rb
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
extra_rdoc_files: []
|
47
|
+
|
48
|
+
executables: []
|
49
|
+
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
requirements: []
|
53
|
+
|
54
|
+
dependencies:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: hoe
|
57
|
+
version_requirement:
|
58
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.1.4
|
63
|
+
version:
|