webrick 1.3.1 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/webrick.rb +7 -7
- data/lib/webrick/accesslog.rb +12 -6
- data/lib/webrick/cgi.rb +58 -5
- data/lib/webrick/compat.rb +2 -1
- data/lib/webrick/config.rb +47 -10
- data/lib/webrick/cookie.rb +69 -7
- data/lib/webrick/htmlutils.rb +4 -2
- data/lib/webrick/httpauth.rb +6 -5
- data/lib/webrick/httpauth/authenticator.rb +13 -8
- data/lib/webrick/httpauth/basicauth.rb +16 -8
- data/lib/webrick/httpauth/digestauth.rb +35 -32
- data/lib/webrick/httpauth/htdigest.rb +12 -8
- data/lib/webrick/httpauth/htgroup.rb +10 -6
- data/lib/webrick/httpauth/htpasswd.rb +46 -9
- data/lib/webrick/httpauth/userdb.rb +1 -0
- data/lib/webrick/httpproxy.rb +93 -48
- data/lib/webrick/httprequest.rb +201 -31
- data/lib/webrick/httpresponse.rb +235 -70
- data/lib/webrick/https.rb +90 -2
- data/lib/webrick/httpserver.rb +45 -15
- data/lib/webrick/httpservlet.rb +6 -5
- data/lib/webrick/httpservlet/abstract.rb +5 -6
- data/lib/webrick/httpservlet/cgi_runner.rb +3 -2
- data/lib/webrick/httpservlet/cgihandler.rb +29 -11
- data/lib/webrick/httpservlet/erbhandler.rb +4 -3
- data/lib/webrick/httpservlet/filehandler.rb +136 -65
- data/lib/webrick/httpservlet/prochandler.rb +15 -1
- data/lib/webrick/httpstatus.rb +24 -14
- data/lib/webrick/httputils.rb +134 -17
- data/lib/webrick/httpversion.rb +28 -1
- data/lib/webrick/log.rb +25 -5
- data/lib/webrick/server.rb +234 -74
- data/lib/webrick/ssl.rb +100 -12
- data/lib/webrick/utils.rb +98 -69
- data/lib/webrick/version.rb +6 -1
- data/webrick.gemspec +76 -0
- metadata +73 -72
- data/README.txt +0 -21
- data/sample/webrick/demo-app.rb +0 -66
- data/sample/webrick/demo-multipart.cgi +0 -12
- data/sample/webrick/demo-servlet.rb +0 -6
- data/sample/webrick/demo-urlencoded.cgi +0 -12
- data/sample/webrick/hello.cgi +0 -11
- data/sample/webrick/hello.rb +0 -8
- data/sample/webrick/httpd.rb +0 -23
- data/sample/webrick/httpproxy.rb +0 -25
- data/sample/webrick/httpsd.rb +0 -33
- data/test/openssl/utils.rb +0 -313
- data/test/ruby/envutil.rb +0 -208
- data/test/webrick/test_cgi.rb +0 -134
- data/test/webrick/test_cookie.rb +0 -131
- data/test/webrick/test_filehandler.rb +0 -285
- data/test/webrick/test_httpauth.rb +0 -167
- data/test/webrick/test_httpproxy.rb +0 -282
- data/test/webrick/test_httprequest.rb +0 -411
- data/test/webrick/test_httpresponse.rb +0 -49
- data/test/webrick/test_httpserver.rb +0 -305
- data/test/webrick/test_httputils.rb +0 -96
- data/test/webrick/test_httpversion.rb +0 -40
- data/test/webrick/test_server.rb +0 -67
- data/test/webrick/test_utils.rb +0 -64
- data/test/webrick/utils.rb +0 -58
- data/test/webrick/webrick.cgi +0 -36
- data/test/webrick/webrick_long_filename.cgi +0 -36
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# httpauth/htgroup.rb -- Apache compatible htgroup file
|
3
4
|
#
|
@@ -35,7 +36,7 @@ module WEBrick
|
|
35
36
|
@path = path
|
36
37
|
@mtime = Time.at(0)
|
37
38
|
@group = Hash.new
|
38
|
-
open(@path,"a").close unless File
|
39
|
+
File.open(@path,"a").close unless File.exist?(@path)
|
39
40
|
reload
|
40
41
|
end
|
41
42
|
|
@@ -45,7 +46,7 @@ module WEBrick
|
|
45
46
|
def reload
|
46
47
|
if (mtime = File::mtime(@path)) > @mtime
|
47
48
|
@group.clear
|
48
|
-
open(@path){|io|
|
49
|
+
File.open(@path){|io|
|
49
50
|
while line = io.gets
|
50
51
|
line.chomp!
|
51
52
|
group, members = line.split(/:\s*/)
|
@@ -62,15 +63,18 @@ module WEBrick
|
|
62
63
|
|
63
64
|
def flush(output=nil)
|
64
65
|
output ||= @path
|
65
|
-
tmp = Tempfile.
|
66
|
+
tmp = Tempfile.create("htgroup", File::dirname(output))
|
66
67
|
begin
|
67
68
|
@group.keys.sort.each{|group|
|
68
69
|
tmp.puts(format("%s: %s", group, self.members(group).join(" ")))
|
69
70
|
}
|
71
|
+
ensure
|
70
72
|
tmp.close
|
71
|
-
|
72
|
-
|
73
|
-
|
73
|
+
if $!
|
74
|
+
File.unlink(tmp.path)
|
75
|
+
else
|
76
|
+
return File.rename(tmp.path, output)
|
77
|
+
end
|
74
78
|
end
|
75
79
|
end
|
76
80
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# httpauth/htpasswd -- Apache compatible htpasswd file
|
3
4
|
#
|
@@ -7,8 +8,8 @@
|
|
7
8
|
#
|
8
9
|
# $IPR: htpasswd.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
require_relative 'userdb'
|
12
|
+
require_relative 'basicauth'
|
12
13
|
require 'tempfile'
|
13
14
|
|
14
15
|
module WEBrick
|
@@ -34,12 +35,30 @@ module WEBrick
|
|
34
35
|
##
|
35
36
|
# Open a password database at +path+
|
36
37
|
|
37
|
-
def initialize(path)
|
38
|
+
def initialize(path, password_hash: nil)
|
38
39
|
@path = path
|
39
40
|
@mtime = Time.at(0)
|
40
41
|
@passwd = Hash.new
|
41
42
|
@auth_type = BasicAuth
|
42
|
-
|
43
|
+
@password_hash = password_hash
|
44
|
+
|
45
|
+
case @password_hash
|
46
|
+
when nil
|
47
|
+
# begin
|
48
|
+
# require "string/crypt"
|
49
|
+
# rescue LoadError
|
50
|
+
# warn("Unable to load string/crypt, proceeding with deprecated use of String#crypt, consider using password_hash: :bcrypt")
|
51
|
+
# end
|
52
|
+
@password_hash = :crypt
|
53
|
+
when :crypt
|
54
|
+
# require "string/crypt"
|
55
|
+
when :bcrypt
|
56
|
+
require "bcrypt"
|
57
|
+
else
|
58
|
+
raise ArgumentError, "only :crypt and :bcrypt are supported for password_hash keyword argument"
|
59
|
+
end
|
60
|
+
|
61
|
+
File.open(@path,"a").close unless File.exist?(@path)
|
43
62
|
reload
|
44
63
|
end
|
45
64
|
|
@@ -50,11 +69,19 @@ module WEBrick
|
|
50
69
|
mtime = File::mtime(@path)
|
51
70
|
if mtime > @mtime
|
52
71
|
@passwd.clear
|
53
|
-
open(@path){|io|
|
72
|
+
File.open(@path){|io|
|
54
73
|
while line = io.gets
|
55
74
|
line.chomp!
|
56
75
|
case line
|
57
76
|
when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
|
77
|
+
if @password_hash == :bcrypt
|
78
|
+
raise StandardError, ".htpasswd file contains crypt password, only bcrypt passwords supported"
|
79
|
+
end
|
80
|
+
user, pass = line.split(":")
|
81
|
+
when %r!\A[^:]+:\$2[aby]\$\d{2}\$.{53}\z!
|
82
|
+
if @password_hash == :crypt
|
83
|
+
raise StandardError, ".htpasswd file contains bcrypt password, only crypt passwords supported"
|
84
|
+
end
|
58
85
|
user, pass = line.split(":")
|
59
86
|
when /:\$/, /:{SHA}/
|
60
87
|
raise NotImplementedError,
|
@@ -75,13 +102,16 @@ module WEBrick
|
|
75
102
|
|
76
103
|
def flush(output=nil)
|
77
104
|
output ||= @path
|
78
|
-
tmp = Tempfile.
|
105
|
+
tmp = Tempfile.create("htpasswd", File::dirname(output))
|
106
|
+
renamed = false
|
79
107
|
begin
|
80
108
|
each{|item| tmp.puts(item.join(":")) }
|
81
109
|
tmp.close
|
82
110
|
File::rename(tmp.path, output)
|
83
|
-
|
84
|
-
|
111
|
+
renamed = true
|
112
|
+
ensure
|
113
|
+
tmp.close
|
114
|
+
File.unlink(tmp.path) if !renamed
|
85
115
|
end
|
86
116
|
end
|
87
117
|
|
@@ -98,7 +128,14 @@ module WEBrick
|
|
98
128
|
# Sets a password in the database for +user+ in +realm+ to +pass+.
|
99
129
|
|
100
130
|
def set_passwd(realm, user, pass)
|
101
|
-
@
|
131
|
+
if @password_hash == :bcrypt
|
132
|
+
# Cost of 5 to match Apache default, and because the
|
133
|
+
# bcrypt default of 10 will introduce significant delays
|
134
|
+
# for every request.
|
135
|
+
@passwd[user] = BCrypt::Password.create(pass, :cost=>5)
|
136
|
+
else
|
137
|
+
@passwd[user] = make_passwd(realm, user, pass)
|
138
|
+
end
|
102
139
|
end
|
103
140
|
|
104
141
|
##
|
data/lib/webrick/httpproxy.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# httpproxy.rb -- HTTPProxy Class
|
3
4
|
#
|
@@ -9,22 +10,21 @@
|
|
9
10
|
# $IPR: httpproxy.rb,v 1.18 2003/03/08 18:58:10 gotoyuzo Exp $
|
10
11
|
# $kNotwork: straw.rb,v 1.3 2002/02/12 15:13:07 gotoken Exp $
|
11
12
|
|
12
|
-
|
13
|
+
require_relative "httpserver"
|
13
14
|
require "net/http"
|
14
15
|
|
15
|
-
Net::HTTP::version_1_2 if RUBY_VERSION < "1.7"
|
16
|
-
|
17
16
|
module WEBrick
|
18
|
-
|
19
|
-
|
17
|
+
|
18
|
+
NullReader = Object.new # :nodoc:
|
19
|
+
class << NullReader # :nodoc:
|
20
20
|
def read(*args)
|
21
21
|
nil
|
22
22
|
end
|
23
23
|
alias gets read
|
24
24
|
end
|
25
25
|
|
26
|
-
FakeProxyURI = Object.new
|
27
|
-
class << FakeProxyURI
|
26
|
+
FakeProxyURI = Object.new # :nodoc:
|
27
|
+
class << FakeProxyURI # :nodoc:
|
28
28
|
def method_missing(meth, *args)
|
29
29
|
if %w(scheme host port path query userinfo).member?(meth.to_s)
|
30
30
|
return nil
|
@@ -33,8 +33,38 @@ module WEBrick
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
# :startdoc:
|
37
|
+
|
36
38
|
##
|
37
39
|
# An HTTP Proxy server which proxies GET, HEAD and POST requests.
|
40
|
+
#
|
41
|
+
# To create a simple proxy server:
|
42
|
+
#
|
43
|
+
# require 'webrick'
|
44
|
+
# require 'webrick/httpproxy'
|
45
|
+
#
|
46
|
+
# proxy = WEBrick::HTTPProxyServer.new Port: 8000
|
47
|
+
#
|
48
|
+
# trap 'INT' do proxy.shutdown end
|
49
|
+
# trap 'TERM' do proxy.shutdown end
|
50
|
+
#
|
51
|
+
# proxy.start
|
52
|
+
#
|
53
|
+
# See ::new for proxy-specific configuration items.
|
54
|
+
#
|
55
|
+
# == Modifying proxied responses
|
56
|
+
#
|
57
|
+
# To modify content the proxy server returns use the +:ProxyContentHandler+
|
58
|
+
# option:
|
59
|
+
#
|
60
|
+
# handler = proc do |req, res|
|
61
|
+
# if res['content-type'] == 'text/plain' then
|
62
|
+
# res.body << "\nThis content was proxied!\n"
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# proxy =
|
67
|
+
# WEBrick::HTTPProxyServer.new Port: 8000, ProxyContentHandler: handler
|
38
68
|
|
39
69
|
class HTTPProxyServer < HTTPServer
|
40
70
|
|
@@ -46,7 +76,7 @@ module WEBrick
|
|
46
76
|
# request
|
47
77
|
# :ProxyVia:: Appended to the via header
|
48
78
|
# :ProxyURI:: The proxy server's URI
|
49
|
-
# :ProxyContentHandler:: Called with a request and
|
79
|
+
# :ProxyContentHandler:: Called with a request and response and allows
|
50
80
|
# modification of the response
|
51
81
|
# :ProxyTimeout:: Sets the proxy timeouts to 30 seconds for open and 60
|
52
82
|
# seconds for read operations
|
@@ -57,6 +87,7 @@ module WEBrick
|
|
57
87
|
@via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
|
58
88
|
end
|
59
89
|
|
90
|
+
# :stopdoc:
|
60
91
|
def service(req, res)
|
61
92
|
if req.request_method == "CONNECT"
|
62
93
|
do_CONNECT(req, res)
|
@@ -112,7 +143,7 @@ module WEBrick
|
|
112
143
|
if proxy = proxy_uri(req, res)
|
113
144
|
proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
|
114
145
|
if proxy.userinfo
|
115
|
-
credentials = "Basic " + [proxy.userinfo].pack("
|
146
|
+
credentials = "Basic " + [proxy.userinfo].pack("m0")
|
116
147
|
end
|
117
148
|
host, port = proxy.host, proxy.port
|
118
149
|
end
|
@@ -126,12 +157,12 @@ module WEBrick
|
|
126
157
|
os << proxy_request_line << CRLF
|
127
158
|
@logger.debug("CONNECT: > #{proxy_request_line}")
|
128
159
|
if credentials
|
129
|
-
@logger.debug("CONNECT: sending
|
160
|
+
@logger.debug("CONNECT: sending credentials")
|
130
161
|
os << "Proxy-Authorization: " << credentials << CRLF
|
131
162
|
end
|
132
163
|
os << CRLF
|
133
164
|
proxy_status_line = os.gets(LF)
|
134
|
-
@logger.debug("CONNECT: read
|
165
|
+
@logger.debug("CONNECT: read Status-Line from the upstream server")
|
135
166
|
@logger.debug("CONNECT: < #{proxy_status_line}")
|
136
167
|
if %r{^HTTP/\d+\.\d+\s+200\s*} =~ proxy_status_line
|
137
168
|
while line = os.gets(LF)
|
@@ -154,7 +185,7 @@ module WEBrick
|
|
154
185
|
res.send_response(ua)
|
155
186
|
access_log(@config, req, res)
|
156
187
|
|
157
|
-
# Should clear request-line not to send the
|
188
|
+
# Should clear request-line not to send the response twice.
|
158
189
|
# see: HTTPServer#run
|
159
190
|
req.parse(NullReader) rescue nil
|
160
191
|
end
|
@@ -162,16 +193,16 @@ module WEBrick
|
|
162
193
|
begin
|
163
194
|
while fds = IO::select([ua, os])
|
164
195
|
if fds[0].member?(ua)
|
165
|
-
buf = ua.
|
196
|
+
buf = ua.readpartial(1024);
|
166
197
|
@logger.debug("CONNECT: #{buf.bytesize} byte from User-Agent")
|
167
|
-
os.
|
198
|
+
os.write(buf)
|
168
199
|
elsif fds[0].member?(os)
|
169
|
-
buf = os.
|
200
|
+
buf = os.readpartial(1024);
|
170
201
|
@logger.debug("CONNECT: #{buf.bytesize} byte from #{host}:#{port}")
|
171
|
-
ua.
|
202
|
+
ua.write(buf)
|
172
203
|
end
|
173
204
|
end
|
174
|
-
rescue
|
205
|
+
rescue
|
175
206
|
os.close
|
176
207
|
@logger.debug("CONNECT #{host}:#{port}: closed")
|
177
208
|
end
|
@@ -180,21 +211,15 @@ module WEBrick
|
|
180
211
|
end
|
181
212
|
|
182
213
|
def do_GET(req, res)
|
183
|
-
perform_proxy_request(req, res
|
184
|
-
http.get(path, header)
|
185
|
-
end
|
214
|
+
perform_proxy_request(req, res, Net::HTTP::Get)
|
186
215
|
end
|
187
216
|
|
188
217
|
def do_HEAD(req, res)
|
189
|
-
perform_proxy_request(req, res
|
190
|
-
http.head(path, header)
|
191
|
-
end
|
218
|
+
perform_proxy_request(req, res, Net::HTTP::Head)
|
192
219
|
end
|
193
220
|
|
194
221
|
def do_POST(req, res)
|
195
|
-
perform_proxy_request(req, res
|
196
|
-
http.post(path, req.body || "", header)
|
197
|
-
end
|
222
|
+
perform_proxy_request(req, res, Net::HTTP::Post, req.body_reader)
|
198
223
|
end
|
199
224
|
|
200
225
|
def do_OPTIONS(req, res)
|
@@ -263,43 +288,63 @@ module WEBrick
|
|
263
288
|
if upstream = proxy_uri(req, res)
|
264
289
|
if upstream.userinfo
|
265
290
|
header['proxy-authorization'] =
|
266
|
-
"Basic " + [upstream.userinfo].pack("
|
291
|
+
"Basic " + [upstream.userinfo].pack("m0")
|
267
292
|
end
|
268
293
|
return upstream
|
269
294
|
end
|
270
295
|
return FakeProxyURI
|
271
296
|
end
|
272
297
|
|
273
|
-
def perform_proxy_request(req, res)
|
298
|
+
def perform_proxy_request(req, res, req_class, body_stream = nil)
|
274
299
|
uri = req.request_uri
|
275
300
|
path = uri.path.dup
|
276
301
|
path << "?" << uri.query if uri.query
|
277
302
|
header = setup_proxy_header(req, res)
|
278
303
|
upstream = setup_upstream_proxy_authentication(req, res, header)
|
279
|
-
response = nil
|
280
304
|
|
305
|
+
body_tmp = []
|
281
306
|
http = Net::HTTP.new(uri.host, uri.port, upstream.host, upstream.port)
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
307
|
+
req_fib = Fiber.new do
|
308
|
+
http.start do
|
309
|
+
if @config[:ProxyTimeout]
|
310
|
+
################################## these issues are
|
311
|
+
http.open_timeout = 30 # secs # necessary (maybe because
|
312
|
+
http.read_timeout = 60 # secs # Ruby's bug, but why?)
|
313
|
+
##################################
|
314
|
+
end
|
315
|
+
if body_stream && req['transfer-encoding'] =~ /\bchunked\b/i
|
316
|
+
header['Transfer-Encoding'] = 'chunked'
|
317
|
+
end
|
318
|
+
http_req = req_class.new(path, header)
|
319
|
+
http_req.body_stream = body_stream if body_stream
|
320
|
+
http.request(http_req) do |response|
|
321
|
+
# Persistent connection requirements are mysterious for me.
|
322
|
+
# So I will close the connection in every response.
|
323
|
+
res['proxy-connection'] = "close"
|
324
|
+
res['connection'] = "close"
|
325
|
+
|
326
|
+
# stream Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
|
327
|
+
res.status = response.code.to_i
|
328
|
+
res.chunked = response.chunked?
|
329
|
+
choose_header(response, res)
|
330
|
+
set_cookie(response, res)
|
331
|
+
set_via(res)
|
332
|
+
response.read_body do |buf|
|
333
|
+
body_tmp << buf
|
334
|
+
Fiber.yield # wait for res.body Proc#call
|
335
|
+
end
|
336
|
+
end # http.request
|
337
|
+
end
|
338
|
+
end
|
339
|
+
req_fib.resume # read HTTP response headers and first chunk of the body
|
340
|
+
res.body = ->(socket) do
|
341
|
+
while buf = body_tmp.shift
|
342
|
+
socket.write(buf)
|
343
|
+
buf.clear
|
344
|
+
req_fib.resume # continue response.read_body
|
288
345
|
end
|
289
|
-
response = yield(http, path, header)
|
290
346
|
end
|
291
|
-
|
292
|
-
# Persistent connection requirements are mysterious for me.
|
293
|
-
# So I will close the connection in every response.
|
294
|
-
res['proxy-connection'] = "close"
|
295
|
-
res['connection'] = "close"
|
296
|
-
|
297
|
-
# Convert Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
|
298
|
-
res.status = response.code.to_i
|
299
|
-
choose_header(response, res)
|
300
|
-
set_cookie(response, res)
|
301
|
-
set_via(res)
|
302
|
-
res.body = response.body
|
303
347
|
end
|
348
|
+
# :stopdoc:
|
304
349
|
end
|
305
350
|
end
|
data/lib/webrick/httprequest.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
#
|
2
3
|
# httprequest.rb -- HTTPRequest Class
|
3
4
|
#
|
@@ -9,39 +10,145 @@
|
|
9
10
|
# $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
|
10
11
|
|
11
12
|
require 'uri'
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
require_relative 'httpversion'
|
14
|
+
require_relative 'httpstatus'
|
15
|
+
require_relative 'httputils'
|
16
|
+
require_relative 'cookie'
|
16
17
|
|
17
18
|
module WEBrick
|
18
19
|
|
19
20
|
##
|
20
|
-
# An HTTP request.
|
21
|
+
# An HTTP request. This is consumed by service and do_* methods in
|
22
|
+
# WEBrick servlets
|
23
|
+
|
21
24
|
class HTTPRequest
|
22
25
|
|
23
|
-
BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
|
26
|
+
BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ] # :nodoc:
|
24
27
|
|
25
28
|
# :section: Request line
|
29
|
+
|
30
|
+
##
|
31
|
+
# The complete request line such as:
|
32
|
+
#
|
33
|
+
# GET / HTTP/1.1
|
34
|
+
|
26
35
|
attr_reader :request_line
|
27
|
-
|
36
|
+
|
37
|
+
##
|
38
|
+
# The request method, GET, POST, PUT, etc.
|
39
|
+
|
40
|
+
attr_reader :request_method
|
41
|
+
|
42
|
+
##
|
43
|
+
# The unparsed URI of the request
|
44
|
+
|
45
|
+
attr_reader :unparsed_uri
|
46
|
+
|
47
|
+
##
|
48
|
+
# The HTTP version of the request
|
49
|
+
|
50
|
+
attr_reader :http_version
|
28
51
|
|
29
52
|
# :section: Request-URI
|
30
|
-
|
31
|
-
|
53
|
+
|
54
|
+
##
|
55
|
+
# The parsed URI of the request
|
56
|
+
|
57
|
+
attr_reader :request_uri
|
58
|
+
|
59
|
+
##
|
60
|
+
# The request path
|
61
|
+
|
62
|
+
attr_reader :path
|
63
|
+
|
64
|
+
##
|
65
|
+
# The script name (CGI variable)
|
66
|
+
|
67
|
+
attr_accessor :script_name
|
68
|
+
|
69
|
+
##
|
70
|
+
# The path info (CGI variable)
|
71
|
+
|
72
|
+
attr_accessor :path_info
|
73
|
+
|
74
|
+
##
|
75
|
+
# The query from the URI of the request
|
76
|
+
|
77
|
+
attr_accessor :query_string
|
32
78
|
|
33
79
|
# :section: Header and entity body
|
34
|
-
|
35
|
-
|
36
|
-
|
80
|
+
|
81
|
+
##
|
82
|
+
# The raw header of the request
|
83
|
+
|
84
|
+
attr_reader :raw_header
|
85
|
+
|
86
|
+
##
|
87
|
+
# The parsed header of the request
|
88
|
+
|
89
|
+
attr_reader :header
|
90
|
+
|
91
|
+
##
|
92
|
+
# The parsed request cookies
|
93
|
+
|
94
|
+
attr_reader :cookies
|
95
|
+
|
96
|
+
##
|
97
|
+
# The Accept header value
|
98
|
+
|
99
|
+
attr_reader :accept
|
100
|
+
|
101
|
+
##
|
102
|
+
# The Accept-Charset header value
|
103
|
+
|
104
|
+
attr_reader :accept_charset
|
105
|
+
|
106
|
+
##
|
107
|
+
# The Accept-Encoding header value
|
108
|
+
|
109
|
+
attr_reader :accept_encoding
|
110
|
+
|
111
|
+
##
|
112
|
+
# The Accept-Language header value
|
113
|
+
|
114
|
+
attr_reader :accept_language
|
37
115
|
|
38
116
|
# :section:
|
117
|
+
|
118
|
+
##
|
119
|
+
# The remote user (CGI variable)
|
120
|
+
|
39
121
|
attr_accessor :user
|
40
|
-
|
122
|
+
|
123
|
+
##
|
124
|
+
# The socket address of the server
|
125
|
+
|
126
|
+
attr_reader :addr
|
127
|
+
|
128
|
+
##
|
129
|
+
# The socket address of the client
|
130
|
+
|
131
|
+
attr_reader :peeraddr
|
132
|
+
|
133
|
+
##
|
134
|
+
# Hash of request attributes
|
135
|
+
|
41
136
|
attr_reader :attributes
|
137
|
+
|
138
|
+
##
|
139
|
+
# Is this a keep-alive connection?
|
140
|
+
|
42
141
|
attr_reader :keep_alive
|
142
|
+
|
143
|
+
##
|
144
|
+
# The local time this request was received
|
145
|
+
|
43
146
|
attr_reader :request_time
|
44
147
|
|
148
|
+
##
|
149
|
+
# Creates a new HTTP request. WEBrick::Config::HTTP is the default
|
150
|
+
# configuration.
|
151
|
+
|
45
152
|
def initialize(config)
|
46
153
|
@config = config
|
47
154
|
@buffer_size = @config[:InputBufferSize]
|
@@ -78,6 +185,10 @@ module WEBrick
|
|
78
185
|
@forwarded_server = @forwarded_for = nil
|
79
186
|
end
|
80
187
|
|
188
|
+
##
|
189
|
+
# Parses a request from +socket+. This is called internally by
|
190
|
+
# WEBrick::HTTPServer.
|
191
|
+
|
81
192
|
def parse(socket=nil)
|
82
193
|
@socket = socket
|
83
194
|
begin
|
@@ -115,9 +226,9 @@ module WEBrick
|
|
115
226
|
raise HTTPStatus::BadRequest, "bad URI `#{@unparsed_uri}'."
|
116
227
|
end
|
117
228
|
|
118
|
-
if /
|
229
|
+
if /\Aclose\z/io =~ self["connection"]
|
119
230
|
@keep_alive = false
|
120
|
-
elsif
|
231
|
+
elsif /\Akeep-alive\z/io =~ self["connection"]
|
121
232
|
@keep_alive = true
|
122
233
|
elsif @http_version < "1.1"
|
123
234
|
@keep_alive = false
|
@@ -126,21 +237,52 @@ module WEBrick
|
|
126
237
|
end
|
127
238
|
end
|
128
239
|
|
240
|
+
##
|
129
241
|
# Generate HTTP/1.1 100 continue response if the client expects it,
|
130
242
|
# otherwise does nothing.
|
131
|
-
|
243
|
+
|
244
|
+
def continue # :nodoc:
|
132
245
|
if self['expect'] == '100-continue' && @config[:HTTPVersion] >= "1.1"
|
133
246
|
@socket << "HTTP/#{@config[:HTTPVersion]} 100 continue#{CRLF}#{CRLF}"
|
134
247
|
@header.delete('expect')
|
135
248
|
end
|
136
249
|
end
|
137
250
|
|
138
|
-
|
251
|
+
##
|
252
|
+
# Returns the request body.
|
253
|
+
|
254
|
+
def body(&block) # :yields: body_chunk
|
139
255
|
block ||= Proc.new{|chunk| @body << chunk }
|
140
256
|
read_body(@socket, block)
|
141
257
|
@body.empty? ? nil : @body
|
142
258
|
end
|
143
259
|
|
260
|
+
##
|
261
|
+
# Prepares the HTTPRequest object for use as the
|
262
|
+
# source for IO.copy_stream
|
263
|
+
|
264
|
+
def body_reader
|
265
|
+
@body_tmp = []
|
266
|
+
@body_rd = Fiber.new do
|
267
|
+
body do |buf|
|
268
|
+
@body_tmp << buf
|
269
|
+
Fiber.yield
|
270
|
+
end
|
271
|
+
end
|
272
|
+
@body_rd.resume # grab the first chunk and yield
|
273
|
+
self
|
274
|
+
end
|
275
|
+
|
276
|
+
# for IO.copy_stream. Note: we may return a larger string than +size+
|
277
|
+
# here; but IO.copy_stream does not care.
|
278
|
+
def readpartial(size, buf = ''.b) # :nodoc
|
279
|
+
res = @body_tmp.shift or raise EOFError, 'end of file reached'
|
280
|
+
buf.replace(res)
|
281
|
+
res.clear
|
282
|
+
@body_rd.resume # get more chunks
|
283
|
+
buf
|
284
|
+
end
|
285
|
+
|
144
286
|
##
|
145
287
|
# Request query as a Hash
|
146
288
|
|
@@ -237,11 +379,14 @@ module WEBrick
|
|
237
379
|
ret
|
238
380
|
end
|
239
381
|
|
240
|
-
|
382
|
+
##
|
383
|
+
# Consumes any remaining body and updates keep-alive status
|
384
|
+
|
385
|
+
def fixup() # :nodoc:
|
241
386
|
begin
|
242
387
|
body{|chunk| } # read remaining body
|
243
388
|
rescue HTTPStatus::Error => ex
|
244
|
-
@logger.error("HTTPRequest#fixup: #{ex.class}
|
389
|
+
@logger.error("HTTPRequest#fixup: #{ex.class} occurred.")
|
245
390
|
@keep_alive = false
|
246
391
|
rescue => ex
|
247
392
|
@logger.error(ex)
|
@@ -251,7 +396,8 @@ module WEBrick
|
|
251
396
|
|
252
397
|
# This method provides the metavariables defined by the revision 3
|
253
398
|
# of "The WWW Common Gateway Interface Version 1.1"
|
254
|
-
#
|
399
|
+
# To browse the current document of CGI Version 1.1, see below:
|
400
|
+
# http://tools.ietf.org/html/rfc3875
|
255
401
|
|
256
402
|
def meta_vars
|
257
403
|
meta = Hash.new
|
@@ -290,15 +436,23 @@ module WEBrick
|
|
290
436
|
|
291
437
|
private
|
292
438
|
|
439
|
+
# :stopdoc:
|
440
|
+
|
293
441
|
MAX_URI_LENGTH = 2083 # :nodoc:
|
294
442
|
|
443
|
+
# same as Mongrel, Thin and Puma
|
444
|
+
MAX_HEADER_LENGTH = (112 * 1024) # :nodoc:
|
445
|
+
|
295
446
|
def read_request_line(socket)
|
296
447
|
@request_line = read_line(socket, MAX_URI_LENGTH) if socket
|
297
|
-
|
448
|
+
raise HTTPStatus::EOFError unless @request_line
|
449
|
+
|
450
|
+
@request_bytes = @request_line.bytesize
|
451
|
+
if @request_bytes >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
|
298
452
|
raise HTTPStatus::RequestURITooLarge
|
299
453
|
end
|
454
|
+
|
300
455
|
@request_time = Time.now
|
301
|
-
raise HTTPStatus::EOFError unless @request_line
|
302
456
|
if /^(\S+)\s+(\S++)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
|
303
457
|
@request_method = $1
|
304
458
|
@unparsed_uri = $2
|
@@ -313,6 +467,9 @@ module WEBrick
|
|
313
467
|
if socket
|
314
468
|
while line = read_line(socket)
|
315
469
|
break if /\A(#{CRLF}|#{LF})\z/om =~ line
|
470
|
+
if (@request_bytes += line.bytesize) > MAX_HEADER_LENGTH
|
471
|
+
raise HTTPStatus::RequestEntityTooLarge, 'headers too large'
|
472
|
+
end
|
316
473
|
@raw_header << line
|
317
474
|
end
|
318
475
|
end
|
@@ -346,7 +503,7 @@ module WEBrick
|
|
346
503
|
return unless socket
|
347
504
|
if tc = self['transfer-encoding']
|
348
505
|
case tc
|
349
|
-
when /
|
506
|
+
when /\Achunked\z/io then read_chunked(socket, block)
|
350
507
|
else raise HTTPStatus::NotImplemented, "Transfer-Encoding: #{tc}."
|
351
508
|
end
|
352
509
|
elsif self['content-length'] || @remaining_size
|
@@ -380,12 +537,16 @@ module WEBrick
|
|
380
537
|
def read_chunked(socket, block)
|
381
538
|
chunk_size, = read_chunk_size(socket)
|
382
539
|
while chunk_size > 0
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
540
|
+
begin
|
541
|
+
sz = [ chunk_size, @buffer_size ].min
|
542
|
+
data = read_data(socket, sz) # read chunk-data
|
543
|
+
if data.nil? || data.bytesize != sz
|
544
|
+
raise HTTPStatus::BadRequest, "bad chunk data size."
|
545
|
+
end
|
546
|
+
block.call(data)
|
547
|
+
end while (chunk_size -= sz) > 0
|
548
|
+
|
387
549
|
read_line(socket) # skip CRLF
|
388
|
-
block.call(data)
|
389
550
|
chunk_size, = read_chunk_size(socket)
|
390
551
|
end
|
391
552
|
read_header(socket) # trailer + CRLF
|
@@ -400,7 +561,7 @@ module WEBrick
|
|
400
561
|
}
|
401
562
|
rescue Errno::ECONNRESET
|
402
563
|
return nil
|
403
|
-
rescue
|
564
|
+
rescue Timeout::Error
|
404
565
|
raise HTTPStatus::RequestTimeout
|
405
566
|
end
|
406
567
|
end
|
@@ -445,10 +606,17 @@ module WEBrick
|
|
445
606
|
if @forwarded_server = self["x-forwarded-server"]
|
446
607
|
@forwarded_server = @forwarded_server.split(",", 2).first
|
447
608
|
end
|
448
|
-
@forwarded_proto = self["x-forwarded-proto"]
|
609
|
+
if @forwarded_proto = self["x-forwarded-proto"]
|
610
|
+
@forwarded_proto = @forwarded_proto.split(",", 2).first
|
611
|
+
end
|
449
612
|
if host_port = self["x-forwarded-host"]
|
450
613
|
host_port = host_port.split(",", 2).first
|
451
|
-
|
614
|
+
if host_port =~ /\A(\[[0-9a-fA-F:]+\])(?::(\d+))?\z/
|
615
|
+
@forwarded_host = $1
|
616
|
+
tmp = $2
|
617
|
+
else
|
618
|
+
@forwarded_host, tmp = host_port.split(":", 2)
|
619
|
+
end
|
452
620
|
@forwarded_port = (tmp || (@forwarded_proto == "https" ? 443 : 80)).to_i
|
453
621
|
end
|
454
622
|
if addrs = self["x-forwarded-for"]
|
@@ -457,5 +625,7 @@ module WEBrick
|
|
457
625
|
@forwarded_for = addrs.first
|
458
626
|
end
|
459
627
|
end
|
628
|
+
|
629
|
+
# :startdoc:
|
460
630
|
end
|
461
631
|
end
|