rubysl-webrick 1.0.0 → 2.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 +14 -6
- data/.travis.yml +5 -6
- data/lib/rubysl/webrick/version.rb +1 -1
- data/lib/rubysl/webrick/webrick.rb +199 -2
- data/lib/webrick/accesslog.rb +96 -5
- data/lib/webrick/cgi.rb +80 -29
- data/lib/webrick/compat.rb +20 -0
- data/lib/webrick/config.rb +59 -5
- data/lib/webrick/cookie.rb +66 -5
- data/lib/webrick/htmlutils.rb +4 -1
- data/lib/webrick/httpauth.rb +53 -3
- data/lib/webrick/httpauth/authenticator.rb +53 -16
- data/lib/webrick/httpauth/basicauth.rb +45 -2
- data/lib/webrick/httpauth/digestauth.rb +82 -17
- data/lib/webrick/httpauth/htdigest.rb +38 -1
- data/lib/webrick/httpauth/htgroup.rb +32 -0
- data/lib/webrick/httpauth/htpasswd.rb +40 -2
- data/lib/webrick/httpauth/userdb.rb +27 -4
- data/lib/webrick/httpproxy.rb +197 -112
- data/lib/webrick/httprequest.rb +268 -50
- data/lib/webrick/httpresponse.rb +170 -33
- data/lib/webrick/https.rb +26 -3
- data/lib/webrick/httpserver.rb +75 -7
- data/lib/webrick/httpservlet/abstract.rb +88 -6
- data/lib/webrick/httpservlet/cgi_runner.rb +5 -4
- data/lib/webrick/httpservlet/cgihandler.rb +37 -18
- data/lib/webrick/httpservlet/erbhandler.rb +40 -7
- data/lib/webrick/httpservlet/filehandler.rb +116 -28
- data/lib/webrick/httpservlet/prochandler.rb +17 -4
- data/lib/webrick/httpstatus.rb +86 -18
- data/lib/webrick/httputils.rb +131 -23
- data/lib/webrick/httpversion.rb +28 -2
- data/lib/webrick/log.rb +72 -5
- data/lib/webrick/server.rb +158 -33
- data/lib/webrick/ssl.rb +78 -9
- data/lib/webrick/utils.rb +151 -5
- data/lib/webrick/version.rb +5 -1
- data/rubysl-webrick.gemspec +0 -1
- metadata +12 -24
@@ -18,17 +18,88 @@ module WEBrick
|
|
18
18
|
module HTTPServlet
|
19
19
|
class HTTPServletError < StandardError; end
|
20
20
|
|
21
|
+
##
|
22
|
+
# AbstractServlet allows HTTP server modules to be reused across multiple
|
23
|
+
# servers and allows encapsulation of functionality.
|
24
|
+
#
|
25
|
+
# By default a servlet will respond to GET, HEAD (through an alias to GET)
|
26
|
+
# and OPTIONS requests.
|
27
|
+
#
|
28
|
+
# By default a new servlet is initialized for every request. A servlet
|
29
|
+
# instance can be reused by overriding ::get_instance in the
|
30
|
+
# AbstractServlet subclass.
|
31
|
+
#
|
32
|
+
# == A Simple Servlet
|
33
|
+
#
|
34
|
+
# class Simple < WEBrick::HTTPServlet::AbstractServlet
|
35
|
+
# def do_GET request, response
|
36
|
+
# status, content_type, body = do_stuff_with request
|
37
|
+
#
|
38
|
+
# response.status = status
|
39
|
+
# response['Content-Type'] = content_type
|
40
|
+
# response.body = body
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# def do_stuff_with request
|
44
|
+
# return 200, 'text/plain', 'you got a page'
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# This servlet can be mounted on a server at a given path:
|
49
|
+
#
|
50
|
+
# server.mount '/simple', Simple
|
51
|
+
#
|
52
|
+
# == Servlet Configuration
|
53
|
+
#
|
54
|
+
# Servlets can be configured via initialize. The first argument is the
|
55
|
+
# HTTP server the servlet is being initialized for.
|
56
|
+
#
|
57
|
+
# class Configurable < Simple
|
58
|
+
# def initialize server, color, size
|
59
|
+
# super server
|
60
|
+
# @color = color
|
61
|
+
# @size = size
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# def do_stuff_with request
|
65
|
+
# content = "<p " \
|
66
|
+
# %q{style="color: #{@color}; font-size: #{@size}"} \
|
67
|
+
# ">Hello, World!"
|
68
|
+
#
|
69
|
+
# return 200, "text/html", content
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# This servlet must be provided two arguments at mount time:
|
74
|
+
#
|
75
|
+
# server.mount '/configurable', Configurable, 'red', '2em'
|
76
|
+
|
21
77
|
class AbstractServlet
|
22
|
-
|
23
|
-
|
78
|
+
|
79
|
+
##
|
80
|
+
# Factory for servlet instances that will handle a request from +server+
|
81
|
+
# using +options+ from the mount point. By default a new servlet
|
82
|
+
# instance is created for every call.
|
83
|
+
|
84
|
+
def self.get_instance(server, *options)
|
85
|
+
self.new(server, *options)
|
24
86
|
end
|
25
87
|
|
88
|
+
##
|
89
|
+
# Initializes a new servlet for +server+ using +options+ which are
|
90
|
+
# stored as-is in +@options+. +@logger+ is also provided.
|
91
|
+
|
26
92
|
def initialize(server, *options)
|
27
93
|
@server = @config = server
|
28
94
|
@logger = @server[:Logger]
|
29
95
|
@options = options
|
30
96
|
end
|
31
97
|
|
98
|
+
##
|
99
|
+
# Dispatches to a +do_+ method based on +req+ if such a method is
|
100
|
+
# available. (+do_GET+ for a GET request). Raises a MethodNotAllowed
|
101
|
+
# exception if the method is not implemented.
|
102
|
+
|
32
103
|
def service(req, res)
|
33
104
|
method_name = "do_" + req.request_method.gsub(/-/, "_")
|
34
105
|
if respond_to?(method_name)
|
@@ -39,27 +110,38 @@ module WEBrick
|
|
39
110
|
end
|
40
111
|
end
|
41
112
|
|
113
|
+
##
|
114
|
+
# Raises a NotFound exception
|
115
|
+
|
42
116
|
def do_GET(req, res)
|
43
117
|
raise HTTPStatus::NotFound, "not found."
|
44
118
|
end
|
45
119
|
|
120
|
+
##
|
121
|
+
# Dispatches to do_GET
|
122
|
+
|
46
123
|
def do_HEAD(req, res)
|
47
124
|
do_GET(req, res)
|
48
125
|
end
|
49
126
|
|
127
|
+
##
|
128
|
+
# Returns the allowed HTTP request methods
|
129
|
+
|
50
130
|
def do_OPTIONS(req, res)
|
51
|
-
m = self.methods.grep(
|
52
|
-
m.collect!{|i| i.sub(/do_/, "") }
|
131
|
+
m = self.methods.grep(/\Ado_([A-Z]+)\z/) {$1}
|
53
132
|
m.sort!
|
54
133
|
res["allow"] = m.join(",")
|
55
134
|
end
|
56
135
|
|
57
136
|
private
|
58
137
|
|
138
|
+
##
|
139
|
+
# Redirects to a path ending in /
|
140
|
+
|
59
141
|
def redirect_to_directory_uri(req, res)
|
60
142
|
if req.path[-1] != ?/
|
61
|
-
location = req.path + "/"
|
62
|
-
if req.query_string && req.query_string.
|
143
|
+
location = WEBrick::HTTPUtils.escape_path(req.path + "/")
|
144
|
+
if req.query_string && req.query_string.bytesize > 0
|
63
145
|
location << "?" << req.query_string
|
64
146
|
end
|
65
147
|
res.set_redirect(HTTPStatus::MovedPermanently, location)
|
@@ -13,14 +13,13 @@ def sysread(io, size)
|
|
13
13
|
while size > 0
|
14
14
|
tmp = io.sysread(size)
|
15
15
|
buf << tmp
|
16
|
-
size -= tmp.
|
16
|
+
size -= tmp.bytesize
|
17
17
|
end
|
18
18
|
return buf
|
19
19
|
end
|
20
20
|
|
21
21
|
STDIN.binmode
|
22
22
|
|
23
|
-
buf = ""
|
24
23
|
len = sysread(STDIN, 8).to_i
|
25
24
|
out = sysread(STDIN, len)
|
26
25
|
STDOUT.reopen(open(out, "w"))
|
@@ -38,8 +37,10 @@ hash.each{|k, v| ENV[k] = v if v }
|
|
38
37
|
dir = File::dirname(ENV["SCRIPT_FILENAME"])
|
39
38
|
Dir::chdir dir
|
40
39
|
|
41
|
-
if
|
42
|
-
|
40
|
+
if ARGV[0]
|
41
|
+
argv = ARGV.dup
|
42
|
+
argv << ENV["SCRIPT_FILENAME"]
|
43
|
+
exec(*argv)
|
43
44
|
# NOTREACHED
|
44
45
|
end
|
45
46
|
exec ENV["SCRIPT_FILENAME"]
|
@@ -1,11 +1,11 @@
|
|
1
|
-
#
|
1
|
+
#
|
2
2
|
# cgihandler.rb -- CGIHandler Class
|
3
|
-
#
|
3
|
+
#
|
4
4
|
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
5
|
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
6
6
|
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
7
7
|
# reserved.
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# $IPR: cgihandler.rb,v 1.27 2003/03/21 19:56:01 gotoyuzo Exp $
|
10
10
|
|
11
11
|
require 'rbconfig'
|
@@ -16,26 +16,39 @@ require 'webrick/httpservlet/abstract'
|
|
16
16
|
module WEBrick
|
17
17
|
module HTTPServlet
|
18
18
|
|
19
|
+
##
|
20
|
+
# Servlet for handling CGI scripts
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
#
|
24
|
+
# server.mount('/cgi/my_script', WEBrick::HTTPServlet::CGIHandler,
|
25
|
+
# '/path/to/my_script')
|
26
|
+
|
19
27
|
class CGIHandler < AbstractServlet
|
20
|
-
Ruby =
|
21
|
-
|
22
|
-
|
23
|
-
|
28
|
+
Ruby = RbConfig.ruby # :nodoc:
|
29
|
+
CGIRunner = "\"#{Ruby}\" \"#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb\"" # :nodoc:
|
30
|
+
|
31
|
+
##
|
32
|
+
# Creates a new CGI script servlet for the script at +name+
|
24
33
|
|
25
34
|
def initialize(server, name)
|
26
|
-
super
|
35
|
+
super(server, name)
|
27
36
|
@script_filename = name
|
28
37
|
@tempdir = server[:TempDir]
|
29
38
|
@cgicmd = "#{CGIRunner} #{server[:CGIInterpreter]}"
|
30
39
|
end
|
31
40
|
|
41
|
+
# :stopdoc:
|
42
|
+
|
32
43
|
def do_GET(req, res)
|
33
44
|
data = nil
|
34
45
|
status = -1
|
35
46
|
|
36
47
|
cgi_in = IO::popen(@cgicmd, "wb")
|
37
|
-
cgi_out = Tempfile.new("webrick.cgiout.", @tempdir)
|
38
|
-
|
48
|
+
cgi_out = Tempfile.new("webrick.cgiout.", @tempdir, mode: IO::BINARY)
|
49
|
+
cgi_out.set_encoding("ASCII-8BIT")
|
50
|
+
cgi_err = Tempfile.new("webrick.cgierr.", @tempdir, mode: IO::BINARY)
|
51
|
+
cgi_err.set_encoding("ASCII-8BIT")
|
39
52
|
begin
|
40
53
|
cgi_in.sync = true
|
41
54
|
meta = req.meta_vars
|
@@ -46,14 +59,14 @@ module WEBrick
|
|
46
59
|
end
|
47
60
|
dump = Marshal.dump(meta)
|
48
61
|
|
49
|
-
cgi_in.write("%8d" % cgi_out.path.
|
62
|
+
cgi_in.write("%8d" % cgi_out.path.bytesize)
|
50
63
|
cgi_in.write(cgi_out.path)
|
51
|
-
cgi_in.write("%8d" % cgi_err.path.
|
64
|
+
cgi_in.write("%8d" % cgi_err.path.bytesize)
|
52
65
|
cgi_in.write(cgi_err.path)
|
53
|
-
cgi_in.write("%8d" % dump.
|
66
|
+
cgi_in.write("%8d" % dump.bytesize)
|
54
67
|
cgi_in.write(dump)
|
55
68
|
|
56
|
-
if req.body and req.body.
|
69
|
+
if req.body and req.body.bytesize > 0
|
57
70
|
cgi_in.write(req.body)
|
58
71
|
end
|
59
72
|
ensure
|
@@ -63,19 +76,19 @@ module WEBrick
|
|
63
76
|
data = cgi_out.read
|
64
77
|
cgi_out.close(true)
|
65
78
|
if errmsg = cgi_err.read
|
66
|
-
if errmsg.
|
79
|
+
if errmsg.bytesize > 0
|
67
80
|
@logger.error("CGIHandler: #{@script_filename}:\n" + errmsg)
|
68
81
|
end
|
69
|
-
end
|
82
|
+
end
|
70
83
|
cgi_err.close(true)
|
71
84
|
end
|
72
|
-
|
85
|
+
|
73
86
|
if status != 0
|
74
87
|
@logger.error("CGIHandler: #{@script_filename} exit with #{status}")
|
75
88
|
end
|
76
89
|
|
77
90
|
data = "" unless data
|
78
|
-
raw_header, body = data.split(/^[\xd\xa]
|
91
|
+
raw_header, body = data.split(/^[\xd\xa]+/, 2)
|
79
92
|
raise HTTPStatus::InternalServerError,
|
80
93
|
"Premature end of script headers: #{@script_filename}" if body.nil?
|
81
94
|
|
@@ -85,6 +98,10 @@ module WEBrick
|
|
85
98
|
res.status = $1.to_i
|
86
99
|
header.delete('status')
|
87
100
|
end
|
101
|
+
if header.has_key?('location')
|
102
|
+
# RFC 3875 6.2.3, 6.2.4
|
103
|
+
res.status = 302 unless (300...400) === res.status
|
104
|
+
end
|
88
105
|
if header.has_key?('set-cookie')
|
89
106
|
header['set-cookie'].each{|k|
|
90
107
|
res.cookies << Cookie.parse_set_cookie(k)
|
@@ -98,6 +115,8 @@ module WEBrick
|
|
98
115
|
res.body = body
|
99
116
|
end
|
100
117
|
alias do_POST do_GET
|
118
|
+
|
119
|
+
# :startdoc:
|
101
120
|
end
|
102
121
|
|
103
122
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
#
|
1
|
+
#
|
2
2
|
# erbhandler.rb -- ERBHandler Class
|
3
|
-
#
|
3
|
+
#
|
4
4
|
# Author: IPR -- Internet Programming with Ruby -- writers
|
5
5
|
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
6
6
|
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
7
7
|
# reserved.
|
8
|
-
#
|
8
|
+
#
|
9
9
|
# $IPR: erbhandler.rb,v 1.25 2003/02/24 19:25:31 gotoyuzo Exp $
|
10
10
|
|
11
11
|
require 'webrick/httpservlet/abstract.rb'
|
@@ -15,12 +15,37 @@ require 'erb'
|
|
15
15
|
module WEBrick
|
16
16
|
module HTTPServlet
|
17
17
|
|
18
|
+
##
|
19
|
+
# ERBHandler evaluates an ERB file and returns the result. This handler
|
20
|
+
# is automatically used if there are .rhtml files in a directory served by
|
21
|
+
# the FileHandler.
|
22
|
+
#
|
23
|
+
# ERBHandler supports GET and POST methods.
|
24
|
+
#
|
25
|
+
# The ERB file is evaluated with the local variables +servlet_request+ and
|
26
|
+
# +servlet_response+ which are a WEBrick::HTTPRequest and
|
27
|
+
# WEBrick::HTTPResponse respectively.
|
28
|
+
#
|
29
|
+
# Example .rhtml file:
|
30
|
+
#
|
31
|
+
# Request to <%= servlet_request.request_uri %>
|
32
|
+
#
|
33
|
+
# Query params <%= servlet_request.query.inspect %>
|
34
|
+
|
18
35
|
class ERBHandler < AbstractServlet
|
36
|
+
|
37
|
+
##
|
38
|
+
# Creates a new ERBHandler on +server+ that will evaluate and serve the
|
39
|
+
# ERB file +name+
|
40
|
+
|
19
41
|
def initialize(server, name)
|
20
|
-
super
|
42
|
+
super(server, name)
|
21
43
|
@script_filename = name
|
22
44
|
end
|
23
45
|
|
46
|
+
##
|
47
|
+
# Handles GET requests
|
48
|
+
|
24
49
|
def do_GET(req, res)
|
25
50
|
unless defined?(ERB)
|
26
51
|
@logger.warn "#{self.class}: ERB not defined."
|
@@ -29,7 +54,7 @@ module WEBrick
|
|
29
54
|
begin
|
30
55
|
data = open(@script_filename){|io| io.read }
|
31
56
|
res.body = evaluate(ERB.new(data), req, res)
|
32
|
-
res['content-type']
|
57
|
+
res['content-type'] ||=
|
33
58
|
HTTPUtils::mime_type(@script_filename, @config[:MimeTypes])
|
34
59
|
rescue StandardError => ex
|
35
60
|
raise
|
@@ -39,13 +64,21 @@ module WEBrick
|
|
39
64
|
end
|
40
65
|
end
|
41
66
|
|
67
|
+
##
|
68
|
+
# Handles POST requests
|
69
|
+
|
42
70
|
alias do_POST do_GET
|
43
71
|
|
44
72
|
private
|
73
|
+
|
74
|
+
##
|
75
|
+
# Evaluates +erb+ providing +servlet_request+ and +servlet_response+ as
|
76
|
+
# local variables.
|
77
|
+
|
45
78
|
def evaluate(erb, servlet_request, servlet_response)
|
46
79
|
Module.new.module_eval{
|
47
|
-
|
48
|
-
|
80
|
+
servlet_request.meta_vars
|
81
|
+
servlet_request.query
|
49
82
|
erb.result(binding)
|
50
83
|
}
|
51
84
|
end
|
@@ -18,12 +18,29 @@ require 'webrick/httpstatus'
|
|
18
18
|
module WEBrick
|
19
19
|
module HTTPServlet
|
20
20
|
|
21
|
+
##
|
22
|
+
# Servlet for serving a single file. You probably want to use the
|
23
|
+
# FileHandler servlet instead as it handles directories and fancy indexes.
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
#
|
27
|
+
# server.mount('/my_page.txt', WEBrick::HTTPServlet::DefaultFileHandler,
|
28
|
+
# '/path/to/my_page.txt')
|
29
|
+
#
|
30
|
+
# This servlet handles If-Modified-Since and Range requests.
|
31
|
+
|
21
32
|
class DefaultFileHandler < AbstractServlet
|
33
|
+
|
34
|
+
##
|
35
|
+
# Creates a DefaultFileHandler instance for the file at +local_path+.
|
36
|
+
|
22
37
|
def initialize(server, local_path)
|
23
|
-
super
|
38
|
+
super(server, local_path)
|
24
39
|
@local_path = local_path
|
25
40
|
end
|
26
41
|
|
42
|
+
# :stopdoc:
|
43
|
+
|
27
44
|
def do_GET(req, res)
|
28
45
|
st = File::stat(@local_path)
|
29
46
|
mtime = st.mtime
|
@@ -32,7 +49,7 @@ module WEBrick
|
|
32
49
|
if not_modified?(req, res, mtime, res['etag'])
|
33
50
|
res.body = ''
|
34
51
|
raise HTTPStatus::NotModified
|
35
|
-
elsif req['range']
|
52
|
+
elsif req['range']
|
36
53
|
make_partial_content(req, res, @local_path, st.size)
|
37
54
|
raise HTTPStatus::PartialContent
|
38
55
|
else
|
@@ -87,7 +104,7 @@ module WEBrick
|
|
87
104
|
content = io.read(last-first+1)
|
88
105
|
body << "--" << boundary << CRLF
|
89
106
|
body << "Content-Type: #{mtype}" << CRLF
|
90
|
-
body << "Content-Range: #{first}-#{last}/#{filesize}" << CRLF
|
107
|
+
body << "Content-Range: bytes #{first}-#{last}/#{filesize}" << CRLF
|
91
108
|
body << CRLF
|
92
109
|
body << content
|
93
110
|
body << CRLF
|
@@ -107,7 +124,7 @@ module WEBrick
|
|
107
124
|
content = io.read(last-first+1)
|
108
125
|
end
|
109
126
|
res['content-type'] = mtype
|
110
|
-
res['content-range'] = "#{first}-#{last}/#{filesize}"
|
127
|
+
res['content-range'] = "bytes #{first}-#{last}/#{filesize}"
|
111
128
|
res['content-length'] = last - first + 1
|
112
129
|
res.body = content
|
113
130
|
else
|
@@ -123,19 +140,46 @@ module WEBrick
|
|
123
140
|
last = filesize - 1 if last >= filesize
|
124
141
|
return first, last
|
125
142
|
end
|
143
|
+
|
144
|
+
# :startdoc:
|
126
145
|
end
|
127
146
|
|
147
|
+
##
|
148
|
+
# Serves a directory including fancy indexing and a variety of other
|
149
|
+
# options.
|
150
|
+
#
|
151
|
+
# Example:
|
152
|
+
#
|
153
|
+
# server.mount '/assets', WEBrick::FileHandler, '/path/to/assets'
|
154
|
+
|
128
155
|
class FileHandler < AbstractServlet
|
129
|
-
HandlerTable = Hash.new
|
156
|
+
HandlerTable = Hash.new # :nodoc:
|
157
|
+
|
158
|
+
##
|
159
|
+
# Allow custom handling of requests for files with +suffix+ by class
|
160
|
+
# +handler+
|
130
161
|
|
131
162
|
def self.add_handler(suffix, handler)
|
132
163
|
HandlerTable[suffix] = handler
|
133
164
|
end
|
134
165
|
|
166
|
+
##
|
167
|
+
# Remove custom handling of requests for files with +suffix+
|
168
|
+
|
135
169
|
def self.remove_handler(suffix)
|
136
170
|
HandlerTable.delete(suffix)
|
137
171
|
end
|
138
172
|
|
173
|
+
##
|
174
|
+
# Creates a FileHandler servlet on +server+ that serves files starting
|
175
|
+
# at directory +root+
|
176
|
+
#
|
177
|
+
# +options+ may be a Hash containing keys from
|
178
|
+
# WEBrick::Config::FileHandler or +true+ or +false+.
|
179
|
+
#
|
180
|
+
# If +options+ is true or false then +:FancyIndexing+ is enabled or
|
181
|
+
# disabled respectively.
|
182
|
+
|
139
183
|
def initialize(server, root, options={}, default=Config::FileHandler)
|
140
184
|
@config = server.config
|
141
185
|
@logger = @config[:Logger]
|
@@ -146,6 +190,8 @@ module WEBrick
|
|
146
190
|
@options = default.dup.update(options)
|
147
191
|
end
|
148
192
|
|
193
|
+
# :stopdoc:
|
194
|
+
|
149
195
|
def service(req, res)
|
150
196
|
# if this class is mounted on "/" and /~username is requested.
|
151
197
|
# we're going to override path informations before invoking service.
|
@@ -163,6 +209,7 @@ module WEBrick
|
|
163
209
|
end
|
164
210
|
end
|
165
211
|
end
|
212
|
+
prevent_directory_traversal(req, res)
|
166
213
|
super(req, res)
|
167
214
|
end
|
168
215
|
|
@@ -198,10 +245,42 @@ module WEBrick
|
|
198
245
|
|
199
246
|
private
|
200
247
|
|
248
|
+
def trailing_pathsep?(path)
|
249
|
+
# check for trailing path separator:
|
250
|
+
# File.dirname("/aaaa/bbbb/") #=> "/aaaa")
|
251
|
+
# File.dirname("/aaaa/bbbb/x") #=> "/aaaa/bbbb")
|
252
|
+
# File.dirname("/aaaa/bbbb") #=> "/aaaa")
|
253
|
+
# File.dirname("/aaaa/bbbbx") #=> "/aaaa")
|
254
|
+
return File.dirname(path) != File.dirname(path+"x")
|
255
|
+
end
|
256
|
+
|
257
|
+
def prevent_directory_traversal(req, res)
|
258
|
+
# Preventing directory traversal on Windows platforms;
|
259
|
+
# Backslashes (0x5c) in path_info are not interpreted as special
|
260
|
+
# character in URI notation. So the value of path_info should be
|
261
|
+
# normalize before accessing to the filesystem.
|
262
|
+
|
263
|
+
# dirty hack for filesystem encoding; in nature, File.expand_path
|
264
|
+
# should not be used for path normalization. [Bug #3345]
|
265
|
+
path = req.path_info.dup.force_encoding(Encoding.find("filesystem"))
|
266
|
+
if trailing_pathsep?(req.path_info)
|
267
|
+
# File.expand_path removes the trailing path separator.
|
268
|
+
# Adding a character is a workaround to save it.
|
269
|
+
# File.expand_path("/aaa/") #=> "/aaa"
|
270
|
+
# File.expand_path("/aaa/" + "x") #=> "/aaa/x"
|
271
|
+
expanded = File.expand_path(path + "x")
|
272
|
+
expanded.chop! # remove trailing "x"
|
273
|
+
else
|
274
|
+
expanded = File.expand_path(path)
|
275
|
+
end
|
276
|
+
expanded.force_encoding(req.path_info.encoding)
|
277
|
+
req.path_info = expanded
|
278
|
+
end
|
279
|
+
|
201
280
|
def exec_handler(req, res)
|
202
281
|
raise HTTPStatus::NotFound, "`#{req.path}' not found" unless @root
|
203
282
|
if set_filename(req, res)
|
204
|
-
handler = get_handler(req)
|
283
|
+
handler = get_handler(req, res)
|
205
284
|
call_callback(:HandlerCallback, req, res)
|
206
285
|
h = handler.get_instance(@config, res.filename)
|
207
286
|
h.service(req, res)
|
@@ -211,9 +290,13 @@ module WEBrick
|
|
211
290
|
return false
|
212
291
|
end
|
213
292
|
|
214
|
-
def get_handler(req)
|
215
|
-
suffix1 = (/\.(\w+)
|
216
|
-
|
293
|
+
def get_handler(req, res)
|
294
|
+
suffix1 = (/\.(\w+)\z/ =~ res.filename) && $1.downcase
|
295
|
+
if /\.(\w+)\.([\w\-]+)\z/ =~ res.filename
|
296
|
+
if @options[:AcceptableLanguages].include?($2.downcase)
|
297
|
+
suffix2 = $1.downcase
|
298
|
+
end
|
299
|
+
end
|
217
300
|
handler_table = @options[:HandlerTable]
|
218
301
|
return handler_table[suffix1] || handler_table[suffix2] ||
|
219
302
|
HandlerTable[suffix1] || HandlerTable[suffix2] ||
|
@@ -226,15 +309,13 @@ module WEBrick
|
|
226
309
|
|
227
310
|
path_info.unshift("") # dummy for checking @root dir
|
228
311
|
while base = path_info.first
|
229
|
-
check_filename(req, res, base)
|
230
312
|
break if base == "/"
|
231
|
-
break unless File.directory?(res.filename + base)
|
313
|
+
break unless File.directory?(File.expand_path(res.filename + base))
|
232
314
|
shift_path_info(req, res, path_info)
|
233
315
|
call_callback(:DirectoryCallback, req, res)
|
234
316
|
end
|
235
317
|
|
236
318
|
if base = path_info.first
|
237
|
-
check_filename(req, res, base)
|
238
319
|
if base == "/"
|
239
320
|
if file = search_index_file(req, res)
|
240
321
|
shift_path_info(req, res, path_info, file)
|
@@ -255,12 +336,10 @@ module WEBrick
|
|
255
336
|
end
|
256
337
|
|
257
338
|
def check_filename(req, res, name)
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
end
|
263
|
-
}
|
339
|
+
if nondisclosure_name?(name) || windows_ambiguous_name?(name)
|
340
|
+
@logger.warn("the request refers nondisclosure name `#{name}'.")
|
341
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
342
|
+
end
|
264
343
|
end
|
265
344
|
|
266
345
|
def shift_path_info(req, res, path_info, base=nil)
|
@@ -268,7 +347,8 @@ module WEBrick
|
|
268
347
|
base = base || tmp
|
269
348
|
req.path_info = path_info.join
|
270
349
|
req.script_name << base
|
271
|
-
res.filename
|
350
|
+
res.filename = File.expand_path(res.filename + base)
|
351
|
+
check_filename(req, res, File.basename(res.filename))
|
272
352
|
end
|
273
353
|
|
274
354
|
def search_index_file(req, res)
|
@@ -308,9 +388,15 @@ module WEBrick
|
|
308
388
|
end
|
309
389
|
end
|
310
390
|
|
391
|
+
def windows_ambiguous_name?(name)
|
392
|
+
return true if /[. ]+\z/ =~ name
|
393
|
+
return true if /::\$DATA\z/ =~ name
|
394
|
+
return false
|
395
|
+
end
|
396
|
+
|
311
397
|
def nondisclosure_name?(name)
|
312
398
|
@options[:NondisclosureName].each{|pattern|
|
313
|
-
if File.fnmatch(pattern, name)
|
399
|
+
if File.fnmatch(pattern, name, File::FNM_CASEFOLD)
|
314
400
|
return true
|
315
401
|
end
|
316
402
|
}
|
@@ -326,7 +412,8 @@ module WEBrick
|
|
326
412
|
list = Dir::entries(local_path).collect{|name|
|
327
413
|
next if name == "." || name == ".."
|
328
414
|
next if nondisclosure_name?(name)
|
329
|
-
|
415
|
+
next if windows_ambiguous_name?(name)
|
416
|
+
st = (File::stat(File.join(local_path, name)) rescue nil)
|
330
417
|
if st.nil?
|
331
418
|
[ name, nil, -1 ]
|
332
419
|
elsif st.directory?
|
@@ -365,25 +452,25 @@ module WEBrick
|
|
365
452
|
res.body << "<A HREF=\"?M=#{d1}\">Last modified</A> "
|
366
453
|
res.body << "<A HREF=\"?S=#{d1}\">Size</A>\n"
|
367
454
|
res.body << "<HR>\n"
|
368
|
-
|
369
|
-
list.unshift [ "..", File::mtime(local_path+"
|
455
|
+
|
456
|
+
list.unshift [ "..", File::mtime(local_path+"/.."), -1 ]
|
370
457
|
list.each{ |name, time, size|
|
371
458
|
if name == ".."
|
372
459
|
dname = "Parent Directory"
|
373
|
-
elsif name.
|
374
|
-
dname = name.sub(/^(.{23})(
|
460
|
+
elsif name.bytesize > 25
|
461
|
+
dname = name.sub(/^(.{23})(?:.*)/, '\1..')
|
375
462
|
else
|
376
463
|
dname = name
|
377
464
|
end
|
378
|
-
s = " <A HREF=\"#{HTTPUtils::escape(name)}\">#{dname}</A>"
|
379
|
-
s << " " * (30 - dname.
|
465
|
+
s = " <A HREF=\"#{HTTPUtils::escape(name)}\">#{HTMLUtils::escape(dname)}</A>"
|
466
|
+
s << " " * (30 - dname.bytesize)
|
380
467
|
s << (time ? time.strftime("%Y/%m/%d %H:%M ") : " " * 22)
|
381
468
|
s << (size >= 0 ? size.to_s : "-") << "\n"
|
382
469
|
res.body << s
|
383
470
|
}
|
384
471
|
res.body << "</PRE><HR>"
|
385
472
|
|
386
|
-
res.body << <<-_end_of_html_
|
473
|
+
res.body << <<-_end_of_html_
|
387
474
|
<ADDRESS>
|
388
475
|
#{HTMLUtils::escape(@config[:ServerSoftware])}<BR>
|
389
476
|
at #{req.host}:#{req.port}
|
@@ -393,6 +480,7 @@ module WEBrick
|
|
393
480
|
_end_of_html_
|
394
481
|
end
|
395
482
|
|
483
|
+
# :startdoc:
|
396
484
|
end
|
397
485
|
end
|
398
486
|
end
|