rack_dav 0.1.2

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.
@@ -0,0 +1,270 @@
1
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
2
+
3
+ require 'rubygems'
4
+ require 'rack_dav'
5
+ require 'fileutils'
6
+
7
+ describe RackDAV::Handler do
8
+ DOC_ROOT = File.expand_path(File.dirname(__FILE__) + '/htdocs')
9
+ METHODS = %w(GET PUT POST DELETE PROPFIND PROPPATCH MKCOL COPY MOVE OPTIONS HEAD LOCK UNLOCK)
10
+
11
+ before do
12
+ FileUtils.mkdir(DOC_ROOT) unless File.exists?(DOC_ROOT)
13
+ @controller = RackDAV::Handler.new(:root => DOC_ROOT)
14
+ end
15
+
16
+ after do
17
+ FileUtils.rm_rf(DOC_ROOT) if File.exists?(DOC_ROOT)
18
+ end
19
+
20
+ attr_reader :response
21
+
22
+ def request(method, uri, options={})
23
+ options = {
24
+ 'HTTP_HOST' => 'localhost',
25
+ 'REMOTE_USER' => 'manni'
26
+ }.merge(options)
27
+ request = Rack::MockRequest.new(@controller)
28
+ @response = request.request(method, uri, options)
29
+ end
30
+
31
+ METHODS.each do |method|
32
+ define_method(method.downcase) do |*args|
33
+ request(method, *args)
34
+ end
35
+ end
36
+
37
+ def render
38
+ xml = Builder::XmlMarkup.new
39
+ xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
40
+ xml.namespace('d') do
41
+ yield xml
42
+ end
43
+ xml.target!
44
+ end
45
+
46
+ def url_escape(string)
47
+ string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
48
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
49
+ end.tr(' ', '+')
50
+ end
51
+
52
+ def response_xml
53
+ REXML::Document.new(@response.body)
54
+ end
55
+
56
+ def multistatus_response(pattern)
57
+ @response.should be_multi_status
58
+ REXML::XPath::match(response_xml, "/multistatus/response", '' => 'DAV:').should_not be_empty
59
+ REXML::XPath::match(response_xml, "/multistatus/response" + pattern, '' => 'DAV:')
60
+ end
61
+
62
+ def propfind_xml(*props)
63
+ render do |xml|
64
+ xml.propfind('xmlns:d' => "DAV:") do
65
+ xml.prop do
66
+ props.each do |prop|
67
+ xml.tag! prop
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ it 'should return all options' do
75
+ options('/').should be_ok
76
+
77
+ METHODS.each do |method|
78
+ response.headers['allow'].should include(method)
79
+ end
80
+ end
81
+
82
+ it 'should return headers' do
83
+ put('/test.html', :input => '<html/>').should be_ok
84
+ head('/test.html').should be_ok
85
+
86
+ response.headers['etag'].should_not be_nil
87
+ response.headers['content-type'].should match(/html/)
88
+ response.headers['last-modified'].should_not be_nil
89
+ end
90
+
91
+ it 'should not find a nonexistent resource' do
92
+ get('/not_found').should be_not_found
93
+ end
94
+
95
+ it 'should not allow directory traversal' do
96
+ get('/../htdocs').should be_forbidden
97
+ end
98
+
99
+ it 'should create a resource and allow its retrieval' do
100
+ put('/test', :input => 'body').should be_ok
101
+ get('/test').should be_ok
102
+ response.body.should == 'body'
103
+ end
104
+ it 'should create and find a url with escaped characters' do
105
+ put(url_escape('/a b'), :input => 'body').should be_ok
106
+ get(url_escape('/a b')).should be_ok
107
+ response.body.should == 'body'
108
+ end
109
+
110
+ it 'should delete a single resource' do
111
+ put('/test', :input => 'body').should be_ok
112
+ delete('/test').should be_no_content
113
+ end
114
+
115
+ it 'should delete recursively' do
116
+ mkcol('/folder').should be_created
117
+ put('/folder/a', :input => 'body').should be_ok
118
+ put('/folder/b', :input => 'body').should be_ok
119
+
120
+ delete('/folder').should be_no_content
121
+ get('/folder').should be_not_found
122
+ get('/folder/a').should be_not_found
123
+ get('/folder/b').should be_not_found
124
+ end
125
+
126
+ it 'should not allow copy to another domain' do
127
+ put('/test', :input => 'body').should be_ok
128
+ copy('http://localhost/', 'HTTP_DESTINATION' => 'http://another/').should be_bad_gateway
129
+ end
130
+
131
+ it 'should not allow copy to the same resource' do
132
+ put('/test', :input => 'body').should be_ok
133
+ copy('/test', 'HTTP_DESTINATION' => '/test').should be_forbidden
134
+ end
135
+
136
+ it 'should not allow an invalid destination uri' do
137
+ put('/test', :input => 'body').should be_ok
138
+ copy('/test', 'HTTP_DESTINATION' => '%').should be_bad_request
139
+ end
140
+
141
+ it 'should copy a single resource' do
142
+ put('/test', :input => 'body').should be_ok
143
+ copy('/test', 'HTTP_DESTINATION' => '/copy').should be_created
144
+ get('/copy').body.should == 'body'
145
+ end
146
+
147
+ it 'should copy a resource with escaped characters' do
148
+ put(url_escape('/a b'), :input => 'body').should be_ok
149
+ copy(url_escape('/a b'), 'HTTP_DESTINATION' => url_escape('/a c')).should be_created
150
+ get(url_escape('/a c')).should be_ok
151
+ response.body.should == 'body'
152
+ end
153
+
154
+ it 'should deny a copy without overwrite' do
155
+ put('/test', :input => 'body').should be_ok
156
+ put('/copy', :input => 'copy').should be_ok
157
+ copy('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'F')
158
+
159
+ multistatus_response('/href').first.text.should == 'http://localhost/test'
160
+ multistatus_response('/status').first.text.should match(/412 Precondition Failed/)
161
+
162
+ get('/copy').body.should == 'copy'
163
+ end
164
+
165
+ it 'should allow a copy with overwrite' do
166
+ put('/test', :input => 'body').should be_ok
167
+ put('/copy', :input => 'copy').should be_ok
168
+ copy('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'T').should be_no_content
169
+ get('/copy').body.should == 'body'
170
+ end
171
+
172
+ it 'should copy a collection' do
173
+ mkcol('/folder').should be_created
174
+ copy('/folder', 'HTTP_DESTINATION' => '/copy').should be_created
175
+ propfind('/copy', :input => propfind_xml(:resourcetype))
176
+ multistatus_response('/propstat/prop/resourcetype/collection').should_not be_empty
177
+ end
178
+
179
+ it 'should copy a collection resursively' do
180
+ mkcol('/folder').should be_created
181
+ put('/folder/a', :input => 'A').should be_ok
182
+ put('/folder/b', :input => 'B').should be_ok
183
+
184
+ copy('/folder', 'HTTP_DESTINATION' => '/copy').should be_created
185
+ propfind('/copy', :input => propfind_xml(:resourcetype))
186
+ multistatus_response('/propstat/prop/resourcetype/collection').should_not be_empty
187
+
188
+ get('/copy/a').body.should == 'A'
189
+ get('/copy/b').body.should == 'B'
190
+ end
191
+
192
+ it 'should move a collection recursively' do
193
+ mkcol('/folder').should be_created
194
+ put('/folder/a', :input => 'A').should be_ok
195
+ put('/folder/b', :input => 'B').should be_ok
196
+
197
+ move('/folder', 'HTTP_DESTINATION' => '/move').should be_created
198
+ propfind('/move', :input => propfind_xml(:resourcetype))
199
+ multistatus_response('/propstat/prop/resourcetype/collection').should_not be_empty
200
+
201
+ get('/move/a').body.should == 'A'
202
+ get('/move/b').body.should == 'B'
203
+ get('/folder/a').should be_not_found
204
+ get('/folder/b').should be_not_found
205
+ end
206
+
207
+ it 'should create a collection' do
208
+ mkcol('/folder').should be_created
209
+ propfind('/folder', :input => propfind_xml(:resourcetype))
210
+ multistatus_response('/propstat/prop/resourcetype/collection').should_not be_empty
211
+ end
212
+
213
+ it 'should not find properties for nonexistent resources' do
214
+ propfind('/non').should be_not_found
215
+ end
216
+
217
+ it 'should find all properties' do
218
+ xml = render do |xml|
219
+ xml.propfind('xmlns:d' => "DAV:") do
220
+ xml.allprop
221
+ end
222
+ end
223
+
224
+ propfind('http://localhost/', :input => xml)
225
+
226
+ multistatus_response('/href').first.text.strip.should == 'http://localhost/'
227
+
228
+ props = %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength)
229
+ props.each do |prop|
230
+ multistatus_response('/propstat/prop/' + prop).should_not be_empty
231
+ end
232
+ end
233
+
234
+ it 'should find named properties' do
235
+ put('/test.html', :input => '<html/>').should be_ok
236
+ propfind('/test.html', :input => propfind_xml(:getcontenttype, :getcontentlength))
237
+
238
+ multistatus_response('/propstat/prop/getcontenttype').first.text.should == 'text/html'
239
+ multistatus_response('/propstat/prop/getcontentlength').first.text.should == '7'
240
+ end
241
+
242
+ it 'should lock a resource' do
243
+ put('/test', :input => 'body').should be_ok
244
+
245
+ xml = render do |xml|
246
+ xml.lockinfo('xmlns:d' => "DAV:") do
247
+ xml.lockscope { xml.exclusive }
248
+ xml.locktype { xml.write }
249
+ xml.owner { xml.href "http://test.de/" }
250
+ end
251
+ end
252
+
253
+ lock('/test', :input => xml)
254
+
255
+ response.should be_ok
256
+
257
+ match = lambda do |pattern|
258
+ REXML::XPath::match(response_xml, "/prop/lockdiscovery/activelock" + pattern, '' => 'DAV:')
259
+ end
260
+
261
+ match[''].should_not be_empty
262
+
263
+ match['/locktype'].should_not be_empty
264
+ match['/lockscope'].should_not be_empty
265
+ match['/depth'].should_not be_empty
266
+ match['/owner'].should_not be_empty
267
+ match['/timeout'].should_not be_empty
268
+ match['/locktoken'].should_not be_empty
269
+ end
270
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack_dav
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 2
10
+ version: 0.1.2
11
+ platform: ruby
12
+ authors:
13
+ - Matthias Georgi
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-17 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: WebDAV handler for Rack
23
+ email: matti.georgi@gmail.com
24
+ executables:
25
+ - rack_dav
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README.md
30
+ files:
31
+ - .gitignore
32
+ - LICENSE
33
+ - rack_dav.gemspec
34
+ - lib/rack_dav.rb
35
+ - lib/rack_dav/file_resource.rb
36
+ - lib/rack_dav/handler.rb
37
+ - lib/rack_dav/controller.rb
38
+ - lib/rack_dav/builder_namespace.rb
39
+ - lib/rack_dav/http_status.rb
40
+ - lib/rack_dav/resource.rb
41
+ - bin/rack_dav
42
+ - spec/handler_spec.rb
43
+ - README.md
44
+ has_rdoc: true
45
+ homepage: http://www.matthias-georgi.de/rack_dav
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options: []
50
+
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ hash: 3
68
+ segments:
69
+ - 0
70
+ version: "0"
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.3.7
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: WebDAV handler for Rack
78
+ test_files: []
79
+