rubysl-webrick 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/rubysl/webrick.rb +2 -0
- data/lib/rubysl/webrick/version.rb +5 -0
- data/lib/rubysl/webrick/webrick.rb +29 -0
- data/lib/webrick.rb +1 -0
- data/lib/webrick/accesslog.rb +67 -0
- data/lib/webrick/cgi.rb +257 -0
- data/lib/webrick/compat.rb +15 -0
- data/lib/webrick/config.rb +97 -0
- data/lib/webrick/cookie.rb +110 -0
- data/lib/webrick/htmlutils.rb +25 -0
- data/lib/webrick/httpauth.rb +45 -0
- data/lib/webrick/httpauth/authenticator.rb +79 -0
- data/lib/webrick/httpauth/basicauth.rb +65 -0
- data/lib/webrick/httpauth/digestauth.rb +343 -0
- data/lib/webrick/httpauth/htdigest.rb +91 -0
- data/lib/webrick/httpauth/htgroup.rb +61 -0
- data/lib/webrick/httpauth/htpasswd.rb +83 -0
- data/lib/webrick/httpauth/userdb.rb +29 -0
- data/lib/webrick/httpproxy.rb +254 -0
- data/lib/webrick/httprequest.rb +365 -0
- data/lib/webrick/httpresponse.rb +327 -0
- data/lib/webrick/https.rb +63 -0
- data/lib/webrick/httpserver.rb +210 -0
- data/lib/webrick/httpservlet.rb +22 -0
- data/lib/webrick/httpservlet/abstract.rb +71 -0
- data/lib/webrick/httpservlet/cgi_runner.rb +45 -0
- data/lib/webrick/httpservlet/cgihandler.rb +104 -0
- data/lib/webrick/httpservlet/erbhandler.rb +54 -0
- data/lib/webrick/httpservlet/filehandler.rb +398 -0
- data/lib/webrick/httpservlet/prochandler.rb +33 -0
- data/lib/webrick/httpstatus.rb +126 -0
- data/lib/webrick/httputils.rb +391 -0
- data/lib/webrick/httpversion.rb +49 -0
- data/lib/webrick/log.rb +88 -0
- data/lib/webrick/server.rb +200 -0
- data/lib/webrick/ssl.rb +126 -0
- data/lib/webrick/utils.rb +100 -0
- data/lib/webrick/version.rb +13 -0
- data/rubysl-webrick.gemspec +23 -0
- metadata +145 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
#
|
2
|
+
# erbhandler.rb -- ERBHandler Class
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
6
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
7
|
+
# reserved.
|
8
|
+
#
|
9
|
+
# $IPR: erbhandler.rb,v 1.25 2003/02/24 19:25:31 gotoyuzo Exp $
|
10
|
+
|
11
|
+
require 'webrick/httpservlet/abstract.rb'
|
12
|
+
|
13
|
+
require 'erb'
|
14
|
+
|
15
|
+
module WEBrick
|
16
|
+
module HTTPServlet
|
17
|
+
|
18
|
+
class ERBHandler < AbstractServlet
|
19
|
+
def initialize(server, name)
|
20
|
+
super
|
21
|
+
@script_filename = name
|
22
|
+
end
|
23
|
+
|
24
|
+
def do_GET(req, res)
|
25
|
+
unless defined?(ERB)
|
26
|
+
@logger.warn "#{self.class}: ERB not defined."
|
27
|
+
raise HTTPStatus::Forbidden, "ERBHandler cannot work."
|
28
|
+
end
|
29
|
+
begin
|
30
|
+
data = open(@script_filename){|io| io.read }
|
31
|
+
res.body = evaluate(ERB.new(data), req, res)
|
32
|
+
res['content-type'] =
|
33
|
+
HTTPUtils::mime_type(@script_filename, @config[:MimeTypes])
|
34
|
+
rescue StandardError => ex
|
35
|
+
raise
|
36
|
+
rescue Exception => ex
|
37
|
+
@logger.error(ex)
|
38
|
+
raise HTTPStatus::InternalServerError, ex.message
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
alias do_POST do_GET
|
43
|
+
|
44
|
+
private
|
45
|
+
def evaluate(erb, servlet_request, servlet_response)
|
46
|
+
Module.new.module_eval{
|
47
|
+
meta_vars = servlet_request.meta_vars
|
48
|
+
query = servlet_request.query
|
49
|
+
erb.result(binding)
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,398 @@
|
|
1
|
+
#
|
2
|
+
# filehandler.rb -- FileHandler Module
|
3
|
+
#
|
4
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
|
+
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
6
|
+
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
|
7
|
+
# reserved.
|
8
|
+
#
|
9
|
+
# $IPR: filehandler.rb,v 1.44 2003/06/07 01:34:51 gotoyuzo Exp $
|
10
|
+
|
11
|
+
require 'thread'
|
12
|
+
require 'time'
|
13
|
+
|
14
|
+
require 'webrick/htmlutils'
|
15
|
+
require 'webrick/httputils'
|
16
|
+
require 'webrick/httpstatus'
|
17
|
+
|
18
|
+
module WEBrick
|
19
|
+
module HTTPServlet
|
20
|
+
|
21
|
+
class DefaultFileHandler < AbstractServlet
|
22
|
+
def initialize(server, local_path)
|
23
|
+
super
|
24
|
+
@local_path = local_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def do_GET(req, res)
|
28
|
+
st = File::stat(@local_path)
|
29
|
+
mtime = st.mtime
|
30
|
+
res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)
|
31
|
+
|
32
|
+
if not_modified?(req, res, mtime, res['etag'])
|
33
|
+
res.body = ''
|
34
|
+
raise HTTPStatus::NotModified
|
35
|
+
elsif req['range']
|
36
|
+
make_partial_content(req, res, @local_path, st.size)
|
37
|
+
raise HTTPStatus::PartialContent
|
38
|
+
else
|
39
|
+
mtype = HTTPUtils::mime_type(@local_path, @config[:MimeTypes])
|
40
|
+
res['content-type'] = mtype
|
41
|
+
res['content-length'] = st.size
|
42
|
+
res['last-modified'] = mtime.httpdate
|
43
|
+
res.body = open(@local_path, "rb")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def not_modified?(req, res, mtime, etag)
|
48
|
+
if ir = req['if-range']
|
49
|
+
begin
|
50
|
+
if Time.httpdate(ir) >= mtime
|
51
|
+
return true
|
52
|
+
end
|
53
|
+
rescue
|
54
|
+
if HTTPUtils::split_header_value(ir).member?(res['etag'])
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
if (ims = req['if-modified-since']) && Time.parse(ims) >= mtime
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
|
64
|
+
if (inm = req['if-none-match']) &&
|
65
|
+
HTTPUtils::split_header_value(inm).member?(res['etag'])
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
|
72
|
+
def make_partial_content(req, res, filename, filesize)
|
73
|
+
mtype = HTTPUtils::mime_type(filename, @config[:MimeTypes])
|
74
|
+
unless ranges = HTTPUtils::parse_range_header(req['range'])
|
75
|
+
raise HTTPStatus::BadRequest,
|
76
|
+
"Unrecognized range-spec: \"#{req['range']}\""
|
77
|
+
end
|
78
|
+
open(filename, "rb"){|io|
|
79
|
+
if ranges.size > 1
|
80
|
+
time = Time.now
|
81
|
+
boundary = "#{time.sec}_#{time.usec}_#{Process::pid}"
|
82
|
+
body = ''
|
83
|
+
ranges.each{|range|
|
84
|
+
first, last = prepare_range(range, filesize)
|
85
|
+
next if first < 0
|
86
|
+
io.pos = first
|
87
|
+
content = io.read(last-first+1)
|
88
|
+
body << "--" << boundary << CRLF
|
89
|
+
body << "Content-Type: #{mtype}" << CRLF
|
90
|
+
body << "Content-Range: #{first}-#{last}/#{filesize}" << CRLF
|
91
|
+
body << CRLF
|
92
|
+
body << content
|
93
|
+
body << CRLF
|
94
|
+
}
|
95
|
+
raise HTTPStatus::RequestRangeNotSatisfiable if body.empty?
|
96
|
+
body << "--" << boundary << "--" << CRLF
|
97
|
+
res["content-type"] = "multipart/byteranges; boundary=#{boundary}"
|
98
|
+
res.body = body
|
99
|
+
elsif range = ranges[0]
|
100
|
+
first, last = prepare_range(range, filesize)
|
101
|
+
raise HTTPStatus::RequestRangeNotSatisfiable if first < 0
|
102
|
+
if last == filesize - 1
|
103
|
+
content = io.dup
|
104
|
+
content.pos = first
|
105
|
+
else
|
106
|
+
io.pos = first
|
107
|
+
content = io.read(last-first+1)
|
108
|
+
end
|
109
|
+
res['content-type'] = mtype
|
110
|
+
res['content-range'] = "#{first}-#{last}/#{filesize}"
|
111
|
+
res['content-length'] = last - first + 1
|
112
|
+
res.body = content
|
113
|
+
else
|
114
|
+
raise HTTPStatus::BadRequest
|
115
|
+
end
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def prepare_range(range, filesize)
|
120
|
+
first = range.first < 0 ? filesize + range.first : range.first
|
121
|
+
return -1, -1 if first < 0 || first >= filesize
|
122
|
+
last = range.last < 0 ? filesize + range.last : range.last
|
123
|
+
last = filesize - 1 if last >= filesize
|
124
|
+
return first, last
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class FileHandler < AbstractServlet
|
129
|
+
HandlerTable = Hash.new
|
130
|
+
|
131
|
+
def self.add_handler(suffix, handler)
|
132
|
+
HandlerTable[suffix] = handler
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.remove_handler(suffix)
|
136
|
+
HandlerTable.delete(suffix)
|
137
|
+
end
|
138
|
+
|
139
|
+
def initialize(server, root, options={}, default=Config::FileHandler)
|
140
|
+
@config = server.config
|
141
|
+
@logger = @config[:Logger]
|
142
|
+
@root = File.expand_path(root)
|
143
|
+
if options == true || options == false
|
144
|
+
options = { :FancyIndexing => options }
|
145
|
+
end
|
146
|
+
@options = default.dup.update(options)
|
147
|
+
end
|
148
|
+
|
149
|
+
def service(req, res)
|
150
|
+
# if this class is mounted on "/" and /~username is requested.
|
151
|
+
# we're going to override path informations before invoking service.
|
152
|
+
if defined?(Etc) && @options[:UserDir] && req.script_name.empty?
|
153
|
+
if %r|^(/~([^/]+))| =~ req.path_info
|
154
|
+
script_name, user = $1, $2
|
155
|
+
path_info = $'
|
156
|
+
begin
|
157
|
+
passwd = Etc::getpwnam(user)
|
158
|
+
@root = File::join(passwd.dir, @options[:UserDir])
|
159
|
+
req.script_name = script_name
|
160
|
+
req.path_info = path_info
|
161
|
+
rescue
|
162
|
+
@logger.debug "#{self.class}#do_GET: getpwnam(#{user}) failed"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
super(req, res)
|
167
|
+
end
|
168
|
+
|
169
|
+
def do_GET(req, res)
|
170
|
+
unless exec_handler(req, res)
|
171
|
+
set_dir_list(req, res)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def do_POST(req, res)
|
176
|
+
unless exec_handler(req, res)
|
177
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def do_OPTIONS(req, res)
|
182
|
+
unless exec_handler(req, res)
|
183
|
+
super(req, res)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# ToDo
|
188
|
+
# RFC2518: HTTP Extensions for Distributed Authoring -- WEBDAV
|
189
|
+
#
|
190
|
+
# PROPFIND PROPPATCH MKCOL DELETE PUT COPY MOVE
|
191
|
+
# LOCK UNLOCK
|
192
|
+
|
193
|
+
# RFC3253: Versioning Extensions to WebDAV
|
194
|
+
# (Web Distributed Authoring and Versioning)
|
195
|
+
#
|
196
|
+
# VERSION-CONTROL REPORT CHECKOUT CHECK_IN UNCHECKOUT
|
197
|
+
# MKWORKSPACE UPDATE LABEL MERGE ACTIVITY
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
def exec_handler(req, res)
|
202
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root
|
203
|
+
if set_filename(req, res)
|
204
|
+
handler = get_handler(req)
|
205
|
+
call_callback(:HandlerCallback, req, res)
|
206
|
+
h = handler.get_instance(@config, res.filename)
|
207
|
+
h.service(req, res)
|
208
|
+
return true
|
209
|
+
end
|
210
|
+
call_callback(:HandlerCallback, req, res)
|
211
|
+
return false
|
212
|
+
end
|
213
|
+
|
214
|
+
def get_handler(req)
|
215
|
+
suffix1 = (/\.(\w+)$/ =~ req.script_name) && $1.downcase
|
216
|
+
suffix2 = (/\.(\w+)\.[\w\-]+$/ =~ req.script_name) && $1.downcase
|
217
|
+
handler_table = @options[:HandlerTable]
|
218
|
+
return handler_table[suffix1] || handler_table[suffix2] ||
|
219
|
+
HandlerTable[suffix1] || HandlerTable[suffix2] ||
|
220
|
+
DefaultFileHandler
|
221
|
+
end
|
222
|
+
|
223
|
+
def set_filename(req, res)
|
224
|
+
res.filename = @root.dup
|
225
|
+
path_info = req.path_info.scan(%r|/[^/]*|)
|
226
|
+
|
227
|
+
path_info.unshift("") # dummy for checking @root dir
|
228
|
+
while base = path_info.first
|
229
|
+
check_filename(req, res, base)
|
230
|
+
break if base == "/"
|
231
|
+
break unless File.directory?(res.filename + base)
|
232
|
+
shift_path_info(req, res, path_info)
|
233
|
+
call_callback(:DirectoryCallback, req, res)
|
234
|
+
end
|
235
|
+
|
236
|
+
if base = path_info.first
|
237
|
+
check_filename(req, res, base)
|
238
|
+
if base == "/"
|
239
|
+
if file = search_index_file(req, res)
|
240
|
+
shift_path_info(req, res, path_info, file)
|
241
|
+
call_callback(:FileCallback, req, res)
|
242
|
+
return true
|
243
|
+
end
|
244
|
+
shift_path_info(req, res, path_info)
|
245
|
+
elsif file = search_file(req, res, base)
|
246
|
+
shift_path_info(req, res, path_info, file)
|
247
|
+
call_callback(:FileCallback, req, res)
|
248
|
+
return true
|
249
|
+
else
|
250
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
return false
|
255
|
+
end
|
256
|
+
|
257
|
+
def check_filename(req, res, name)
|
258
|
+
@options[:NondisclosureName].each{|pattern|
|
259
|
+
if File.fnmatch("/#{pattern}", name)
|
260
|
+
@logger.warn("the request refers nondisclosure name `#{name}'.")
|
261
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
262
|
+
end
|
263
|
+
}
|
264
|
+
end
|
265
|
+
|
266
|
+
def shift_path_info(req, res, path_info, base=nil)
|
267
|
+
tmp = path_info.shift
|
268
|
+
base = base || tmp
|
269
|
+
req.path_info = path_info.join
|
270
|
+
req.script_name << base
|
271
|
+
res.filename << base
|
272
|
+
end
|
273
|
+
|
274
|
+
def search_index_file(req, res)
|
275
|
+
@config[:DirectoryIndex].each{|index|
|
276
|
+
if file = search_file(req, res, "/"+index)
|
277
|
+
return file
|
278
|
+
end
|
279
|
+
}
|
280
|
+
return nil
|
281
|
+
end
|
282
|
+
|
283
|
+
def search_file(req, res, basename)
|
284
|
+
langs = @options[:AcceptableLanguages]
|
285
|
+
path = res.filename + basename
|
286
|
+
if File.file?(path)
|
287
|
+
return basename
|
288
|
+
elsif langs.size > 0
|
289
|
+
req.accept_language.each{|lang|
|
290
|
+
path_with_lang = path + ".#{lang}"
|
291
|
+
if langs.member?(lang) && File.file?(path_with_lang)
|
292
|
+
return basename + ".#{lang}"
|
293
|
+
end
|
294
|
+
}
|
295
|
+
(langs - req.accept_language).each{|lang|
|
296
|
+
path_with_lang = path + ".#{lang}"
|
297
|
+
if File.file?(path_with_lang)
|
298
|
+
return basename + ".#{lang}"
|
299
|
+
end
|
300
|
+
}
|
301
|
+
end
|
302
|
+
return nil
|
303
|
+
end
|
304
|
+
|
305
|
+
def call_callback(callback_name, req, res)
|
306
|
+
if cb = @options[callback_name]
|
307
|
+
cb.call(req, res)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def nondisclosure_name?(name)
|
312
|
+
@options[:NondisclosureName].each{|pattern|
|
313
|
+
if File.fnmatch(pattern, name)
|
314
|
+
return true
|
315
|
+
end
|
316
|
+
}
|
317
|
+
return false
|
318
|
+
end
|
319
|
+
|
320
|
+
def set_dir_list(req, res)
|
321
|
+
redirect_to_directory_uri(req, res)
|
322
|
+
unless @options[:FancyIndexing]
|
323
|
+
raise HTTPStatus::Forbidden, "no access permission to `#{req.path}'"
|
324
|
+
end
|
325
|
+
local_path = res.filename
|
326
|
+
list = Dir::entries(local_path).collect{|name|
|
327
|
+
next if name == "." || name == ".."
|
328
|
+
next if nondisclosure_name?(name)
|
329
|
+
st = (File::stat(local_path + name) rescue nil)
|
330
|
+
if st.nil?
|
331
|
+
[ name, nil, -1 ]
|
332
|
+
elsif st.directory?
|
333
|
+
[ name + "/", st.mtime, -1 ]
|
334
|
+
else
|
335
|
+
[ name, st.mtime, st.size ]
|
336
|
+
end
|
337
|
+
}
|
338
|
+
list.compact!
|
339
|
+
|
340
|
+
if d0 = req.query["N"]; idx = 0
|
341
|
+
elsif d0 = req.query["M"]; idx = 1
|
342
|
+
elsif d0 = req.query["S"]; idx = 2
|
343
|
+
else d0 = "A" ; idx = 0
|
344
|
+
end
|
345
|
+
d1 = (d0 == "A") ? "D" : "A"
|
346
|
+
|
347
|
+
if d0 == "A"
|
348
|
+
list.sort!{|a,b| a[idx] <=> b[idx] }
|
349
|
+
else
|
350
|
+
list.sort!{|a,b| b[idx] <=> a[idx] }
|
351
|
+
end
|
352
|
+
|
353
|
+
res['content-type'] = "text/html"
|
354
|
+
|
355
|
+
res.body = <<-_end_of_html_
|
356
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
357
|
+
<HTML>
|
358
|
+
<HEAD><TITLE>Index of #{HTMLUtils::escape(req.path)}</TITLE></HEAD>
|
359
|
+
<BODY>
|
360
|
+
<H1>Index of #{HTMLUtils::escape(req.path)}</H1>
|
361
|
+
_end_of_html_
|
362
|
+
|
363
|
+
res.body << "<PRE>\n"
|
364
|
+
res.body << " <A HREF=\"?N=#{d1}\">Name</A> "
|
365
|
+
res.body << "<A HREF=\"?M=#{d1}\">Last modified</A> "
|
366
|
+
res.body << "<A HREF=\"?S=#{d1}\">Size</A>\n"
|
367
|
+
res.body << "<HR>\n"
|
368
|
+
|
369
|
+
list.unshift [ "..", File::mtime(local_path+".."), -1 ]
|
370
|
+
list.each{ |name, time, size|
|
371
|
+
if name == ".."
|
372
|
+
dname = "Parent Directory"
|
373
|
+
elsif name.size > 25
|
374
|
+
dname = name.sub(/^(.{23})(.*)/){ $1 + ".." }
|
375
|
+
else
|
376
|
+
dname = name
|
377
|
+
end
|
378
|
+
s = " <A HREF=\"#{HTTPUtils::escape(name)}\">#{dname}</A>"
|
379
|
+
s << " " * (30 - dname.size)
|
380
|
+
s << (time ? time.strftime("%Y/%m/%d %H:%M ") : " " * 22)
|
381
|
+
s << (size >= 0 ? size.to_s : "-") << "\n"
|
382
|
+
res.body << s
|
383
|
+
}
|
384
|
+
res.body << "</PRE><HR>"
|
385
|
+
|
386
|
+
res.body << <<-_end_of_html_
|
387
|
+
<ADDRESS>
|
388
|
+
#{HTMLUtils::escape(@config[:ServerSoftware])}<BR>
|
389
|
+
at #{req.host}:#{req.port}
|
390
|
+
</ADDRESS>
|
391
|
+
</BODY>
|
392
|
+
</HTML>
|
393
|
+
_end_of_html_
|
394
|
+
end
|
395
|
+
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|