webrick-webdav 1.0
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.
- 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: []
|