rack-webdav 0.4.3 → 0.4.4
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.
- checksums.yaml +4 -4
- data/bin/byebug +17 -0
- data/bin/coderay +17 -0
- data/bin/htmldiff +17 -0
- data/bin/ldiff +17 -0
- data/bin/nokogiri +17 -0
- data/bin/pry +17 -0
- data/bin/puma +17 -0
- data/bin/pumactl +17 -0
- data/bin/rack-webdav +23 -109
- data/bin/rackup +17 -0
- data/bin/rake +17 -0
- data/bin/rspec +17 -0
- data/bin/rubocop +17 -0
- data/bin/ruby-parse +17 -0
- data/bin/ruby-rewrite +17 -0
- data/bin/unicorn +17 -0
- data/bin/unicorn_rails +17 -0
- data/lib/rack-webdav/controller.rb +447 -480
- data/lib/rack-webdav/file_resource.rb +247 -0
- data/lib/rack-webdav/handler.rb +27 -50
- data/lib/rack-webdav/http_status.rb +10 -10
- data/lib/rack-webdav/resource.rb +73 -368
- data/lib/rack-webdav/string.rb +28 -0
- data/lib/rack-webdav/version.rb +10 -11
- data/lib/rack-webdav.rb +5 -1
- data/rack-webdav.gemspec +3 -0
- metadata +62 -9
- data/lib/rack-webdav/file.rb +0 -37
- data/lib/rack-webdav/interceptor.rb +0 -22
- data/lib/rack-webdav/interceptor_resource.rb +0 -119
- data/lib/rack-webdav/lock.rb +0 -40
- data/lib/rack-webdav/logger.rb +0 -30
- data/lib/rack-webdav/remote_file.rb +0 -148
- data/lib/rack-webdav/resources/file_resource.rb +0 -384
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
# encoding: UTF-8
|
|
2
|
-
|
|
3
|
-
require 'pstore'
|
|
4
|
-
require 'webrick/httputils'
|
|
5
|
-
require 'rack-webdav/file_resource_lock'
|
|
6
|
-
|
|
7
|
-
module RackWebDAV
|
|
8
|
-
|
|
9
|
-
class FileResource < Resource
|
|
10
|
-
|
|
11
|
-
include WEBrick::HTTPUtils
|
|
12
|
-
include RackWebDAV::Utils
|
|
13
|
-
|
|
14
|
-
# If this is a collection, return the child resources.
|
|
15
|
-
def children
|
|
16
|
-
Dir[file_path + '/*'].map do |path|
|
|
17
|
-
child ::File.basename(path)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Is this resource a collection?
|
|
22
|
-
def collection?
|
|
23
|
-
::File.directory?(file_path)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Does this recource exist?
|
|
27
|
-
def exist?
|
|
28
|
-
::File.exist?(file_path)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Return the creation time.
|
|
32
|
-
def creation_date
|
|
33
|
-
stat.ctime
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Return the time of last modification.
|
|
37
|
-
def last_modified
|
|
38
|
-
stat.mtime
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Set the time of last modification.
|
|
42
|
-
def last_modified=(time)
|
|
43
|
-
::File.utime(Time.now, time, file_path)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Return an Etag, an unique hash value for this resource.
|
|
47
|
-
def etag
|
|
48
|
-
sprintf('%x-%x-%x', stat.ino, stat.size, stat.mtime.to_i)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Return the mime type of this resource.
|
|
52
|
-
def content_type
|
|
53
|
-
if stat.directory?
|
|
54
|
-
"text/html"
|
|
55
|
-
else
|
|
56
|
-
mime_type(file_path, DefaultMimeTypes)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Return the size in bytes for this resource.
|
|
61
|
-
def content_length
|
|
62
|
-
stat.size
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# HTTP GET request.
|
|
66
|
-
#
|
|
67
|
-
# Write the content of the resource to the response.body.
|
|
68
|
-
def get(request, response)
|
|
69
|
-
raise NotFound unless exist?
|
|
70
|
-
if stat.directory?
|
|
71
|
-
response.body = ""
|
|
72
|
-
Rack::Directory.new(root).call(request.env)[2].each do |line|
|
|
73
|
-
response.body << line
|
|
74
|
-
end
|
|
75
|
-
response['Content-Length'] = response.body.bytesize.to_s
|
|
76
|
-
else
|
|
77
|
-
file = Rack::File.new(root)
|
|
78
|
-
status, headers, body = file.call(request.env)
|
|
79
|
-
|
|
80
|
-
if body.respond_to?(:to_path)
|
|
81
|
-
response.body = ::File.open(body.to_path, 'rb').read
|
|
82
|
-
response['Content-Length'] = response.body.bytesize.to_s
|
|
83
|
-
else
|
|
84
|
-
response.body = ""
|
|
85
|
-
total_bytes = 0
|
|
86
|
-
body.each { |part|
|
|
87
|
-
response.body += part
|
|
88
|
-
total_bytes += part.bytesize
|
|
89
|
-
}
|
|
90
|
-
response['Content-Length'] = total_bytes
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
OK
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# HTTP PUT request.
|
|
97
|
-
#
|
|
98
|
-
# Save the content of the request.body.
|
|
99
|
-
def put(request, response)
|
|
100
|
-
write(request.body)
|
|
101
|
-
Created
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# HTTP POST request.
|
|
105
|
-
#
|
|
106
|
-
# Usually forbidden.
|
|
107
|
-
def post(request, response)
|
|
108
|
-
raise HTTPStatus::Forbidden
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# HTTP DELETE request.
|
|
112
|
-
#
|
|
113
|
-
# Delete this resource.
|
|
114
|
-
def delete
|
|
115
|
-
if stat.directory?
|
|
116
|
-
FileUtils.rm_rf(file_path)
|
|
117
|
-
else
|
|
118
|
-
::File.unlink(file_path)
|
|
119
|
-
end
|
|
120
|
-
::File.unlink(prop_path) if ::File.exists?(prop_path)
|
|
121
|
-
@_prop_hash = nil
|
|
122
|
-
NoContent
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# HTTP COPY request.
|
|
126
|
-
#
|
|
127
|
-
# Copy this resource to given destination resource.
|
|
128
|
-
# Copy this resource to given destination resource.
|
|
129
|
-
def copy(dest, overwrite)
|
|
130
|
-
if(collection?)
|
|
131
|
-
if(dest.exist?)
|
|
132
|
-
if(dest.collection? && overwrite)
|
|
133
|
-
FileUtils.cp_r(file_path, dest.send(:file_path))
|
|
134
|
-
Created
|
|
135
|
-
else
|
|
136
|
-
if(overwrite)
|
|
137
|
-
FileUtils.rm(dest.send(:file_path))
|
|
138
|
-
FileUtils.cp_r(file_path, dest.send(:file_path))
|
|
139
|
-
NoContent
|
|
140
|
-
else
|
|
141
|
-
PreconditionFailed
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
else
|
|
145
|
-
FileUtils.cp_r(file_path, dest.send(:file_path))
|
|
146
|
-
Created
|
|
147
|
-
end
|
|
148
|
-
else
|
|
149
|
-
if(dest.exist? && !overwrite)
|
|
150
|
-
PreconditionFailed
|
|
151
|
-
else
|
|
152
|
-
if(::File.directory?(::File.dirname(dest.send(:file_path))))
|
|
153
|
-
new = !dest.exist?
|
|
154
|
-
if(dest.collection? && dest.exist?)
|
|
155
|
-
FileUtils.rm_rf(dest.send(:file_path))
|
|
156
|
-
end
|
|
157
|
-
FileUtils.cp(file_path, dest.send(:file_path).sub(/\/$/, ''))
|
|
158
|
-
FileUtils.cp(prop_path, dest.prop_path) if ::File.exist? prop_path
|
|
159
|
-
new ? Created : NoContent
|
|
160
|
-
else
|
|
161
|
-
Conflict
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# HTTP MOVE request.
|
|
168
|
-
#
|
|
169
|
-
# Move this resource to given destination resource.
|
|
170
|
-
def move(*args)
|
|
171
|
-
result = copy(*args)
|
|
172
|
-
delete if [Created, NoContent].include?(result)
|
|
173
|
-
result
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
# HTTP MKCOL request.
|
|
177
|
-
#
|
|
178
|
-
# Create this resource as collection.
|
|
179
|
-
def make_collection
|
|
180
|
-
if(request.body.read.to_s == '')
|
|
181
|
-
if(::File.directory?(file_path))
|
|
182
|
-
MethodNotAllowed
|
|
183
|
-
else
|
|
184
|
-
if(::File.directory?(::File.dirname(file_path)) && !::File.exists?(file_path) && !::File.exists?(file_path.gsub(/\/$/, '')))
|
|
185
|
-
Dir.mkdir(file_path)
|
|
186
|
-
Created
|
|
187
|
-
else
|
|
188
|
-
Conflict
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
else
|
|
192
|
-
UnsupportedMediaType
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
# Write to this resource from given IO.
|
|
197
|
-
def write(io)
|
|
198
|
-
tempfile = "#{file_path}.#{Process.pid}.#{object_id}"
|
|
199
|
-
open(tempfile, "wb") do |file|
|
|
200
|
-
while part = io.read(8192)
|
|
201
|
-
file << part
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
::File.rename(tempfile, file_path)
|
|
205
|
-
ensure
|
|
206
|
-
::File.unlink(tempfile) rescue nil
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# name:: String - Property name
|
|
210
|
-
# Returns the value of the given property
|
|
211
|
-
def get_property(name)
|
|
212
|
-
begin
|
|
213
|
-
super
|
|
214
|
-
rescue NotImplemented
|
|
215
|
-
custom_props(name)
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
# name:: String - Property name
|
|
220
|
-
# value:: New value
|
|
221
|
-
# Set the property to the given value
|
|
222
|
-
def set_property(name, value)
|
|
223
|
-
begin
|
|
224
|
-
super
|
|
225
|
-
rescue NotImplemented
|
|
226
|
-
set_custom_props(name,value)
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def remove_property(element)
|
|
231
|
-
prop_hash.transaction do
|
|
232
|
-
prop_hash.delete(to_element_key(element))
|
|
233
|
-
prop_hash.commit
|
|
234
|
-
end
|
|
235
|
-
val = prop_hash.transaction{ prop_hash[to_element_key(element)] }
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def lock(args)
|
|
239
|
-
unless(parent_exists?)
|
|
240
|
-
Conflict
|
|
241
|
-
else
|
|
242
|
-
lock_check(args[:type])
|
|
243
|
-
lock = FileResourceLock.explicit_locks(@path, root, :scope => args[:scope], :kind => args[:type], :user => @user)
|
|
244
|
-
unless(lock)
|
|
245
|
-
token = UUIDTools::UUID.random_create.to_s
|
|
246
|
-
lock = FileResourceLock.generate(@path, @user, token, root)
|
|
247
|
-
lock.scope = args[:scope]
|
|
248
|
-
lock.kind = args[:type]
|
|
249
|
-
lock.owner = args[:owner]
|
|
250
|
-
lock.depth = args[:depth]
|
|
251
|
-
if(args[:timeout])
|
|
252
|
-
lock.timeout = args[:timeout] <= @max_timeout && args[:timeout] > 0 ? args[:timeout] : @max_timeout
|
|
253
|
-
else
|
|
254
|
-
lock.timeout = @default_timeout
|
|
255
|
-
end
|
|
256
|
-
lock.save
|
|
257
|
-
end
|
|
258
|
-
begin
|
|
259
|
-
lock_check(args[:type])
|
|
260
|
-
rescue RackWebDAV::LockFailure => lock_failure
|
|
261
|
-
lock.destroy
|
|
262
|
-
raise lock_failure
|
|
263
|
-
rescue HTTPStatus::Status => status
|
|
264
|
-
status
|
|
265
|
-
end
|
|
266
|
-
[lock.remaining_timeout, lock.token]
|
|
267
|
-
end
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def unlock(token)
|
|
271
|
-
token = token.slice(1, token.length - 2)
|
|
272
|
-
if(token.nil? || token.empty?)
|
|
273
|
-
BadRequest
|
|
274
|
-
else
|
|
275
|
-
lock = FileResourceLock.find_by_token(token, root)
|
|
276
|
-
if lock.nil?
|
|
277
|
-
Forbidden
|
|
278
|
-
elsif(lock.path !~ /^#{Regexp.escape(@path)}.*$/)
|
|
279
|
-
Conflict
|
|
280
|
-
else
|
|
281
|
-
lock.destroy
|
|
282
|
-
NoContent
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
protected
|
|
288
|
-
|
|
289
|
-
def lock_check(lock_type=nil)
|
|
290
|
-
if(FileResourceLock.explicitly_locked?(@path, root))
|
|
291
|
-
raise Locked if lock_type && lock_type == 'exclusive'
|
|
292
|
-
#raise Locked if FileResourceLock.explicit_locks(@path, root).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id]).size > 0
|
|
293
|
-
elsif(FileResourceLock.implicitly_locked?(@path, root))
|
|
294
|
-
if(lock_type.to_s == 'exclusive')
|
|
295
|
-
locks = FileResourceLock.implicit_locks(@path)
|
|
296
|
-
failure = RackWebDAV::LockFailure.new("Failed to lock: #{@path}")
|
|
297
|
-
locks.each do |lock|
|
|
298
|
-
failure.add_failure(@path, Locked)
|
|
299
|
-
end
|
|
300
|
-
raise failure
|
|
301
|
-
else
|
|
302
|
-
if @user.respond_to?(:id)
|
|
303
|
-
locks = FileResourceLock.implict_locks(@path).find(:all, :conditions => ["scope = 'exclusive' AND user_id != ?", @user.id])
|
|
304
|
-
else
|
|
305
|
-
locks = []
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
if(locks.size > 0)
|
|
309
|
-
failure = LockFailure.new("Failed to lock: #{@path}")
|
|
310
|
-
locks.each do |lock|
|
|
311
|
-
failure.add_failure(@path, Locked)
|
|
312
|
-
end
|
|
313
|
-
raise failure
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
def set_custom_props(element, val)
|
|
320
|
-
prop_hash.transaction do
|
|
321
|
-
prop_hash[to_element_key(element)] = val
|
|
322
|
-
prop_hash.commit
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
def custom_props(element)
|
|
327
|
-
val = prop_hash.transaction(true) do
|
|
328
|
-
prop_hash[to_element_key(element)]
|
|
329
|
-
end
|
|
330
|
-
raise NotFound unless val
|
|
331
|
-
val
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
def store_directory
|
|
335
|
-
path = ::File.join(root, '.attrib_store')
|
|
336
|
-
unless(::File.directory?(::File.dirname(path)))
|
|
337
|
-
FileUtils.mkdir_p(::File.dirname(path))
|
|
338
|
-
end
|
|
339
|
-
path
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
def prop_path
|
|
343
|
-
path = ::File.join(store_directory, "#{::File.join(::File.dirname(file_path), ::File.basename(file_path)).gsub('/', '_')}.pstore")
|
|
344
|
-
unless(::File.directory?(::File.dirname(path)))
|
|
345
|
-
FileUtils.mkdir_p(::File.dirname(path))
|
|
346
|
-
end
|
|
347
|
-
path
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
def lock_path
|
|
351
|
-
path = ::File.join(store_directory, 'locks.pstore')
|
|
352
|
-
unless(::File.directory?(::File.dirname(path)))
|
|
353
|
-
FileUtils.mkdir_p(::File.dirname(path))
|
|
354
|
-
end
|
|
355
|
-
path
|
|
356
|
-
end
|
|
357
|
-
|
|
358
|
-
def prop_hash
|
|
359
|
-
@_prop_hash ||= IS_18 ? PStore.new(prop_path) : PStore.new(prop_path, true)
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
def authenticate(user, pass)
|
|
363
|
-
if(options[:username])
|
|
364
|
-
options[:username] == user && options[:password] == pass
|
|
365
|
-
else
|
|
366
|
-
true
|
|
367
|
-
end
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
def root
|
|
371
|
-
@options[:root]
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
def file_path
|
|
375
|
-
::File.join(root, path)
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
def stat
|
|
379
|
-
@stat ||= ::File.stat(file_path)
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
end
|