webrick-webdav 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/webrick/httpservlet/webdavhandler.rb +583 -0
- metadata +39 -0
@@ -0,0 +1,583 @@
|
|
1
|
+
#
|
2
|
+
# webdavhandler.rb - WEBrick WebDAV handler
|
3
|
+
#
|
4
|
+
# Author: Tatsuki Sugiura <sugi@nemui.org>
|
5
|
+
# License: Ruby's
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'time'
|
9
|
+
require 'fileutils.rb'
|
10
|
+
require 'rexml/document'
|
11
|
+
require 'webrick/httpservlet/filehandler'
|
12
|
+
require 'iconv'
|
13
|
+
|
14
|
+
module WEBrick
|
15
|
+
class HTTPRequest
|
16
|
+
# buffer is too small to transport huge files...
|
17
|
+
if BUFSIZE < 512 * 1024
|
18
|
+
remove_const :BUFSIZE
|
19
|
+
BUFSIZE = 512 * 1024
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Config
|
24
|
+
webdavconf = {
|
25
|
+
:FileSystemCoding => "UTF-8",
|
26
|
+
:DefaultClientCoding => "UTF-8",
|
27
|
+
:DefaultClientCodingWin => "CP932",
|
28
|
+
:DefaultClientCodingMacx => "UTF-8",
|
29
|
+
:DefaultClientCodingUnix => "EUC-JP",
|
30
|
+
:NotInListName => %w(.*),
|
31
|
+
:NondisclosureName => %w(.ht*),
|
32
|
+
}
|
33
|
+
WebDAVHandler = FileHandler.merge(webdavconf)
|
34
|
+
end
|
35
|
+
|
36
|
+
module HTTPStatus
|
37
|
+
new_StatusMessage = {
|
38
|
+
102, 'Processing',
|
39
|
+
207, 'Multi-Status',
|
40
|
+
422, 'Unprocessable Entity',
|
41
|
+
423, 'Locked',
|
42
|
+
424, 'Failed Dependency',
|
43
|
+
507, 'Insufficient Storage',
|
44
|
+
}
|
45
|
+
StatusMessage.each_key {|k| new_StatusMessage.delete(k)}
|
46
|
+
StatusMessage.update new_StatusMessage
|
47
|
+
|
48
|
+
new_StatusMessage.each{|code, message|
|
49
|
+
var_name = message.gsub(/[ \-]/,'_').upcase
|
50
|
+
err_name = message.gsub(/[ \-]/,'')
|
51
|
+
|
52
|
+
case code
|
53
|
+
when 100...200; parent = Info
|
54
|
+
when 200...300; parent = Success
|
55
|
+
when 300...400; parent = Redirect
|
56
|
+
when 400...500; parent = ClientError
|
57
|
+
when 500...600; parent = ServerError
|
58
|
+
end
|
59
|
+
|
60
|
+
eval %-
|
61
|
+
RC_#{var_name} = #{code}
|
62
|
+
class #{err_name} < #{parent}
|
63
|
+
def self.code() RC_#{var_name} end
|
64
|
+
def self.reason_phrase() StatusMessage[code] end
|
65
|
+
def code() self::class::code end
|
66
|
+
def reason_phrase() self::class::reason_phrase end
|
67
|
+
alias to_i code
|
68
|
+
end
|
69
|
+
-
|
70
|
+
|
71
|
+
CodeToError[code] = const_get(err_name)
|
72
|
+
}
|
73
|
+
end # HTTPStatus
|
74
|
+
end # WEBrick
|
75
|
+
|
76
|
+
module WEBrick; module HTTPServlet;
|
77
|
+
class WebDAVHandler < FileHandler
|
78
|
+
class Unsupported < NotImplementedError; end
|
79
|
+
class IgnoreProp < StandardError; end
|
80
|
+
|
81
|
+
class CodeConvFilter
|
82
|
+
module Detector
|
83
|
+
def dav_ua(req)
|
84
|
+
case req["USER-AGENT"]
|
85
|
+
when /Microsoft Data Access Internet Publishing/
|
86
|
+
{@options[:DefaultClientCodingWin] => 70, "UTF-8" => 30}
|
87
|
+
when /^gnome-vfs/
|
88
|
+
{"UTF-8" => 90}
|
89
|
+
when /^WebDAVFS/
|
90
|
+
{@options[:DefaultClientCodingMacx] => 80}
|
91
|
+
when /Konqueror/
|
92
|
+
{@options[:DefaultClientCodingUnix] => 60, "UTF-8" => 40}
|
93
|
+
else
|
94
|
+
{}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def chk_utf8(req)
|
99
|
+
begin
|
100
|
+
Iconv.iconv("UTF-8", "UTF-8", req.path, req.path_info)
|
101
|
+
{"UTF-8" => 40}
|
102
|
+
rescue Iconv::IllegalSequence
|
103
|
+
{"UTF-8" => -500}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def chk_os(req)
|
108
|
+
case req["USER-AGENT"]
|
109
|
+
when /Microsoft|Windows/i
|
110
|
+
{@options[:DefaultClientCodingWin] => 10}
|
111
|
+
when /UNIX|X11/i
|
112
|
+
{@options[:DefaultClientCodingUnix] => 10}
|
113
|
+
when /darwin|MacOSX/
|
114
|
+
{"UTF-8" => 20}
|
115
|
+
else
|
116
|
+
{}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def default(req)
|
121
|
+
{@options[:DefaultClientCoding] => 20}
|
122
|
+
end
|
123
|
+
end # Detector
|
124
|
+
|
125
|
+
def initialize(options={}, default=Config::WebDAVHandler)
|
126
|
+
@options = default.merge(options)
|
127
|
+
@detect_meth = [:default, :chk_utf8, :dav_ua, :chk_os]
|
128
|
+
@enc_score = Hash.new(0)
|
129
|
+
end
|
130
|
+
attr_accessor :detect_meth
|
131
|
+
|
132
|
+
def detect(req)
|
133
|
+
self.extend Detector
|
134
|
+
detect_meth.each { |meth|
|
135
|
+
score = self.__send__ meth, req
|
136
|
+
@enc_score.update(score) {|enc, cur, new| cur + new}
|
137
|
+
}
|
138
|
+
#$DEBUG and $stderr.puts "code detection score ===> #{@enc_score.inspect}"
|
139
|
+
platform_codename(@enc_score.keys.sort_by{|k| @enc_score[k] }.last)
|
140
|
+
end
|
141
|
+
|
142
|
+
def conv(req, from=nil, to="UTF-8")
|
143
|
+
from ||= detect(req)
|
144
|
+
#$DEBUG and $stderr.puts "=== CONVERT === #{from} -> #{to}"
|
145
|
+
return true if from == to
|
146
|
+
req.path_info = Iconv.iconv(to, from, req.path_info).first
|
147
|
+
req.instance_variable_set :@path, Iconv.iconv(to, from, req.path).first
|
148
|
+
req["destination"].nil? or req.instance_eval {
|
149
|
+
@header["destination"][0] = HTTPUtils.escape(
|
150
|
+
Iconv.iconv(to, from,
|
151
|
+
HTTPUtils.unescape(@header["destination"][0])).first)
|
152
|
+
}
|
153
|
+
true
|
154
|
+
end
|
155
|
+
|
156
|
+
def conv2fscode!(req)
|
157
|
+
conv(req, nil, @options[:FileSystemCoding])
|
158
|
+
end
|
159
|
+
|
160
|
+
def platform_codename(name)
|
161
|
+
case RUBY_PLATFORM
|
162
|
+
when /linux/
|
163
|
+
name
|
164
|
+
when /solaris|sunos/
|
165
|
+
{
|
166
|
+
"CP932" => "MS932",
|
167
|
+
"EUC-JP" => "eucJP"
|
168
|
+
}[name]
|
169
|
+
when /aix/
|
170
|
+
{
|
171
|
+
"CP932" => "IBM-932",
|
172
|
+
"EUC-JP" => "IBM-eucJP"
|
173
|
+
}[name]
|
174
|
+
else
|
175
|
+
name
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end # CodeConvFilter
|
179
|
+
|
180
|
+
def initialize(server, root, options={}, default=Config::WebDAVHandler)
|
181
|
+
super
|
182
|
+
@cconv = CodeConvFilter.new(@options)
|
183
|
+
end
|
184
|
+
|
185
|
+
def service(req, res)
|
186
|
+
codeconv_req!(req)
|
187
|
+
super
|
188
|
+
end
|
189
|
+
|
190
|
+
# TODO:
|
191
|
+
# class 2 protocols; LOCK UNLOCK
|
192
|
+
#def do_LOCK(req, res)
|
193
|
+
#end
|
194
|
+
#def do_UNLOCK(req, res)
|
195
|
+
#end
|
196
|
+
|
197
|
+
def do_OPTIONS(req, res)
|
198
|
+
@logger.debug "run do_OPTIONS"
|
199
|
+
#res["DAV"] = "1,2"
|
200
|
+
res["DAV"] = "1"
|
201
|
+
res["MS-Author-Via"] = "DAV"
|
202
|
+
super
|
203
|
+
end
|
204
|
+
|
205
|
+
def do_PROPFIND(req, res)
|
206
|
+
map_filename(req, res)
|
207
|
+
@logger.debug "propfind requeset depth=#{req['Depth']}"
|
208
|
+
depth = (req["Depth"].nil? || req["Depth"] == "infinity") ? nil : req["Depth"].to_i
|
209
|
+
raise HTTPStatus::Forbidden unless depth # deny inifinite propfind
|
210
|
+
|
211
|
+
begin
|
212
|
+
req_doc = REXML::Document.new req.body
|
213
|
+
rescue REXML::ParseException
|
214
|
+
raise HTTPStatus::BadRequest
|
215
|
+
end
|
216
|
+
|
217
|
+
ns = {""=>"DAV:"}
|
218
|
+
req_props = []
|
219
|
+
all_props = %w(creationdate getlastmodified getetag
|
220
|
+
resourcetype getcontenttype getcontentlength)
|
221
|
+
|
222
|
+
if req.body.nil? || !REXML::XPath.match(req_doc, "/propfind/allprop", ns).empty?
|
223
|
+
req_props = all_props
|
224
|
+
elsif !REXML::XPath.match(req_doc, "/propfind/propname", ns).empty?
|
225
|
+
# TODO: support propname
|
226
|
+
raise HTTPStatus::NotImplemented
|
227
|
+
elsif !REXML::XPath.match(req_doc, "/propfind/prop", ns).empty?
|
228
|
+
REXML::XPath.each(req_doc, "/propfind/prop/*", ns){|e|
|
229
|
+
req_props << e.name
|
230
|
+
}
|
231
|
+
else
|
232
|
+
raise HTTPStatus::BadRequest
|
233
|
+
end
|
234
|
+
|
235
|
+
ret = get_rec_prop(req, res, res.filename,
|
236
|
+
HTTPUtils.escape(codeconv_str_fscode2utf(req.path)),
|
237
|
+
req_props, *[depth].compact)
|
238
|
+
res.body << build_multistat(ret).to_s(0)
|
239
|
+
res["Content-Type"] = 'text/xml; charset="utf-8"'
|
240
|
+
raise HTTPStatus::MultiStatus
|
241
|
+
end
|
242
|
+
|
243
|
+
def do_PROPPATCH(req, res)
|
244
|
+
map_filename(req, res)
|
245
|
+
ret = []
|
246
|
+
ns = {""=>"DAV:"}
|
247
|
+
begin
|
248
|
+
req_doc = REXML::Document.new req.body
|
249
|
+
rescue REXML::ParseException
|
250
|
+
raise HTTPStatus::BadRequest
|
251
|
+
end
|
252
|
+
REXML::XPath.each(req_doc, "/propertyupdate/remove/prop/*", ns){|e|
|
253
|
+
ps = REXML::Element.new "D:propstat"
|
254
|
+
ps.add_element("D:prop").add_element "D:"+e.name
|
255
|
+
ps << elem_status(req, res, HTTPStatus::Forbidden)
|
256
|
+
ret << ps
|
257
|
+
}
|
258
|
+
REXML::XPath.each(req_doc, "/propertyupdate/set/prop/*", ns){|e|
|
259
|
+
ps = REXML::Element.new "D:propstat"
|
260
|
+
ps.add_element("D:prop").add_element "D:"+e.name
|
261
|
+
begin
|
262
|
+
e.namespace.nil? || e.namespace == "DAV:" or raise Unsupported
|
263
|
+
case e.name
|
264
|
+
when "getlastmodified"
|
265
|
+
File.utime(Time.now, Time.httpdate(e.text), res.filename)
|
266
|
+
else
|
267
|
+
raise Unsupported
|
268
|
+
end
|
269
|
+
ps << elem_status(req, res, HTTPStatus::OK)
|
270
|
+
rescue Errno::EACCES, ArgumentError
|
271
|
+
ps << elem_status(req, res, HTTPStatus::Conflict)
|
272
|
+
rescue Unsupported
|
273
|
+
ps << elem_status(req, res, HTTPStatus::Forbidden)
|
274
|
+
rescue
|
275
|
+
ps << elem_status(req, res, HTTPStatus::InternalServerError)
|
276
|
+
end
|
277
|
+
ret << ps
|
278
|
+
}
|
279
|
+
res.body << build_multistat([[req.request_uri, *ret]]).to_s(0)
|
280
|
+
res["Content-Type"] = 'text/xml; charset="utf-8"'
|
281
|
+
raise HTTPStatus::MultiStatus
|
282
|
+
end
|
283
|
+
|
284
|
+
def do_MKCOL(req, res)
|
285
|
+
req.body.nil? or raise HTTPStatus::MethodNotAllowed
|
286
|
+
begin
|
287
|
+
@logger.debug "mkdir #{@root+req.path_info}"
|
288
|
+
Dir.mkdir(@root + req.path_info)
|
289
|
+
rescue Errno::ENOENT, Errno::EACCES
|
290
|
+
raise HTTPStatus::Forbidden
|
291
|
+
rescue Errno::ENOSPC
|
292
|
+
raise HTTPStatus::InsufficientStorage
|
293
|
+
rescue Errno::EEXIST
|
294
|
+
raise HTTPStatus::Conflict
|
295
|
+
end
|
296
|
+
raise HTTPStatus::Created
|
297
|
+
end
|
298
|
+
|
299
|
+
def do_DELETE(req, res)
|
300
|
+
map_filename(req, res)
|
301
|
+
begin
|
302
|
+
@logger.debug "rm_rf #{res.filename}"
|
303
|
+
FileUtils.rm_rf(res.filename)
|
304
|
+
rescue Errno::EPERM
|
305
|
+
raise HTTPStatus::Forbidden
|
306
|
+
#rescue
|
307
|
+
# FIXME: to return correct error.
|
308
|
+
# we needs to stop useing rm_rf and check each deleted entries.
|
309
|
+
end
|
310
|
+
raise HTTPStatus::NoContent
|
311
|
+
end
|
312
|
+
|
313
|
+
def do_PUT(req, res)
|
314
|
+
file = @root + req.path_info
|
315
|
+
if req['range']
|
316
|
+
ranges = HTTPUtils::parse_range_header(req['range']) or
|
317
|
+
raise HTTPStatus::BadRequest,
|
318
|
+
"Unrecognized range-spec: \"#{req['range']}\""
|
319
|
+
end
|
320
|
+
|
321
|
+
if !ranges.nil? && ranges.length != 1
|
322
|
+
raise HTTPStatus::NotImplemented
|
323
|
+
end
|
324
|
+
|
325
|
+
begin
|
326
|
+
File.open(file, "w+") {|f|
|
327
|
+
if ranges
|
328
|
+
# TODO: supports multiple range
|
329
|
+
ranges.each{|range|
|
330
|
+
first, last = prepare_range(range, filesize)
|
331
|
+
first + req.content_length != last and
|
332
|
+
raise HTTPStatus::BadRequest
|
333
|
+
f.pos = first
|
334
|
+
req.body {|buf| f << buf }
|
335
|
+
}
|
336
|
+
else
|
337
|
+
req.body {|buf| f << buf }
|
338
|
+
end
|
339
|
+
}
|
340
|
+
rescue Errno::ENOENT
|
341
|
+
raise HTTPStatus::Conflict
|
342
|
+
rescue Errno::ENOSPC
|
343
|
+
raise HTTPStatus::InsufficientStorage
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def do_COPY(req, res)
|
348
|
+
src, dest, depth, exists_p = cp_mv_precheck(req, res)
|
349
|
+
@logger.debug "copy #{src} -> #{dest}"
|
350
|
+
begin
|
351
|
+
if depth.nil? # infinity
|
352
|
+
FileUtils.cp_r(src, dest, {:preserve => true})
|
353
|
+
elsif depth == 0
|
354
|
+
if File.directory?(src)
|
355
|
+
st = File.stat(src)
|
356
|
+
Dir.mkdir(dest)
|
357
|
+
begin
|
358
|
+
File.utime(st.atime, st.mtime, dest)
|
359
|
+
rescue
|
360
|
+
# simply ignore
|
361
|
+
end
|
362
|
+
else
|
363
|
+
FileUtils.cp(src, dest, {:preserve => true})
|
364
|
+
end
|
365
|
+
end
|
366
|
+
rescue Errno::ENOENT
|
367
|
+
raise HTTPStatus::Conflict
|
368
|
+
# FIXME: use multi status(?) and check error URL.
|
369
|
+
rescue Errno::ENOSPC
|
370
|
+
raise HTTPStatus::InsufficientStorage
|
371
|
+
end
|
372
|
+
|
373
|
+
raise exists_p ? HTTPStatus::NoContent : HTTPStatus::Created
|
374
|
+
end
|
375
|
+
|
376
|
+
def do_MOVE(req, res)
|
377
|
+
src, dest, depth, exists_p = cp_mv_precheck(req, res)
|
378
|
+
@logger.debug "rename #{src} -> #{dest}"
|
379
|
+
begin
|
380
|
+
File.rename(src, dest)
|
381
|
+
rescue Errno::ENOENT
|
382
|
+
raise HTTPStatus::Conflict
|
383
|
+
# FIXME: use multi status(?) and check error URL.
|
384
|
+
rescue Errno::ENOSPC
|
385
|
+
raise HTTPStatus::InsufficientStorage
|
386
|
+
end
|
387
|
+
|
388
|
+
if exists_p
|
389
|
+
raise HTTPStatus::NoContent
|
390
|
+
else
|
391
|
+
raise HTTPStatus::Created
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
######################
|
397
|
+
private
|
398
|
+
|
399
|
+
def get_handler(req)
|
400
|
+
return DefaultFileHandler
|
401
|
+
end
|
402
|
+
|
403
|
+
def search_index_file(req, res)
|
404
|
+
return nil
|
405
|
+
end
|
406
|
+
|
407
|
+
def cp_mv_precheck(req, res)
|
408
|
+
depth = (req["Depth"].nil? || req["Depth"] == "infinity") ? nil : req["Depth"].to_i
|
409
|
+
depth.nil? || depth == 0 or raise HTTPStatus::BadRequest
|
410
|
+
@logger.debug "copy/move requested. Deistnation=#{req['Destination']}"
|
411
|
+
dest_uri = URI.parse(req["Destination"])
|
412
|
+
unless "#{req.host}:#{req.port}" == "#{dest_uri.host}:#{dest_uri.port}"
|
413
|
+
raise HTTPStatus::BadGateway
|
414
|
+
# TODO: anyone needs to copy other server?
|
415
|
+
end
|
416
|
+
src = @root + req.path_info
|
417
|
+
dest = @root + resolv_destpath(req)
|
418
|
+
|
419
|
+
src == dest and raise HTTPStatus::Forbidden
|
420
|
+
|
421
|
+
exists_p = false
|
422
|
+
if File.exists?(dest)
|
423
|
+
exists_p = true
|
424
|
+
if req["Overwrite"] == "T"
|
425
|
+
@logger.debug "copy/move precheck: Overwrite flug=T, deleteing #{dest}"
|
426
|
+
FileUtils.rm_rf(dest)
|
427
|
+
else
|
428
|
+
raise HTTPStatus::PreconditionFailed
|
429
|
+
end
|
430
|
+
end
|
431
|
+
return *[src, dest, depth, exists_p]
|
432
|
+
end
|
433
|
+
|
434
|
+
def codeconv_req!(req)
|
435
|
+
@logger.debug "codeconv req obj: orig; path_info='#{req.path_info}', dest='#{req["Destination"]}'"
|
436
|
+
begin
|
437
|
+
@cconv.conv2fscode!(req)
|
438
|
+
rescue Iconv::IllegalSequence
|
439
|
+
@logger.warn "code conversion fail! for request object. #{@cconv.detect(req)}->(fscode)"
|
440
|
+
end
|
441
|
+
@logger.debug "codeconv req obj: ret; path_info='#{req.path_info}', dest='#{req["Destination"]}'"
|
442
|
+
true
|
443
|
+
end
|
444
|
+
|
445
|
+
def codeconv_str_fscode2utf(str)
|
446
|
+
return str if @options[:FileSystemCoding] == "UTF-8"
|
447
|
+
@logger.debug "codeconv str fscode2utf: orig='#{str}'"
|
448
|
+
begin
|
449
|
+
ret = Iconv.iconv("UTF-8", @options[:FileSystemCoding], str).first
|
450
|
+
rescue Iconv::IllegalSequence
|
451
|
+
@logger.warn "code conversion fail! #{@options[:FileSystemCoding]}->UTF-8 str=#{str.dump}"
|
452
|
+
ret = str
|
453
|
+
end
|
454
|
+
@logger.debug "codeconv str fscode2utf: ret='#{ret}'"
|
455
|
+
ret
|
456
|
+
end
|
457
|
+
|
458
|
+
def map_filename(req, res)
|
459
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root
|
460
|
+
set_filename(req, res)
|
461
|
+
end
|
462
|
+
|
463
|
+
def build_multistat(rs)
|
464
|
+
m = elem_multistat
|
465
|
+
rs.each {|href, *cont|
|
466
|
+
res = m.add_element "D:response"
|
467
|
+
res.add_element("D:href").text = href
|
468
|
+
cont.flatten.each {|c| res.elements << c}
|
469
|
+
}
|
470
|
+
REXML::Document.new << m
|
471
|
+
end
|
472
|
+
|
473
|
+
def elem_status(req, res, retcodesym)
|
474
|
+
gen_element("D:status",
|
475
|
+
"HTTP/#{req.http_version} #{retcodesym.code} #{retcodesym.reason_phrase}")
|
476
|
+
end
|
477
|
+
|
478
|
+
def get_rec_prop(req, res, file, r_uri, props, depth = 5000)
|
479
|
+
ret_set = []
|
480
|
+
depth -= 1
|
481
|
+
ret_set << [r_uri, get_propstat(req, res, file, props)]
|
482
|
+
@logger.debug "get prop file='#{file}' depth=#{depth}"
|
483
|
+
return ret_set if !(File.directory?(file) && depth >= 0)
|
484
|
+
Dir.entries(file).each {|d|
|
485
|
+
d == ".." || d == "." and next
|
486
|
+
(@options[:NondisclosureName]+@options[:NotInListName]).find {|pat|
|
487
|
+
File.fnmatch(pat, d) } and next
|
488
|
+
if File.directory?("#{file}/#{d}")
|
489
|
+
ret_set += get_rec_prop(req, res, "#{file}/#{d}",
|
490
|
+
HTTPUtils.normalize_path(
|
491
|
+
r_uri+HTTPUtils.escape(
|
492
|
+
codeconv_str_fscode2utf("/#{d}/"))),
|
493
|
+
props, depth)
|
494
|
+
else
|
495
|
+
ret_set << [HTTPUtils.normalize_path(
|
496
|
+
r_uri+HTTPUtils.escape(
|
497
|
+
codeconv_str_fscode2utf("/#{d}"))),
|
498
|
+
get_propstat(req, res, "#{file}/#{d}", props)]
|
499
|
+
end
|
500
|
+
}
|
501
|
+
ret_set
|
502
|
+
end
|
503
|
+
|
504
|
+
def get_propstat(req, res, file, props)
|
505
|
+
propstat = REXML::Element.new "D:propstat"
|
506
|
+
errstat = {}
|
507
|
+
begin
|
508
|
+
st = File::lstat(file)
|
509
|
+
pe = REXML::Element.new "D:prop"
|
510
|
+
props.each {|pname|
|
511
|
+
begin
|
512
|
+
if respond_to?("get_prop_#{pname}", true)
|
513
|
+
pe << __send__("get_prop_#{pname}", file, st)
|
514
|
+
else
|
515
|
+
raise HTTPStatus::NotFound
|
516
|
+
end
|
517
|
+
rescue IgnoreProp
|
518
|
+
# simple ignore
|
519
|
+
rescue HTTPStatus::Status
|
520
|
+
# FIXME: add to errstat
|
521
|
+
end
|
522
|
+
}
|
523
|
+
propstat.elements << pe
|
524
|
+
propstat.elements << elem_status(req, res, HTTPStatus::OK)
|
525
|
+
rescue
|
526
|
+
propstat.elements << elem_status(req, res, HTTPStatus::InternalServerError)
|
527
|
+
end
|
528
|
+
propstat
|
529
|
+
end
|
530
|
+
|
531
|
+
def get_prop_creationdate(file, st)
|
532
|
+
gen_element "D:creationdate", st.ctime.xmlschema
|
533
|
+
end
|
534
|
+
|
535
|
+
def get_prop_getlastmodified(file, st)
|
536
|
+
gen_element "D:getlastmodified", st.mtime.httpdate
|
537
|
+
end
|
538
|
+
|
539
|
+
def get_prop_getetag(file, st)
|
540
|
+
gen_element "D:getetag", sprintf('%x-%x-%x', st.ino, st.size, st.mtime.to_i)
|
541
|
+
end
|
542
|
+
|
543
|
+
def get_prop_resourcetype(file, st)
|
544
|
+
t = gen_element "D:resourcetype"
|
545
|
+
File.directory?(file) and t.add_element("D:collection")
|
546
|
+
t
|
547
|
+
end
|
548
|
+
|
549
|
+
def get_prop_getcontenttype(file, st)
|
550
|
+
gen_element("D:getcontenttype",
|
551
|
+
File.file?(file) ?
|
552
|
+
HTTPUtils::mime_type(file, @config[:MimeTypes]) :
|
553
|
+
"httpd/unix-directory")
|
554
|
+
end
|
555
|
+
|
556
|
+
def get_prop_getcontentlength(file, st)
|
557
|
+
File.file?(file) or raise HTTPStatus::NotFound
|
558
|
+
gen_element "D:getcontentlength", st.size
|
559
|
+
end
|
560
|
+
|
561
|
+
def elem_multistat
|
562
|
+
gen_element "D:multistatus", nil, {"xmlns:D" => "DAV:"}
|
563
|
+
end
|
564
|
+
|
565
|
+
def gen_element(elem, text = nil, attrib = {})
|
566
|
+
e = REXML::Element.new elem
|
567
|
+
text and e.text = text
|
568
|
+
attrib.each {|k, v| e.attributes[k] = v }
|
569
|
+
e
|
570
|
+
end
|
571
|
+
|
572
|
+
def resolv_destpath(req)
|
573
|
+
if /^#{Regexp.escape(req.script_name)}/ =~
|
574
|
+
HTTPUtils.unescape(URI.parse(req["Destination"]).path)
|
575
|
+
return $'
|
576
|
+
else
|
577
|
+
@logger.error "[BUG] can't resolv destination path. script='#{req.script_name}', path='#{req.path}', dest='#{req["Destination"]}', root='#{@root}'"
|
578
|
+
raise HTTPStatus::InternalServerError
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
end # WebDAVHandler
|
583
|
+
end; end # HTTPServlet; WEBrick
|
metadata
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: webrick-webdav
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "1.0"
|
7
|
+
date: 2005-07-24
|
8
|
+
summary: "WebDAV handler for WEBrick, Ruby's HTTP toolkit."
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: sugi@nemui.org
|
12
|
+
homepage: http://sugi.nemui.org/
|
13
|
+
rubyforge_project:
|
14
|
+
description: "A class for handling WebDAV requests through WEBrick, Ruby's built-in HTTP
|
15
|
+
server toolkit. This class was originally a part of Sarada, a configurable
|
16
|
+
WebDAV server available at: http://sugi.nemui.org/pub/ruby/sarada/."
|
17
|
+
autorequire:
|
18
|
+
default_executable:
|
19
|
+
bindir: bin
|
20
|
+
has_rdoc: false
|
21
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
22
|
+
requirements:
|
23
|
+
-
|
24
|
+
- ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.0
|
27
|
+
version:
|
28
|
+
platform: ruby
|
29
|
+
authors:
|
30
|
+
- Tatsuki Sugiura
|
31
|
+
files:
|
32
|
+
- lib/webrick/httpservlet/webdavhandler.rb
|
33
|
+
test_files: []
|
34
|
+
rdoc_options: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
requirements: []
|
39
|
+
dependencies: []
|