dav4rack 0.2.11 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore DELETED
@@ -1,7 +0,0 @@
1
- .*
2
- !.gitignore
3
- *.gem
4
- *~
5
- doc/*
6
- pkg/*
7
- test/repo
@@ -1,257 +0,0 @@
1
- require 'webrick/httputils'
2
-
3
- module DAV4Rack
4
-
5
- class FileResource < Resource
6
-
7
- include WEBrick::HTTPUtils
8
-
9
- # If this is a collection, return the child resources.
10
- def children
11
- Dir[file_path + '/*'].map do |path|
12
- child File.basename(path)
13
- end
14
- end
15
-
16
- # Is this resource a collection?
17
- def collection?
18
- File.directory?(file_path)
19
- end
20
-
21
- # Does this recource exist?
22
- def exist?
23
- File.exist?(file_path)
24
- end
25
-
26
- # Return the creation time.
27
- def creation_date
28
- stat.ctime
29
- end
30
-
31
- # Return the time of last modification.
32
- def last_modified
33
- stat.mtime
34
- end
35
-
36
- # Set the time of last modification.
37
- def last_modified=(time)
38
- File.utime(Time.now, time, file_path)
39
- end
40
-
41
- # Return an Etag, an unique hash value for this resource.
42
- def etag
43
- sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
44
- end
45
-
46
- # Return the mime type of this resource.
47
- def content_type
48
- if stat.directory?
49
- "text/html"
50
- else
51
- mime_type(file_path, DefaultMimeTypes)
52
- end
53
- end
54
-
55
- # Return the size in bytes for this resource.
56
- def content_length
57
- stat.size
58
- end
59
-
60
- # HTTP GET request.
61
- #
62
- # Write the content of the resource to the response.body.
63
- def get(request, response)
64
- raise NotFound unless exist?
65
- if stat.directory?
66
- response.body = ""
67
- Rack::Directory.new(root).call(request.env)[2].each do |line|
68
- response.body << line
69
- end
70
- response['Content-Length'] = response.body.bytesize.to_s
71
- else
72
- file = Rack::File.new(root)
73
- response.body = file
74
- end
75
- OK
76
- end
77
-
78
- # HTTP PUT request.
79
- #
80
- # Save the content of the request.body.
81
- def put(request, response)
82
- write(request.body)
83
- Created
84
- end
85
-
86
- # HTTP POST request.
87
- #
88
- # Usually forbidden.
89
- def post(request, response)
90
- raise HTTPStatus::Forbidden
91
- end
92
-
93
- # HTTP DELETE request.
94
- #
95
- # Delete this resource.
96
- def delete
97
- if stat.directory?
98
- FileUtils.rm_rf(file_path)
99
- else
100
- File.unlink(file_path)
101
- end
102
- NoContent
103
- end
104
-
105
- # HTTP COPY request.
106
- #
107
- # Copy this resource to given destination resource.
108
- # Copy this resource to given destination resource.
109
- def copy(dest, overwrite)
110
- if(collection?)
111
- if(dest.exist?)
112
- if(dest.collection? && overwrite)
113
- FileUtils.cp_r(file_path, dest.send(:file_path))
114
- Created
115
- else
116
- if(overwrite)
117
- FileUtils.rm(dest.send(:file_path))
118
- FileUtils.cp_r(file_path, dest.send(:file_path))
119
- NoContent
120
- else
121
- PreconditionFailed
122
- end
123
- end
124
- else
125
- FileUtils.cp_r(file_path, dest.send(:file_path))
126
- Created
127
- end
128
- else
129
- if(dest.exist? && !overwrite)
130
- PreconditionFailed
131
- else
132
- if(File.directory?(File.dirname(dest.send(:file_path))))
133
- new = !dest.exist?
134
- if(dest.collection? && dest.exist?)
135
- FileUtils.rm_rf(dest.send(:file_path))
136
- end
137
- FileUtils.cp(file_path, dest.send(:file_path).sub(/\/$/, ''))
138
- new ? Created : NoContent
139
- else
140
- Conflict
141
- end
142
- end
143
- end
144
- end
145
-
146
- # HTTP MOVE request.
147
- #
148
- # Move this resource to given destination resource.
149
- def move(*args)
150
- result = copy(*args)
151
- delete if [Created, NoContent].include?(result)
152
- result
153
- end
154
-
155
- # HTTP MKCOL request.
156
- #
157
- # Create this resource as collection.
158
- def make_collection
159
- if(request.body.read.to_s == '')
160
- if(File.directory?(file_path))
161
- MethodNotAllowed
162
- else
163
- if(File.directory?(File.dirname(file_path)))
164
- Dir.mkdir(file_path)
165
- Created
166
- else
167
- Conflict
168
- end
169
- end
170
- else
171
- UnsupportedMediaType
172
- end
173
- end
174
-
175
- # Write to this resource from given IO.
176
- def write(io)
177
- tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
178
-
179
- open(tempfile, "wb") do |file|
180
- while part = io.read(8192)
181
- file << part
182
- end
183
- end
184
-
185
- File.rename(tempfile, file_path)
186
- ensure
187
- File.unlink(tempfile) rescue nil
188
- end
189
-
190
- # name:: String - Property name
191
- # Returns the value of the given property
192
- def get_property(name)
193
- super || custom_props(name)
194
- end
195
-
196
- # name:: String - Property name
197
- # value:: New value
198
- # Set the property to the given value
199
- def set_property(name, value)
200
- super || set_custom_props(name,value)
201
- end
202
-
203
- protected
204
-
205
- def set_custom_props(key,val)
206
- prop_hash[key.to_sym] = val
207
- File.open(prop_path, 'w') do |file|
208
- file.write(YAML.dump(prop_hash))
209
- end
210
- end
211
-
212
- def custom_props(key)
213
- prop_hash[key.to_sym]
214
- end
215
-
216
- def prop_path
217
- path = File.join(root, '.props', File.dirname(file_path), File.basename(file_path))
218
- unless(File.directory?(File.dirname(path)))
219
- FileUtils.mkdir_p(File.dirname(path))
220
- end
221
- path
222
- end
223
-
224
- def prop_hash
225
- unless(@_prop_hash)
226
- if(File.exists?(prop_path))
227
- @_prop_hash = YAML.load(File.read(prop_path))
228
- else
229
- @_prop_hash = {}
230
- end
231
- end
232
- @_prop_hash
233
- end
234
-
235
- def authenticate(user, pass)
236
- if(options[:username])
237
- options[:username] == user && options[:password] == pass
238
- else
239
- true
240
- end
241
- end
242
-
243
- def root
244
- @options[:root]
245
- end
246
-
247
- def file_path
248
- File.join(root, path)
249
- end
250
-
251
- def stat
252
- @stat ||= File.stat(file_path)
253
- end
254
-
255
- end
256
-
257
- end
@@ -1,301 +0,0 @@
1
- $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
2
-
3
- require 'rubygems'
4
- require 'dav4rack'
5
- require 'fileutils'
6
- require 'nokogiri'
7
- require 'rspec'
8
-
9
- describe DAV4Rack::Handler do
10
- DOC_ROOT = File.expand_path(File.dirname(__FILE__) + '/htdocs')
11
- METHODS = %w(GET PUT POST DELETE PROPFIND PROPPATCH MKCOL COPY MOVE OPTIONS HEAD LOCK UNLOCK)
12
-
13
- before do
14
- FileUtils.mkdir(DOC_ROOT) unless File.exists?(DOC_ROOT)
15
- @controller = DAV4Rack::Handler.new(:root => DOC_ROOT)
16
- end
17
-
18
- after do
19
- FileUtils.rm_rf(DOC_ROOT) if File.exists?(DOC_ROOT)
20
- end
21
-
22
- attr_reader :response
23
-
24
- def request(method, uri, options={})
25
- options = {
26
- 'HTTP_HOST' => 'localhost',
27
- 'REMOTE_USER' => 'user'
28
- }.merge(options)
29
- request = Rack::MockRequest.new(@controller)
30
- @response = request.request(method, uri, options)
31
- end
32
-
33
- METHODS.each do |method|
34
- define_method(method.downcase) do |*args|
35
- request(method, *args)
36
- end
37
- end
38
-
39
- def render(root_type)
40
- raise ArgumentError.new 'Expecting block' unless block_given?
41
- doc = Nokogiri::XML::Builder.new do |xml_base|
42
- xml_base.send(root_type.to_s, 'xmlns:D' => 'D:') do
43
- xml_base.parent.namespace = xml_base.parent.namespace_definitions.first
44
- xml = xml_base['D']
45
- yield xml
46
- end
47
- end
48
- doc.to_xml
49
- end
50
-
51
- def url_escape(string)
52
- URI.escape(string)
53
- end
54
-
55
- def response_xml
56
- Nokogiri.XML(@response.body)
57
- end
58
-
59
- def multistatus_response(pattern)
60
- @response.should be_multi_status
61
- response_xml.xpath('//D:multistatus/D:response', response_xml.root.namespaces).should_not be_empty
62
- response_xml.xpath("//D:multistatus/D:response#{pattern}", response_xml.root.namespaces)
63
- end
64
-
65
- def multi_status_created
66
- response_xml.xpath('//D:multistatus/D:response/D:status').should_not be_empty
67
- response_xml.xpath('//D:multistatus/D:response/D:status').text.should =~ /Created/
68
- end
69
-
70
- def multi_status_ok
71
- response_xml.xpath('//D:multistatus/D:response/D:status').should_not be_empty
72
- response_xml.xpath('//D:multistatus/D:response/D:status').text.should =~ /OK/
73
- end
74
-
75
- def multi_status_no_content
76
- response_xml.xpath('//D:multistatus/D:response/D:status').should_not be_empty
77
- response_xml.xpath('//D:multistatus/D:response/D:status').text.should =~ /No Content/
78
- end
79
-
80
- def propfind_xml(*props)
81
- render(:propfind) do |xml|
82
- xml.prop do
83
- props.each do |prop|
84
- xml.send(prop.to_sym)
85
- end
86
- end
87
- end
88
- end
89
-
90
- it 'should return all options' do
91
- options('/').should be_ok
92
-
93
- METHODS.each do |method|
94
- response.headers['allow'].should include(method)
95
- end
96
- end
97
-
98
- it 'should return headers' do
99
- put('/test.html', :input => '<html/>').should be_created
100
- head('/test.html').should be_ok
101
-
102
- response.headers['etag'].should_not be_nil
103
- response.headers['content-type'].should match(/html/)
104
- response.headers['last-modified'].should_not be_nil
105
- end
106
-
107
- it 'should not find a nonexistent resource' do
108
- get('/not_found').should be_not_found
109
- end
110
-
111
- it 'should not allow directory traversal' do
112
- get('/../htdocs').should be_forbidden
113
- end
114
-
115
- it 'should create a resource and allow its retrieval' do
116
- put('/test', :input => 'body').should be_created
117
- get('/test').should be_ok
118
- response.body.should == 'body'
119
- end
120
-
121
- it 'should return an absolute url after a put request' do
122
- put('/test', :input => 'body').should be_created
123
- response['location'].should =~ /http:\/\/localhost(:\d+)?\/test/
124
- end
125
-
126
- it 'should create and find a url with escaped characters' do
127
- put(url_escape('/a b'), :input => 'body').should be_created
128
- get(url_escape('/a b')).should be_ok
129
- response.body.should == 'body'
130
- end
131
-
132
- it 'should delete a single resource' do
133
- put('/test', :input => 'body').should be_created
134
- delete('/test').should be_no_content
135
- end
136
-
137
- it 'should delete recursively' do
138
- mkcol('/folder').should be_created
139
- put('/folder/a', :input => 'body').should be_created
140
- put('/folder/b', :input => 'body').should be_created
141
-
142
- delete('/folder').should be_no_content
143
- get('/folder').should be_not_found
144
- get('/folder/a').should be_not_found
145
- get('/folder/b').should be_not_found
146
- end
147
-
148
- it 'should not allow copy to another domain' do
149
- put('/test', :input => 'body').should be_created
150
- copy('http://localhost/', 'HTTP_DESTINATION' => 'http://another/').should be_bad_gateway
151
- end
152
-
153
- it 'should not allow copy to the same resource' do
154
- put('/test', :input => 'body').should be_created
155
- copy('/test', 'HTTP_DESTINATION' => '/test').should be_forbidden
156
- end
157
-
158
- it 'should copy a single resource' do
159
- put('/test', :input => 'body').should be_created
160
- copy('/test', 'HTTP_DESTINATION' => '/copy').should be_created
161
- get('/copy').body.should == 'body'
162
- end
163
-
164
- it 'should copy a resource with escaped characters' do
165
- put(url_escape('/a b'), :input => 'body').should be_created
166
- copy(url_escape('/a b'), 'HTTP_DESTINATION' => url_escape('/a c')).should be_created
167
- get(url_escape('/a c')).should be_ok
168
- response.body.should == 'body'
169
- end
170
-
171
- it 'should deny a copy without overwrite' do
172
- put('/test', :input => 'body').should be_created
173
- put('/copy', :input => 'copy').should be_created
174
- copy('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'F').should be_precondition_failed
175
- get('/copy').body.should == 'copy'
176
- end
177
-
178
- it 'should allow a copy with overwrite' do
179
- put('/test', :input => 'body').should be_created
180
- put('/copy', :input => 'copy').should be_created
181
- copy('/test', 'HTTP_DESTINATION' => '/copy', 'HTTP_OVERWRITE' => 'T').should be_no_content
182
- get('/copy').body.should == 'body'
183
- end
184
-
185
- it 'should copy a collection' do
186
- mkcol('/folder').should be_created
187
- copy('/folder', 'HTTP_DESTINATION' => '/copy')
188
- multi_status_created.should eq true
189
- propfind('/copy', :input => propfind_xml(:resourcetype))
190
- multistatus_response('/D:propstat/D:prop/D:resourcetype/D:collection').should_not be_empty
191
- end
192
-
193
- it 'should copy a collection resursively' do
194
- mkcol('/folder').should be_created
195
- put('/folder/a', :input => 'A').should be_created
196
- put('/folder/b', :input => 'B').should be_created
197
-
198
- copy('/folder', 'HTTP_DESTINATION' => '/copy')
199
- multi_status_created.should eq true
200
- propfind('/copy', :input => propfind_xml(:resourcetype))
201
- multistatus_response('/D:propstat/D:prop/D:resourcetype/D:collection').should_not be_empty
202
- get('/copy/a').body.should == 'A'
203
- get('/copy/b').body.should == 'B'
204
- end
205
-
206
- it 'should move a collection recursively' do
207
- mkcol('/folder').should be_created
208
- put('/folder/a', :input => 'A').should be_created
209
- put('/folder/b', :input => 'B').should be_created
210
-
211
- move('/folder', 'HTTP_DESTINATION' => '/move')
212
- multi_status_created.should eq true
213
- propfind('/move', :input => propfind_xml(:resourcetype))
214
- multistatus_response('/D:propstat/D:prop/D:resourcetype/D:collection').should_not be_empty
215
-
216
- get('/move/a').body.should == 'A'
217
- get('/move/b').body.should == 'B'
218
- get('/folder/a').should be_not_found
219
- get('/folder/b').should be_not_found
220
- end
221
-
222
- it 'should create a collection' do
223
- mkcol('/folder').should be_created
224
- propfind('/folder', :input => propfind_xml(:resourcetype))
225
- multistatus_response('/D:propstat/D:prop/D:resourcetype/D:collection').should_not be_empty
226
- end
227
-
228
- it 'should return full urls after creating a collection' do
229
- mkcol('/folder').should be_created
230
- propfind('/folder', :input => propfind_xml(:resourcetype))
231
- multistatus_response('/D:propstat/D:prop/D:resourcetype/D:collection').should_not be_empty
232
- multistatus_response('/D:href').first.text.should =~ /http:\/\/localhost(:\d+)?\/folder/
233
- end
234
-
235
- it 'should not find properties for nonexistent resources' do
236
- propfind('/non').should be_not_found
237
- end
238
-
239
- it 'should find all properties' do
240
- xml = render(:propfind) do |xml|
241
- xml.allprop
242
- end
243
-
244
- propfind('http://localhost/', :input => xml)
245
-
246
- multistatus_response('/D:href').first.text.strip.should =~ /http:\/\/localhost(:\d+)?\//
247
-
248
- props = %w(creationdate displayname getlastmodified getetag resourcetype getcontenttype getcontentlength)
249
- props.each do |prop|
250
- multistatus_response("/D:propstat/D:prop/D:#{prop}").should_not be_empty
251
- end
252
- end
253
-
254
- it 'should find named properties' do
255
- put('/test.html', :input => '<html/>').should be_created
256
- propfind('/test.html', :input => propfind_xml(:getcontenttype, :getcontentlength))
257
-
258
- multistatus_response('/D:propstat/D:prop/D:getcontenttype').first.text.should == 'text/html'
259
- multistatus_response('/D:propstat/D:prop/D:getcontentlength').first.text.should == '7'
260
- end
261
-
262
- it 'should lock a resource' do
263
- put('/test', :input => 'body').should be_created
264
-
265
- xml = render(:lockinfo) do |xml|
266
- xml.lockscope { xml.exclusive }
267
- xml.locktype { xml.write }
268
- xml.owner { xml.href "http://test.de/" }
269
- end
270
-
271
- lock('/test', :input => xml)
272
-
273
- response.should be_ok
274
-
275
- match = lambda do |pattern|
276
- response_xml.xpath "/D:prop/D:lockdiscovery/D:activelock#{pattern}"
277
- end
278
-
279
- match[''].should_not be_empty
280
-
281
- match['/D:locktype'].should_not be_empty
282
- match['/D:lockscope'].should_not be_empty
283
- match['/D:depth'].should_not be_empty
284
- match['/D:timeout'].should_not be_empty
285
- match['/D:locktoken'].should_not be_empty
286
- match['/D:owner'].should_not be_empty
287
- end
288
-
289
- context "when mapping a path" do
290
-
291
- before do
292
- @controller = DAV4Rack::Handler.new(:root => DOC_ROOT, :root_uri_path => '/webdav/')
293
- end
294
-
295
- it "should return correct urls" do
296
- # FIXME: a put to '/test' works, too -- should it?
297
- put('/webdav/test', :input => 'body').should be_created
298
- response.headers['location'].should =~ /http:\/\/localhost(:\d+)?\/webdav\/test/
299
- end
300
- end
301
- end