webrick 1.3.1 → 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +63 -0
  5. data/Rakefile +10 -0
  6. data/bin/console +14 -0
  7. data/bin/setup +8 -0
  8. data/lib/webrick.rb +7 -7
  9. data/lib/webrick/accesslog.rb +12 -6
  10. data/lib/webrick/cgi.rb +58 -5
  11. data/lib/webrick/compat.rb +2 -1
  12. data/lib/webrick/config.rb +47 -10
  13. data/lib/webrick/cookie.rb +69 -7
  14. data/lib/webrick/htmlutils.rb +4 -2
  15. data/lib/webrick/httpauth.rb +6 -5
  16. data/lib/webrick/httpauth/authenticator.rb +13 -8
  17. data/lib/webrick/httpauth/basicauth.rb +16 -8
  18. data/lib/webrick/httpauth/digestauth.rb +35 -32
  19. data/lib/webrick/httpauth/htdigest.rb +12 -8
  20. data/lib/webrick/httpauth/htgroup.rb +10 -6
  21. data/lib/webrick/httpauth/htpasswd.rb +46 -9
  22. data/lib/webrick/httpauth/userdb.rb +1 -0
  23. data/lib/webrick/httpproxy.rb +93 -48
  24. data/lib/webrick/httprequest.rb +201 -31
  25. data/lib/webrick/httpresponse.rb +235 -70
  26. data/lib/webrick/https.rb +90 -2
  27. data/lib/webrick/httpserver.rb +45 -15
  28. data/lib/webrick/httpservlet.rb +6 -5
  29. data/lib/webrick/httpservlet/abstract.rb +5 -6
  30. data/lib/webrick/httpservlet/cgi_runner.rb +3 -2
  31. data/lib/webrick/httpservlet/cgihandler.rb +29 -11
  32. data/lib/webrick/httpservlet/erbhandler.rb +4 -3
  33. data/lib/webrick/httpservlet/filehandler.rb +136 -65
  34. data/lib/webrick/httpservlet/prochandler.rb +15 -1
  35. data/lib/webrick/httpstatus.rb +24 -14
  36. data/lib/webrick/httputils.rb +134 -17
  37. data/lib/webrick/httpversion.rb +28 -1
  38. data/lib/webrick/log.rb +25 -5
  39. data/lib/webrick/server.rb +234 -74
  40. data/lib/webrick/ssl.rb +100 -12
  41. data/lib/webrick/utils.rb +98 -69
  42. data/lib/webrick/version.rb +6 -1
  43. data/webrick.gemspec +76 -0
  44. metadata +73 -72
  45. data/README.txt +0 -21
  46. data/sample/webrick/demo-app.rb +0 -66
  47. data/sample/webrick/demo-multipart.cgi +0 -12
  48. data/sample/webrick/demo-servlet.rb +0 -6
  49. data/sample/webrick/demo-urlencoded.cgi +0 -12
  50. data/sample/webrick/hello.cgi +0 -11
  51. data/sample/webrick/hello.rb +0 -8
  52. data/sample/webrick/httpd.rb +0 -23
  53. data/sample/webrick/httpproxy.rb +0 -25
  54. data/sample/webrick/httpsd.rb +0 -33
  55. data/test/openssl/utils.rb +0 -313
  56. data/test/ruby/envutil.rb +0 -208
  57. data/test/webrick/test_cgi.rb +0 -134
  58. data/test/webrick/test_cookie.rb +0 -131
  59. data/test/webrick/test_filehandler.rb +0 -285
  60. data/test/webrick/test_httpauth.rb +0 -167
  61. data/test/webrick/test_httpproxy.rb +0 -282
  62. data/test/webrick/test_httprequest.rb +0 -411
  63. data/test/webrick/test_httpresponse.rb +0 -49
  64. data/test/webrick/test_httpserver.rb +0 -305
  65. data/test/webrick/test_httputils.rb +0 -96
  66. data/test/webrick/test_httpversion.rb +0 -40
  67. data/test/webrick/test_server.rb +0 -67
  68. data/test/webrick/test_utils.rb +0 -64
  69. data/test/webrick/utils.rb +0 -58
  70. data/test/webrick/webrick.cgi +0 -36
  71. 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::exist?(@path)
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.new("htgroup", File::dirname(output))
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
- File::rename(tmp.path, output)
72
- rescue
73
- tmp.close(true)
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
- require 'webrick/httpauth/userdb'
11
- require 'webrick/httpauth/basicauth'
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
- open(@path,"a").close unless File::exist?(@path)
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.new("htpasswd", File::dirname(output))
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
- rescue
84
- tmp.close(true)
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
- @passwd[user] = make_passwd(realm, user, pass)
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
  ##
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #--
2
3
  # httpauth/userdb.rb -- UserDB mix-in module.
3
4
  #
@@ -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
- require "webrick/httpserver"
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
- NullReader = Object.new
19
- class << NullReader
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 resopnse and allows
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("m").delete("\n")
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 a credentials")
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 a Status-Line form the upstream server")
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 sesponse twice.
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.sysread(1024);
196
+ buf = ua.readpartial(1024);
166
197
  @logger.debug("CONNECT: #{buf.bytesize} byte from User-Agent")
167
- os.syswrite(buf)
198
+ os.write(buf)
168
199
  elsif fds[0].member?(os)
169
- buf = os.sysread(1024);
200
+ buf = os.readpartial(1024);
170
201
  @logger.debug("CONNECT: #{buf.bytesize} byte from #{host}:#{port}")
171
- ua.syswrite(buf)
202
+ ua.write(buf)
172
203
  end
173
204
  end
174
- rescue => ex
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) do |http, path, header|
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) do |http, path, header|
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) do |http, path, header|
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("m").delete("\n")
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
- http.start do
283
- if @config[:ProxyTimeout]
284
- ################################## these issues are
285
- http.open_timeout = 30 # secs # necessary (maybe bacause
286
- http.read_timeout = 60 # secs # Ruby's bug, but why?)
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
@@ -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
- require 'webrick/httpversion'
13
- require 'webrick/httpstatus'
14
- require 'webrick/httputils'
15
- require 'webrick/cookie'
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
- attr_reader :request_method, :unparsed_uri, :http_version
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
- attr_reader :request_uri, :path
31
- attr_accessor :script_name, :path_info, :query_string
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
- attr_reader :raw_header, :header, :cookies
35
- attr_reader :accept, :accept_charset
36
- attr_reader :accept_encoding, :accept_language
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
- attr_reader :addr, :peeraddr
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 /close/io =~ self["connection"]
229
+ if /\Aclose\z/io =~ self["connection"]
119
230
  @keep_alive = false
120
- elsif /keep-alive/io =~ self["connection"]
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
- def continue
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
- def body(&block)
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
- def fixup()
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} occured.")
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
- # http://Web.Golux.Com/coar/cgi/
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
- if @request_line.bytesize >= MAX_URI_LENGTH and @request_line[-1, 1] != LF
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 /chunked/io then read_chunked(socket, block)
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
- data = read_data(socket, chunk_size) # read chunk-data
384
- if data.nil? || data.bytesize != chunk_size
385
- raise BadRequest, "bad chunk data size."
386
- end
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 TimeoutError
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
- @forwarded_host, tmp = host_port.split(":", 2)
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