rubysl-webrick 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|