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
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
#--
|
2
2
|
# httpauth/userdb.rb -- UserDB mix-in module.
|
3
3
|
#
|
4
4
|
# Author: IPR -- Internet Programming with Ruby -- writers
|
@@ -9,19 +9,42 @@
|
|
9
9
|
|
10
10
|
module WEBrick
|
11
11
|
module HTTPAuth
|
12
|
+
|
13
|
+
##
|
14
|
+
# User database mixin for HTTPAuth. This mixin dispatches user record
|
15
|
+
# access to the underlying auth_type for this database.
|
16
|
+
|
12
17
|
module UserDB
|
13
|
-
|
18
|
+
|
19
|
+
##
|
20
|
+
# The authentication type.
|
21
|
+
#
|
22
|
+
# WEBrick::HTTPAuth::BasicAuth or WEBrick::HTTPAuth::DigestAuth are
|
23
|
+
# built-in.
|
24
|
+
|
25
|
+
attr_accessor :auth_type
|
26
|
+
|
27
|
+
##
|
28
|
+
# Creates an obscured password in +realm+ with +user+ and +password+
|
29
|
+
# using the auth_type of this database.
|
14
30
|
|
15
31
|
def make_passwd(realm, user, pass)
|
16
32
|
@auth_type::make_passwd(realm, user, pass)
|
17
33
|
end
|
18
34
|
|
35
|
+
##
|
36
|
+
# Sets a password in +realm+ with +user+ and +password+ for the
|
37
|
+
# auth_type of this database.
|
38
|
+
|
19
39
|
def set_passwd(realm, user, pass)
|
20
40
|
self[user] = pass
|
21
|
-
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Retrieves a password in +realm+ for +user+ for the auth_type of this
|
45
|
+
# database. +reload_db+ is a dummy value.
|
22
46
|
|
23
47
|
def get_passwd(realm, user, reload_db=false)
|
24
|
-
# reload_db is dummy
|
25
48
|
make_passwd(realm, user, self[user])
|
26
49
|
end
|
27
50
|
end
|
data/lib/webrick/httpproxy.rb
CHANGED
@@ -15,24 +15,83 @@ require "net/http"
|
|
15
15
|
Net::HTTP::version_1_2 if RUBY_VERSION < "1.7"
|
16
16
|
|
17
17
|
module WEBrick
|
18
|
-
|
19
|
-
|
18
|
+
|
19
|
+
NullReader = Object.new # :nodoc:
|
20
|
+
class << NullReader # :nodoc:
|
20
21
|
def read(*args)
|
21
22
|
nil
|
22
23
|
end
|
23
24
|
alias gets read
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
FakeProxyURI = Object.new # :nodoc:
|
28
|
+
class << FakeProxyURI # :nodoc:
|
29
|
+
def method_missing(meth, *args)
|
30
|
+
if %w(scheme host port path query userinfo).member?(meth.to_s)
|
31
|
+
return nil
|
32
|
+
end
|
28
33
|
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# :startdoc:
|
38
|
+
|
39
|
+
##
|
40
|
+
# An HTTP Proxy server which proxies GET, HEAD and POST requests.
|
41
|
+
#
|
42
|
+
# To create a simple proxy server:
|
43
|
+
#
|
44
|
+
# require 'webrick'
|
45
|
+
# require 'webrick/httpproxy'
|
46
|
+
#
|
47
|
+
# proxy = WEBrick::HTTPProxyServer.new Port: 8000
|
48
|
+
#
|
49
|
+
# trap 'INT' do proxy.shutdown end
|
50
|
+
# trap 'TERM' do proxy.shutdown end
|
51
|
+
#
|
52
|
+
# proxy.start
|
53
|
+
#
|
54
|
+
# See ::new for proxy-specific configuration items.
|
55
|
+
#
|
56
|
+
# == Modifying proxied responses
|
57
|
+
#
|
58
|
+
# To modify content the proxy server returns use the +:ProxyContentHandler+
|
59
|
+
# option:
|
60
|
+
#
|
61
|
+
# handler = proc do |req, res|
|
62
|
+
# if res['content-type'] == 'text/plain' then
|
63
|
+
# res.body << "\nThis content was proxied!\n"
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# proxy =
|
68
|
+
# WEBrick::HTTPProxyServer.new Port: 8000, ProxyContentHandler: handler
|
69
|
+
|
70
|
+
class HTTPProxyServer < HTTPServer
|
71
|
+
|
72
|
+
##
|
73
|
+
# Proxy server configurations. The proxy server handles the following
|
74
|
+
# configuration items in addition to those supported by HTTPServer:
|
75
|
+
#
|
76
|
+
# :ProxyAuthProc:: Called with a request and response to authorize a
|
77
|
+
# request
|
78
|
+
# :ProxyVia:: Appended to the via header
|
79
|
+
# :ProxyURI:: The proxy server's URI
|
80
|
+
# :ProxyContentHandler:: Called with a request and response and allows
|
81
|
+
# modification of the response
|
82
|
+
# :ProxyTimeout:: Sets the proxy timeouts to 30 seconds for open and 60
|
83
|
+
# seconds for read operations
|
84
|
+
|
85
|
+
def initialize(config={}, default=Config::HTTP)
|
86
|
+
super(config, default)
|
29
87
|
c = @config
|
30
88
|
@via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
|
31
89
|
end
|
32
90
|
|
91
|
+
# :stopdoc:
|
33
92
|
def service(req, res)
|
34
93
|
if req.request_method == "CONNECT"
|
35
|
-
|
94
|
+
do_CONNECT(req, res)
|
36
95
|
elsif req.unparsed_uri =~ %r!^http://!
|
37
96
|
proxy_service(req, res)
|
38
97
|
else
|
@@ -47,118 +106,24 @@ module WEBrick
|
|
47
106
|
req.header.delete("proxy-authorization")
|
48
107
|
end
|
49
108
|
|
50
|
-
# Some header fields should not be transferred.
|
51
|
-
HopByHop = %w( connection keep-alive proxy-authenticate upgrade
|
52
|
-
proxy-authorization te trailers transfer-encoding )
|
53
|
-
ShouldNotTransfer = %w( set-cookie proxy-connection )
|
54
|
-
def split_field(f) f ? f.split(/,\s+/).collect{|i| i.downcase } : [] end
|
55
|
-
|
56
|
-
def choose_header(src, dst)
|
57
|
-
connections = split_field(src['connection'])
|
58
|
-
src.each{|key, value|
|
59
|
-
key = key.downcase
|
60
|
-
if HopByHop.member?(key) || # RFC2616: 13.5.1
|
61
|
-
connections.member?(key) || # RFC2616: 14.10
|
62
|
-
ShouldNotTransfer.member?(key) # pragmatics
|
63
|
-
@logger.debug("choose_header: `#{key}: #{value}'")
|
64
|
-
next
|
65
|
-
end
|
66
|
-
dst[key] = value
|
67
|
-
}
|
68
|
-
end
|
69
|
-
|
70
|
-
# Net::HTTP is stupid about the multiple header fields.
|
71
|
-
# Here is workaround:
|
72
|
-
def set_cookie(src, dst)
|
73
|
-
if str = src['set-cookie']
|
74
|
-
cookies = []
|
75
|
-
str.split(/,\s*/).each{|token|
|
76
|
-
if /^[^=]+;/o =~ token
|
77
|
-
cookies[-1] << ", " << token
|
78
|
-
elsif /=/o =~ token
|
79
|
-
cookies << token
|
80
|
-
else
|
81
|
-
cookies[-1] << ", " << token
|
82
|
-
end
|
83
|
-
}
|
84
|
-
dst.cookies.replace(cookies)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def set_via(h)
|
89
|
-
if @config[:ProxyVia]
|
90
|
-
if h['via']
|
91
|
-
h['via'] << ", " << @via
|
92
|
-
else
|
93
|
-
h['via'] = @via
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
109
|
def proxy_uri(req, res)
|
99
|
-
|
110
|
+
# should return upstream proxy server's URI
|
111
|
+
return @config[:ProxyURI]
|
100
112
|
end
|
101
113
|
|
102
114
|
def proxy_service(req, res)
|
103
115
|
# Proxy Authentication
|
104
|
-
proxy_auth(req, res)
|
105
|
-
|
106
|
-
# Create Request-URI to send to the origin server
|
107
|
-
uri = req.request_uri
|
108
|
-
path = uri.path.dup
|
109
|
-
path << "?" << uri.query if uri.query
|
110
|
-
|
111
|
-
# Choose header fields to transfer
|
112
|
-
header = Hash.new
|
113
|
-
choose_header(req, header)
|
114
|
-
set_via(header)
|
115
|
-
|
116
|
-
# select upstream proxy server
|
117
|
-
if proxy = proxy_uri(req, res)
|
118
|
-
proxy_host = proxy.host
|
119
|
-
proxy_port = proxy.port
|
120
|
-
if proxy.userinfo
|
121
|
-
credentials = "Basic " + [proxy.userinfo].pack("m*")
|
122
|
-
credentials.chomp!
|
123
|
-
header['proxy-authorization'] = credentials
|
124
|
-
end
|
125
|
-
end
|
116
|
+
proxy_auth(req, res)
|
126
117
|
|
127
|
-
response = nil
|
128
118
|
begin
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
http.open_timeout = 30 # secs # necessary (maybe bacause
|
134
|
-
http.read_timeout = 60 # secs # Ruby's bug, but why?)
|
135
|
-
##################################
|
136
|
-
end
|
137
|
-
case req.request_method
|
138
|
-
when "GET" then response = http.get(path, header)
|
139
|
-
when "POST" then response = http.post(path, req.body || "", header)
|
140
|
-
when "HEAD" then response = http.head(path, header)
|
141
|
-
else
|
142
|
-
raise HTTPStatus::MethodNotAllowed,
|
143
|
-
"unsupported method `#{req.request_method}'."
|
144
|
-
end
|
145
|
-
}
|
119
|
+
self.send("do_#{req.request_method}", req, res)
|
120
|
+
rescue NoMethodError
|
121
|
+
raise HTTPStatus::MethodNotAllowed,
|
122
|
+
"unsupported method `#{req.request_method}'."
|
146
123
|
rescue => err
|
147
124
|
logger.debug("#{err.class}: #{err.message}")
|
148
125
|
raise HTTPStatus::ServiceUnavailable, err.message
|
149
126
|
end
|
150
|
-
|
151
|
-
# Persistent connction requirements are mysterious for me.
|
152
|
-
# So I will close the connection in every response.
|
153
|
-
res['proxy-connection'] = "close"
|
154
|
-
res['connection'] = "close"
|
155
|
-
|
156
|
-
# Convert Net::HTTP::HTTPResponse to WEBrick::HTTPProxy
|
157
|
-
res.status = response.code.to_i
|
158
|
-
choose_header(response, res)
|
159
|
-
set_cookie(response, res)
|
160
|
-
set_via(res)
|
161
|
-
res.body = response.body
|
162
127
|
|
163
128
|
# Process contents
|
164
129
|
if handler = @config[:ProxyContentHandler]
|
@@ -166,7 +131,7 @@ module WEBrick
|
|
166
131
|
end
|
167
132
|
end
|
168
133
|
|
169
|
-
def
|
134
|
+
def do_CONNECT(req, res)
|
170
135
|
# Proxy Authentication
|
171
136
|
proxy_auth(req, res)
|
172
137
|
|
@@ -179,8 +144,7 @@ module WEBrick
|
|
179
144
|
if proxy = proxy_uri(req, res)
|
180
145
|
proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
|
181
146
|
if proxy.userinfo
|
182
|
-
credentials = "Basic " + [proxy.userinfo].pack("m
|
183
|
-
credentials.chomp!
|
147
|
+
credentials = "Basic " + [proxy.userinfo].pack("m").delete("\n")
|
184
148
|
end
|
185
149
|
host, port = proxy.host, proxy.port
|
186
150
|
end
|
@@ -222,7 +186,7 @@ module WEBrick
|
|
222
186
|
res.send_response(ua)
|
223
187
|
access_log(@config, req, res)
|
224
188
|
|
225
|
-
# Should clear request-line not to send the
|
189
|
+
# Should clear request-line not to send the response twice.
|
226
190
|
# see: HTTPServer#run
|
227
191
|
req.parse(NullReader) rescue nil
|
228
192
|
end
|
@@ -231,11 +195,11 @@ module WEBrick
|
|
231
195
|
while fds = IO::select([ua, os])
|
232
196
|
if fds[0].member?(ua)
|
233
197
|
buf = ua.sysread(1024);
|
234
|
-
@logger.debug("CONNECT: #{buf.
|
198
|
+
@logger.debug("CONNECT: #{buf.bytesize} byte from User-Agent")
|
235
199
|
os.syswrite(buf)
|
236
200
|
elsif fds[0].member?(os)
|
237
201
|
buf = os.sysread(1024);
|
238
|
-
@logger.debug("CONNECT: #{buf.
|
202
|
+
@logger.debug("CONNECT: #{buf.bytesize} byte from #{host}:#{port}")
|
239
203
|
ua.syswrite(buf)
|
240
204
|
end
|
241
205
|
end
|
@@ -247,8 +211,129 @@ module WEBrick
|
|
247
211
|
raise HTTPStatus::EOFError
|
248
212
|
end
|
249
213
|
|
214
|
+
def do_GET(req, res)
|
215
|
+
perform_proxy_request(req, res) do |http, path, header|
|
216
|
+
http.get(path, header)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def do_HEAD(req, res)
|
221
|
+
perform_proxy_request(req, res) do |http, path, header|
|
222
|
+
http.head(path, header)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def do_POST(req, res)
|
227
|
+
perform_proxy_request(req, res) do |http, path, header|
|
228
|
+
http.post(path, req.body || "", header)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
250
232
|
def do_OPTIONS(req, res)
|
251
233
|
res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT"
|
252
234
|
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
# Some header fields should not be transferred.
|
239
|
+
HopByHop = %w( connection keep-alive proxy-authenticate upgrade
|
240
|
+
proxy-authorization te trailers transfer-encoding )
|
241
|
+
ShouldNotTransfer = %w( set-cookie proxy-connection )
|
242
|
+
def split_field(f) f ? f.split(/,\s+/).collect{|i| i.downcase } : [] end
|
243
|
+
|
244
|
+
def choose_header(src, dst)
|
245
|
+
connections = split_field(src['connection'])
|
246
|
+
src.each{|key, value|
|
247
|
+
key = key.downcase
|
248
|
+
if HopByHop.member?(key) || # RFC2616: 13.5.1
|
249
|
+
connections.member?(key) || # RFC2616: 14.10
|
250
|
+
ShouldNotTransfer.member?(key) # pragmatics
|
251
|
+
@logger.debug("choose_header: `#{key}: #{value}'")
|
252
|
+
next
|
253
|
+
end
|
254
|
+
dst[key] = value
|
255
|
+
}
|
256
|
+
end
|
257
|
+
|
258
|
+
# Net::HTTP is stupid about the multiple header fields.
|
259
|
+
# Here is workaround:
|
260
|
+
def set_cookie(src, dst)
|
261
|
+
if str = src['set-cookie']
|
262
|
+
cookies = []
|
263
|
+
str.split(/,\s*/).each{|token|
|
264
|
+
if /^[^=]+;/o =~ token
|
265
|
+
cookies[-1] << ", " << token
|
266
|
+
elsif /=/o =~ token
|
267
|
+
cookies << token
|
268
|
+
else
|
269
|
+
cookies[-1] << ", " << token
|
270
|
+
end
|
271
|
+
}
|
272
|
+
dst.cookies.replace(cookies)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def set_via(h)
|
277
|
+
if @config[:ProxyVia]
|
278
|
+
if h['via']
|
279
|
+
h['via'] << ", " << @via
|
280
|
+
else
|
281
|
+
h['via'] = @via
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def setup_proxy_header(req, res)
|
287
|
+
# Choose header fields to transfer
|
288
|
+
header = Hash.new
|
289
|
+
choose_header(req, header)
|
290
|
+
set_via(header)
|
291
|
+
return header
|
292
|
+
end
|
293
|
+
|
294
|
+
def setup_upstream_proxy_authentication(req, res, header)
|
295
|
+
if upstream = proxy_uri(req, res)
|
296
|
+
if upstream.userinfo
|
297
|
+
header['proxy-authorization'] =
|
298
|
+
"Basic " + [upstream.userinfo].pack("m").delete("\n")
|
299
|
+
end
|
300
|
+
return upstream
|
301
|
+
end
|
302
|
+
return FakeProxyURI
|
303
|
+
end
|
304
|
+
|
305
|
+
def perform_proxy_request(req, res)
|
306
|
+
uri = req.request_uri
|
307
|
+
path = uri.path.dup
|
308
|
+
path << "?" << uri.query if uri.query
|
309
|
+
header = setup_proxy_header(req, res)
|
310
|
+
upstream = setup_upstream_proxy_authentication(req, res, header)
|
311
|
+
response = nil
|
312
|
+
|
313
|
+
http = Net::HTTP.new(uri.host, uri.port, upstream.host, upstream.port)
|
314
|
+
http.start do
|
315
|
+
if @config[:ProxyTimeout]
|
316
|
+
################################## these issues are
|
317
|
+
http.open_timeout = 30 # secs # necessary (maybe bacause
|
318
|
+
http.read_timeout = 60 # secs # Ruby's bug, but why?)
|
319
|
+
##################################
|
320
|
+
end
|
321
|
+
response = yield(http, path, header)
|
322
|
+
end
|
323
|
+
|
324
|
+
# Persistent connection requirements are mysterious for me.
|
325
|
+
# So I will close the connection in every response.
|
326
|
+
res['proxy-connection'] = "close"
|
327
|
+
res['connection'] = "close"
|
328
|
+
|
329
|
+
# Convert Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
|
330
|
+
res.status = response.code.to_i
|
331
|
+
choose_header(response, res)
|
332
|
+
set_cookie(response, res)
|
333
|
+
set_via(res)
|
334
|
+
res.body = response.body
|
335
|
+
end
|
336
|
+
|
337
|
+
# :stopdoc:
|
253
338
|
end
|
254
339
|
end
|
data/lib/webrick/httprequest.rb
CHANGED
@@ -8,9 +8,7 @@
|
|
8
8
|
#
|
9
9
|
# $IPR: httprequest.rb,v 1.64 2003/07/13 17:18:22 gotoyuzo Exp $
|
10
10
|
|
11
|
-
require 'timeout'
|
12
11
|
require 'uri'
|
13
|
-
|
14
12
|
require 'webrick/httpversion'
|
15
13
|
require 'webrick/httpstatus'
|
16
14
|
require 'webrick/httputils'
|
@@ -18,32 +16,141 @@ require 'webrick/cookie'
|
|
18
16
|
|
19
17
|
module WEBrick
|
20
18
|
|
19
|
+
##
|
20
|
+
# An HTTP request. This is consumed by service and do_* methods in
|
21
|
+
# WEBrick servlets
|
22
|
+
|
21
23
|
class HTTPRequest
|
22
|
-
BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ]
|
23
|
-
BUFSIZE = 1024*4
|
24
24
|
|
25
|
-
#
|
25
|
+
BODY_CONTAINABLE_METHODS = [ "POST", "PUT" ] # :nodoc:
|
26
|
+
|
27
|
+
# :section: Request line
|
28
|
+
|
29
|
+
##
|
30
|
+
# The complete request line such as:
|
31
|
+
#
|
32
|
+
# GET / HTTP/1.1
|
33
|
+
|
26
34
|
attr_reader :request_line
|
27
|
-
attr_reader :request_method, :unparsed_uri, :http_version
|
28
35
|
|
29
|
-
|
30
|
-
|
31
|
-
|
36
|
+
##
|
37
|
+
# The request method, GET, POST, PUT, etc.
|
38
|
+
|
39
|
+
attr_reader :request_method
|
40
|
+
|
41
|
+
##
|
42
|
+
# The unparsed URI of the request
|
43
|
+
|
44
|
+
attr_reader :unparsed_uri
|
45
|
+
|
46
|
+
##
|
47
|
+
# The HTTP version of the request
|
48
|
+
|
49
|
+
attr_reader :http_version
|
50
|
+
|
51
|
+
# :section: Request-URI
|
52
|
+
|
53
|
+
##
|
54
|
+
# The parsed URI of the request
|
55
|
+
|
56
|
+
attr_reader :request_uri
|
57
|
+
|
58
|
+
##
|
59
|
+
# The request path
|
60
|
+
|
61
|
+
attr_reader :path
|
62
|
+
|
63
|
+
##
|
64
|
+
# The script name (CGI variable)
|
65
|
+
|
66
|
+
attr_accessor :script_name
|
67
|
+
|
68
|
+
##
|
69
|
+
# The path info (CGI variable)
|
70
|
+
|
71
|
+
attr_accessor :path_info
|
72
|
+
|
73
|
+
##
|
74
|
+
# The query from the URI of the request
|
75
|
+
|
76
|
+
attr_accessor :query_string
|
77
|
+
|
78
|
+
# :section: Header and entity body
|
79
|
+
|
80
|
+
##
|
81
|
+
# The raw header of the request
|
82
|
+
|
83
|
+
attr_reader :raw_header
|
32
84
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
attr_reader :
|
85
|
+
##
|
86
|
+
# The parsed header of the request
|
87
|
+
|
88
|
+
attr_reader :header
|
89
|
+
|
90
|
+
##
|
91
|
+
# The parsed request cookies
|
92
|
+
|
93
|
+
attr_reader :cookies
|
94
|
+
|
95
|
+
##
|
96
|
+
# The Accept header value
|
97
|
+
|
98
|
+
attr_reader :accept
|
99
|
+
|
100
|
+
##
|
101
|
+
# The Accept-Charset header value
|
102
|
+
|
103
|
+
attr_reader :accept_charset
|
104
|
+
|
105
|
+
##
|
106
|
+
# The Accept-Encoding header value
|
107
|
+
|
108
|
+
attr_reader :accept_encoding
|
109
|
+
|
110
|
+
##
|
111
|
+
# The Accept-Language header value
|
112
|
+
|
113
|
+
attr_reader :accept_language
|
114
|
+
|
115
|
+
# :section:
|
116
|
+
|
117
|
+
##
|
118
|
+
# The remote user (CGI variable)
|
37
119
|
|
38
|
-
# Misc
|
39
120
|
attr_accessor :user
|
40
|
-
|
121
|
+
|
122
|
+
##
|
123
|
+
# The socket address of the server
|
124
|
+
|
125
|
+
attr_reader :addr
|
126
|
+
|
127
|
+
##
|
128
|
+
# The socket address of the client
|
129
|
+
|
130
|
+
attr_reader :peeraddr
|
131
|
+
|
132
|
+
##
|
133
|
+
# Hash of request attributes
|
134
|
+
|
41
135
|
attr_reader :attributes
|
136
|
+
|
137
|
+
##
|
138
|
+
# Is this a keep-alive connection?
|
139
|
+
|
42
140
|
attr_reader :keep_alive
|
141
|
+
|
142
|
+
##
|
143
|
+
# The local time this request was received
|
144
|
+
|
43
145
|
attr_reader :request_time
|
44
146
|
|
147
|
+
##
|
148
|
+
# Creates a new HTTP request. WEBrick::Config::HTTP is the default
|
149
|
+
# configuration.
|
150
|
+
|
45
151
|
def initialize(config)
|
46
152
|
@config = config
|
153
|
+
@buffer_size = @config[:InputBufferSize]
|
47
154
|
@logger = config[:Logger]
|
48
155
|
|
49
156
|
@request_line = @request_method =
|
@@ -72,8 +179,15 @@ module WEBrick
|
|
72
179
|
|
73
180
|
@remaining_size = nil
|
74
181
|
@socket = nil
|
182
|
+
|
183
|
+
@forwarded_proto = @forwarded_host = @forwarded_port =
|
184
|
+
@forwarded_server = @forwarded_for = nil
|
75
185
|
end
|
76
186
|
|
187
|
+
##
|
188
|
+
# Parses a request from +socket+. This is called internally by
|
189
|
+
# WEBrick::HTTPServer.
|
190
|
+
|
77
191
|
def parse(socket=nil)
|
78
192
|
@socket = socket
|
79
193
|
begin
|
@@ -98,6 +212,7 @@ module WEBrick
|
|
98
212
|
return if @unparsed_uri == "*"
|
99
213
|
|
100
214
|
begin
|
215
|
+
setup_forwarded_info
|
101
216
|
@request_uri = parse_uri(@unparsed_uri)
|
102
217
|
@path = HTTPUtils::unescape(@request_uri.path)
|
103
218
|
@path = HTTPUtils::normalize_path(@path)
|
@@ -121,12 +236,29 @@ module WEBrick
|
|
121
236
|
end
|
122
237
|
end
|
123
238
|
|
124
|
-
|
239
|
+
##
|
240
|
+
# Generate HTTP/1.1 100 continue response if the client expects it,
|
241
|
+
# otherwise does nothing.
|
242
|
+
|
243
|
+
def continue # :nodoc:
|
244
|
+
if self['expect'] == '100-continue' && @config[:HTTPVersion] >= "1.1"
|
245
|
+
@socket << "HTTP/#{@config[:HTTPVersion]} 100 continue#{CRLF}#{CRLF}"
|
246
|
+
@header.delete('expect')
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# Returns the request body.
|
252
|
+
|
253
|
+
def body(&block) # :yields: body_chunk
|
125
254
|
block ||= Proc.new{|chunk| @body << chunk }
|
126
255
|
read_body(@socket, block)
|
127
256
|
@body.empty? ? nil : @body
|
128
257
|
end
|
129
258
|
|
259
|
+
##
|
260
|
+
# Request query as a Hash
|
261
|
+
|
130
262
|
def query
|
131
263
|
unless @query
|
132
264
|
parse_query()
|
@@ -134,14 +266,23 @@ module WEBrick
|
|
134
266
|
@query
|
135
267
|
end
|
136
268
|
|
269
|
+
##
|
270
|
+
# The content-length header
|
271
|
+
|
137
272
|
def content_length
|
138
273
|
return Integer(self['content-length'])
|
139
274
|
end
|
140
275
|
|
276
|
+
##
|
277
|
+
# The content-type header
|
278
|
+
|
141
279
|
def content_type
|
142
280
|
return self['content-type']
|
143
281
|
end
|
144
282
|
|
283
|
+
##
|
284
|
+
# Retrieves +header_name+
|
285
|
+
|
145
286
|
def [](header_name)
|
146
287
|
if @header
|
147
288
|
value = @header[header_name.downcase]
|
@@ -149,18 +290,61 @@ module WEBrick
|
|
149
290
|
end
|
150
291
|
end
|
151
292
|
|
293
|
+
##
|
294
|
+
# Iterates over the request headers
|
295
|
+
|
152
296
|
def each
|
153
|
-
@header
|
154
|
-
|
155
|
-
|
156
|
-
|
297
|
+
if @header
|
298
|
+
@header.each{|k, v|
|
299
|
+
value = @header[k]
|
300
|
+
yield(k, value.empty? ? nil : value.join(", "))
|
301
|
+
}
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
##
|
306
|
+
# The host this request is for
|
307
|
+
|
308
|
+
def host
|
309
|
+
return @forwarded_host || @host
|
310
|
+
end
|
311
|
+
|
312
|
+
##
|
313
|
+
# The port this request is for
|
314
|
+
|
315
|
+
def port
|
316
|
+
return @forwarded_port || @port
|
317
|
+
end
|
318
|
+
|
319
|
+
##
|
320
|
+
# The server name this request is for
|
321
|
+
|
322
|
+
def server_name
|
323
|
+
return @forwarded_server || @config[:ServerName]
|
324
|
+
end
|
325
|
+
|
326
|
+
##
|
327
|
+
# The client's IP address
|
328
|
+
|
329
|
+
def remote_ip
|
330
|
+
return self["client-ip"] || @forwarded_for || @peeraddr[3]
|
331
|
+
end
|
332
|
+
|
333
|
+
##
|
334
|
+
# Is this an SSL request?
|
335
|
+
|
336
|
+
def ssl?
|
337
|
+
return @request_uri.scheme == "https"
|
157
338
|
end
|
158
339
|
|
340
|
+
##
|
341
|
+
# Should the connection this request was made on be kept alive?
|
342
|
+
|
159
343
|
def keep_alive?
|
160
344
|
@keep_alive
|
161
345
|
end
|
162
346
|
|
163
|
-
def to_s
|
347
|
+
def to_s # :nodoc:
|
164
348
|
ret = @request_line.dup
|
165
349
|
@raw_header.each{|line| ret << line }
|
166
350
|
ret << CRLF
|
@@ -168,7 +352,10 @@ module WEBrick
|
|
168
352
|
ret
|
169
353
|
end
|
170
354
|
|
171
|
-
|
355
|
+
##
|
356
|
+
# Consumes any remaining body and updates keep-alive status
|
357
|
+
|
358
|
+
def fixup() # :nodoc:
|
172
359
|
begin
|
173
360
|
body{|chunk| } # read remaining body
|
174
361
|
rescue HTTPStatus::Error => ex
|
@@ -180,11 +367,11 @@ module WEBrick
|
|
180
367
|
end
|
181
368
|
end
|
182
369
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
# (http://Web.Golux.Com/coar/cgi/)
|
370
|
+
# This method provides the metavariables defined by the revision 3
|
371
|
+
# of "The WWW Common Gateway Interface Version 1.1"
|
372
|
+
# http://Web.Golux.Com/coar/cgi/
|
187
373
|
|
374
|
+
def meta_vars
|
188
375
|
meta = Hash.new
|
189
376
|
|
190
377
|
cl = self["Content-Length"]
|
@@ -221,11 +408,18 @@ module WEBrick
|
|
221
408
|
|
222
409
|
private
|
223
410
|
|
411
|
+
# :stopdoc:
|
412
|
+
|
413
|
+
MAX_URI_LENGTH = 2083 # :nodoc:
|
414
|
+
|
224
415
|
def read_request_line(socket)
|
225
|
-
@request_line = read_line(socket) if socket
|
416
|
+
@request_line = read_line(socket, MAX_URI_LENGTH) if socket
|
417
|
+
if @request_line.bytesize >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
|
418
|
+
raise HTTPStatus::RequestURITooLarge
|
419
|
+
end
|
226
420
|
@request_time = Time.now
|
227
421
|
raise HTTPStatus::EOFError unless @request_line
|
228
|
-
if /^(\S+)\s+(\S
|
422
|
+
if /^(\S+)\s+(\S++)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
|
229
423
|
@request_method = $1
|
230
424
|
@unparsed_uri = $2
|
231
425
|
@http_version = HTTPVersion.new($3 ? $3 : "0.9")
|
@@ -242,20 +436,19 @@ module WEBrick
|
|
242
436
|
@raw_header << line
|
243
437
|
end
|
244
438
|
end
|
245
|
-
|
246
|
-
@header = HTTPUtils::parse_header(@raw_header)
|
247
|
-
rescue => ex
|
248
|
-
raise HTTPStatus::BadRequest, ex.message
|
249
|
-
end
|
439
|
+
@header = HTTPUtils::parse_header(@raw_header.join)
|
250
440
|
end
|
251
441
|
|
252
442
|
def parse_uri(str, scheme="http")
|
253
443
|
if @config[:Escape8bitURI]
|
254
444
|
str = HTTPUtils::escape8bit(str)
|
255
445
|
end
|
446
|
+
str.sub!(%r{\A/+}o, '/')
|
256
447
|
uri = URI::parse(str)
|
257
448
|
return uri if uri.absolute?
|
258
|
-
if
|
449
|
+
if @forwarded_host
|
450
|
+
host, port = @forwarded_host, @forwarded_port
|
451
|
+
elsif self["host"]
|
259
452
|
pattern = /\A(#{URI::REGEXP::PATTERN::HOST})(?::(\d+))?\z/n
|
260
453
|
host, port = *self['host'].scan(pattern)[0]
|
261
454
|
elsif @addr.size > 0
|
@@ -263,7 +456,7 @@ module WEBrick
|
|
263
456
|
else
|
264
457
|
host, port = @config[:ServerName], @config[:Port]
|
265
458
|
end
|
266
|
-
uri.scheme = scheme
|
459
|
+
uri.scheme = @forwarded_proto || scheme
|
267
460
|
uri.host = host
|
268
461
|
uri.port = port ? port.to_i : nil
|
269
462
|
return URI::parse(uri.to_s)
|
@@ -278,10 +471,10 @@ module WEBrick
|
|
278
471
|
end
|
279
472
|
elsif self['content-length'] || @remaining_size
|
280
473
|
@remaining_size ||= self['content-length'].to_i
|
281
|
-
while @remaining_size > 0
|
282
|
-
sz =
|
474
|
+
while @remaining_size > 0
|
475
|
+
sz = [@buffer_size, @remaining_size].min
|
283
476
|
break unless buf = read_data(socket, sz)
|
284
|
-
@remaining_size -= buf.
|
477
|
+
@remaining_size -= buf.bytesize
|
285
478
|
block.call(buf)
|
286
479
|
end
|
287
480
|
if @remaining_size > 0 && @socket.eof?
|
@@ -307,13 +500,8 @@ module WEBrick
|
|
307
500
|
def read_chunked(socket, block)
|
308
501
|
chunk_size, = read_chunk_size(socket)
|
309
502
|
while chunk_size > 0
|
310
|
-
data =
|
311
|
-
|
312
|
-
tmp = read_data(socket, chunk_size-data.size) # read chunk-data
|
313
|
-
break unless tmp
|
314
|
-
data << tmp
|
315
|
-
end
|
316
|
-
if data.nil? || data.size != chunk_size
|
503
|
+
data = read_data(socket, chunk_size) # read chunk-data
|
504
|
+
if data.nil? || data.bytesize != chunk_size
|
317
505
|
raise BadRequest, "bad chunk data size."
|
318
506
|
end
|
319
507
|
read_line(socket) # skip CRLF
|
@@ -325,10 +513,10 @@ module WEBrick
|
|
325
513
|
@remaining_size = 0
|
326
514
|
end
|
327
515
|
|
328
|
-
def _read_data(io, method, arg)
|
516
|
+
def _read_data(io, method, *arg)
|
329
517
|
begin
|
330
|
-
timeout(@config[:RequestTimeout]){
|
331
|
-
return io.__send__(method, arg)
|
518
|
+
WEBrick::Utils.timeout(@config[:RequestTimeout]){
|
519
|
+
return io.__send__(method, *arg)
|
332
520
|
}
|
333
521
|
rescue Errno::ECONNRESET
|
334
522
|
return nil
|
@@ -337,8 +525,8 @@ module WEBrick
|
|
337
525
|
end
|
338
526
|
end
|
339
527
|
|
340
|
-
def read_line(io)
|
341
|
-
_read_data(io, :gets, LF)
|
528
|
+
def read_line(io, size=4096)
|
529
|
+
_read_data(io, :gets, LF, size)
|
342
530
|
end
|
343
531
|
|
344
532
|
def read_data(io, size)
|
@@ -361,5 +549,35 @@ module WEBrick
|
|
361
549
|
raise HTTPStatus::BadRequest, ex.message
|
362
550
|
end
|
363
551
|
end
|
552
|
+
|
553
|
+
PrivateNetworkRegexp = /
|
554
|
+
^unknown$|
|
555
|
+
^((::ffff:)?127.0.0.1|::1)$|
|
556
|
+
^(::ffff:)?(10|172\.(1[6-9]|2[0-9]|3[01])|192\.168)\.
|
557
|
+
/ixo
|
558
|
+
|
559
|
+
# It's said that all X-Forwarded-* headers will contain more than one
|
560
|
+
# (comma-separated) value if the original request already contained one of
|
561
|
+
# these headers. Since we could use these values as Host header, we choose
|
562
|
+
# the initial(first) value. (apr_table_mergen() adds new value after the
|
563
|
+
# existing value with ", " prefix)
|
564
|
+
def setup_forwarded_info
|
565
|
+
if @forwarded_server = self["x-forwarded-server"]
|
566
|
+
@forwarded_server = @forwarded_server.split(",", 2).first
|
567
|
+
end
|
568
|
+
@forwarded_proto = self["x-forwarded-proto"]
|
569
|
+
if host_port = self["x-forwarded-host"]
|
570
|
+
host_port = host_port.split(",", 2).first
|
571
|
+
@forwarded_host, tmp = host_port.split(":", 2)
|
572
|
+
@forwarded_port = (tmp || (@forwarded_proto == "https" ? 443 : 80)).to_i
|
573
|
+
end
|
574
|
+
if addrs = self["x-forwarded-for"]
|
575
|
+
addrs = addrs.split(",").collect(&:strip)
|
576
|
+
addrs.reject!{|ip| PrivateNetworkRegexp =~ ip }
|
577
|
+
@forwarded_for = addrs.first
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
# :startdoc:
|
364
582
|
end
|
365
583
|
end
|