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.
Files changed (39) hide show
  1. checksums.yaml +14 -6
  2. data/.travis.yml +5 -6
  3. data/lib/rubysl/webrick/version.rb +1 -1
  4. data/lib/rubysl/webrick/webrick.rb +199 -2
  5. data/lib/webrick/accesslog.rb +96 -5
  6. data/lib/webrick/cgi.rb +80 -29
  7. data/lib/webrick/compat.rb +20 -0
  8. data/lib/webrick/config.rb +59 -5
  9. data/lib/webrick/cookie.rb +66 -5
  10. data/lib/webrick/htmlutils.rb +4 -1
  11. data/lib/webrick/httpauth.rb +53 -3
  12. data/lib/webrick/httpauth/authenticator.rb +53 -16
  13. data/lib/webrick/httpauth/basicauth.rb +45 -2
  14. data/lib/webrick/httpauth/digestauth.rb +82 -17
  15. data/lib/webrick/httpauth/htdigest.rb +38 -1
  16. data/lib/webrick/httpauth/htgroup.rb +32 -0
  17. data/lib/webrick/httpauth/htpasswd.rb +40 -2
  18. data/lib/webrick/httpauth/userdb.rb +27 -4
  19. data/lib/webrick/httpproxy.rb +197 -112
  20. data/lib/webrick/httprequest.rb +268 -50
  21. data/lib/webrick/httpresponse.rb +170 -33
  22. data/lib/webrick/https.rb +26 -3
  23. data/lib/webrick/httpserver.rb +75 -7
  24. data/lib/webrick/httpservlet/abstract.rb +88 -6
  25. data/lib/webrick/httpservlet/cgi_runner.rb +5 -4
  26. data/lib/webrick/httpservlet/cgihandler.rb +37 -18
  27. data/lib/webrick/httpservlet/erbhandler.rb +40 -7
  28. data/lib/webrick/httpservlet/filehandler.rb +116 -28
  29. data/lib/webrick/httpservlet/prochandler.rb +17 -4
  30. data/lib/webrick/httpstatus.rb +86 -18
  31. data/lib/webrick/httputils.rb +131 -23
  32. data/lib/webrick/httpversion.rb +28 -2
  33. data/lib/webrick/log.rb +72 -5
  34. data/lib/webrick/server.rb +158 -33
  35. data/lib/webrick/ssl.rb +78 -9
  36. data/lib/webrick/utils.rb +151 -5
  37. data/lib/webrick/version.rb +5 -1
  38. data/rubysl-webrick.gemspec +0 -1
  39. 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
- attr_accessor :auth_type # BasicAuth or DigestAuth
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
@@ -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
- NullReader = Object.new
19
- class << NullReader
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
- class HTTPProxyServer < HTTPServer
27
- def initialize(config)
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
- proxy_connect(req, res)
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
- @config[:ProxyURI]
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
- http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port)
130
- http.start{
131
- if @config[:ProxyTimeout]
132
- ################################## these issues are
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 proxy_connect(req, res)
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 sesponse twice.
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.size} byte from User-Agent")
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.size} byte from #{host}:#{port}")
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
@@ -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
- # Request line
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
- # Request-URI
30
- attr_reader :request_uri, :host, :port, :path
31
- attr_accessor :script_name, :path_info, :query_string
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
- # Header and entity body
34
- attr_reader :raw_header, :header, :cookies
35
- attr_reader :accept, :accept_charset
36
- attr_reader :accept_encoding, :accept_language
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
- attr_reader :addr, :peeraddr
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
- def body(&block)
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.each{|k, v|
154
- value = @header[k]
155
- yield(k, value.empty? ? nil : value.join(", "))
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
- def fixup()
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
- def meta_vars
184
- # This method provides the metavariables defined by the revision 3
185
- # of ``The WWW Common Gateway Interface Version 1.1''.
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+?)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line
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
- begin
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 self["host"]
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 = BUFSIZE < @remaining_size ? BUFSIZE : @remaining_size
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.size
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
- while data.size < chunk_size
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