ServeWebdav 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|