webrick 1.3.1 → 1.5.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.

Potentially problematic release.


This version of webrick might be problematic. Click here for more details.

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/accesslog.rb +9 -1
  9. data/lib/webrick/cgi.rb +58 -5
  10. data/lib/webrick/compat.rb +2 -1
  11. data/lib/webrick/config.rb +47 -10
  12. data/lib/webrick/cookie.rb +69 -7
  13. data/lib/webrick/htmlutils.rb +4 -2
  14. data/lib/webrick/httpauth/authenticator.rb +13 -8
  15. data/lib/webrick/httpauth/basicauth.rb +16 -8
  16. data/lib/webrick/httpauth/digestauth.rb +35 -32
  17. data/lib/webrick/httpauth/htdigest.rb +12 -8
  18. data/lib/webrick/httpauth/htgroup.rb +10 -6
  19. data/lib/webrick/httpauth/htpasswd.rb +46 -9
  20. data/lib/webrick/httpauth/userdb.rb +1 -0
  21. data/lib/webrick/httpauth.rb +6 -5
  22. data/lib/webrick/httpproxy.rb +93 -48
  23. data/lib/webrick/httprequest.rb +192 -27
  24. data/lib/webrick/httpresponse.rb +221 -70
  25. data/lib/webrick/https.rb +90 -2
  26. data/lib/webrick/httpserver.rb +45 -15
  27. data/lib/webrick/httpservlet/abstract.rb +5 -6
  28. data/lib/webrick/httpservlet/cgi_runner.rb +3 -2
  29. data/lib/webrick/httpservlet/cgihandler.rb +22 -10
  30. data/lib/webrick/httpservlet/erbhandler.rb +4 -3
  31. data/lib/webrick/httpservlet/filehandler.rb +136 -65
  32. data/lib/webrick/httpservlet/prochandler.rb +15 -1
  33. data/lib/webrick/httpservlet.rb +6 -5
  34. data/lib/webrick/httpstatus.rb +24 -14
  35. data/lib/webrick/httputils.rb +133 -13
  36. data/lib/webrick/httpversion.rb +28 -1
  37. data/lib/webrick/log.rb +25 -5
  38. data/lib/webrick/server.rb +234 -74
  39. data/lib/webrick/ssl.rb +100 -12
  40. data/lib/webrick/utils.rb +98 -69
  41. data/lib/webrick/version.rb +6 -1
  42. data/lib/webrick.rb +7 -7
  43. data/webrick.gemspec +76 -0
  44. metadata +70 -69
  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/digestauth.rb -- HTTP digest access authentication
3
4
  #
@@ -11,9 +12,9 @@
11
12
  #
12
13
  # $IPR: digestauth.rb,v 1.5 2003/02/20 07:15:47 gotoyuzo Exp $
13
14
 
14
- require 'webrick/config'
15
- require 'webrick/httpstatus'
16
- require 'webrick/httpauth/authenticator'
15
+ require_relative '../config'
16
+ require_relative '../httpstatus'
17
+ require_relative 'authenticator'
17
18
  require 'digest/md5'
18
19
  require 'digest/sha1'
19
20
 
@@ -45,9 +46,22 @@ module WEBrick
45
46
  class DigestAuth
46
47
  include Authenticator
47
48
 
48
- AuthScheme = "Digest"
49
- OpaqueInfo = Struct.new(:time, :nonce, :nc)
50
- attr_reader :algorithm, :qop
49
+ AuthScheme = "Digest" # :nodoc:
50
+
51
+ ##
52
+ # Struct containing the opaque portion of the digest authentication
53
+
54
+ OpaqueInfo = Struct.new(:time, :nonce, :nc) # :nodoc:
55
+
56
+ ##
57
+ # Digest authentication algorithm
58
+
59
+ attr_reader :algorithm
60
+
61
+ ##
62
+ # Quality of protection. RFC 2617 defines "auth" and "auth-int"
63
+
64
+ attr_reader :qop
51
65
 
52
66
  ##
53
67
  # Used by UserDB to create a digest password entry
@@ -97,7 +111,7 @@ module WEBrick
97
111
  @instance_key = hexdigest(self.__id__, Time.now.to_i, Process.pid)
98
112
  @opaques = {}
99
113
  @last_nonce_expire = Time.now
100
- @mutex = Mutex.new
114
+ @mutex = Thread::Mutex.new
101
115
  end
102
116
 
103
117
  ##
@@ -115,8 +129,7 @@ module WEBrick
115
129
  end
116
130
 
117
131
  ##
118
- # Returns a challenge response which asks for for authentication
119
- # information
132
+ # Returns a challenge response which asks for authentication information
120
133
 
121
134
  def challenge(req, res, stale=false)
122
135
  nonce = generate_next_nonce(req)
@@ -142,6 +155,8 @@ module WEBrick
142
155
 
143
156
  private
144
157
 
158
+ # :stopdoc:
159
+
145
160
  MustParams = ['username','realm','nonce','uri','response']
146
161
  MustParamsAuth = ['cnonce','nc']
147
162
 
@@ -189,7 +204,7 @@ module WEBrick
189
204
 
190
205
  password = @userdb.get_passwd(@realm, auth_req['username'], @reload_db)
191
206
  unless password
192
- error('%s: the user is not allowd.', auth_req['username'])
207
+ error('%s: the user is not allowed.', auth_req['username'])
193
208
  return false
194
209
  end
195
210
 
@@ -220,9 +235,11 @@ module WEBrick
220
235
  ha2 = hexdigest(req.request_method, auth_req['uri'])
221
236
  ha2_res = hexdigest("", auth_req['uri'])
222
237
  elsif auth_req['qop'] == "auth-int"
223
- ha2 = hexdigest(req.request_method, auth_req['uri'],
224
- hexdigest(req.body))
225
- ha2_res = hexdigest("", auth_req['uri'], hexdigest(res.body))
238
+ body_digest = @h.new
239
+ req.body { |chunk| body_digest.update(chunk) }
240
+ body_digest = body_digest.hexdigest
241
+ ha2 = hexdigest(req.request_method, auth_req['uri'], body_digest)
242
+ ha2_res = hexdigest("", auth_req['uri'], body_digest)
226
243
  end
227
244
 
228
245
  if auth_req['qop'] == "auth" || auth_req['qop'] == "auth-int"
@@ -273,23 +290,8 @@ module WEBrick
273
290
 
274
291
  def split_param_value(string)
275
292
  ret = {}
276
- while string.bytesize != 0
277
- case string
278
- when /^\s*([\w\-\.\*\%\!]+)=\s*\"((\\.|[^\"])*)\"\s*,?/
279
- key = $1
280
- matched = $2
281
- string = $'
282
- ret[key] = matched.gsub(/\\(.)/, "\\1")
283
- when /^\s*([\w\-\.\*\%\!]+)=\s*([^,\"]*),?/
284
- key = $1
285
- matched = $2
286
- string = $'
287
- ret[key] = matched.clone
288
- when /^s*^,/
289
- string = $'
290
- else
291
- break
292
- end
293
+ string.scan(/\G\s*([\w\-.*%!]+)=\s*(?:\"((?>\\.|[^\"])*)\"|([^,\"]*))\s*,?/) do
294
+ ret[$1] = $3 || $2.gsub(/\\(.)/, "\\1")
293
295
  end
294
296
  ret
295
297
  end
@@ -297,7 +299,7 @@ module WEBrick
297
299
  def generate_next_nonce(req)
298
300
  now = "%012d" % req.request_time.to_i
299
301
  pk = hexdigest(now, @instance_key)[0,32]
300
- nonce = [now + ":" + pk].pack("m*").chop # it has 60 length of chars.
302
+ nonce = [now + ":" + pk].pack("m0") # it has 60 length of chars.
301
303
  nonce
302
304
  end
303
305
 
@@ -375,6 +377,7 @@ module WEBrick
375
377
  @h.hexdigest(args.join(":"))
376
378
  end
377
379
 
380
+ # :startdoc:
378
381
  end
379
382
 
380
383
  ##
@@ -384,7 +387,7 @@ module WEBrick
384
387
  include ProxyAuthenticator
385
388
 
386
389
  private
387
- def check_uri(req, auth_req)
390
+ def check_uri(req, auth_req) # :nodoc:
388
391
  return true
389
392
  end
390
393
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: false
1
2
  #
2
3
  # httpauth/htdigest.rb -- Apache compatible htdigest file
3
4
  #
@@ -7,8 +8,8 @@
7
8
  #
8
9
  # $IPR: htdigest.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
9
10
 
10
- require 'webrick/httpauth/userdb'
11
- require 'webrick/httpauth/digestauth'
11
+ require_relative 'userdb'
12
+ require_relative 'digestauth'
12
13
  require 'tempfile'
13
14
 
14
15
  module WEBrick
@@ -37,9 +38,9 @@ module WEBrick
37
38
  @path = path
38
39
  @mtime = Time.at(0)
39
40
  @digest = Hash.new
40
- @mutex = Mutex::new
41
+ @mutex = Thread::Mutex::new
41
42
  @auth_type = DigestAuth
42
- open(@path,"a").close unless File::exist?(@path)
43
+ File.open(@path,"a").close unless File.exist?(@path)
43
44
  reload
44
45
  end
45
46
 
@@ -50,7 +51,7 @@ module WEBrick
50
51
  mtime = File::mtime(@path)
51
52
  if mtime > @mtime
52
53
  @digest.clear
53
- open(@path){|io|
54
+ File.open(@path){|io|
54
55
  while line = io.gets
55
56
  line.chomp!
56
57
  user, realm, pass = line.split(/:/, 3)
@@ -70,13 +71,16 @@ module WEBrick
70
71
 
71
72
  def flush(output=nil)
72
73
  output ||= @path
73
- tmp = Tempfile.new("htpasswd", File::dirname(output))
74
+ tmp = Tempfile.create("htpasswd", File::dirname(output))
75
+ renamed = false
74
76
  begin
75
77
  each{|item| tmp.puts(item.join(":")) }
76
78
  tmp.close
77
79
  File::rename(tmp.path, output)
78
- rescue
79
- tmp.close(true)
80
+ renamed = true
81
+ ensure
82
+ tmp.close
83
+ File.unlink(tmp.path) if !renamed
80
84
  end
81
85
  end
82
86
 
@@ -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
  # httpauth.rb -- HTTP access authentication
3
4
  #
@@ -8,11 +9,11 @@
8
9
  #
9
10
  # $IPR: httpauth.rb,v 1.14 2003/07/22 19:20:42 gotoyuzo Exp $
10
11
 
11
- require 'webrick/httpauth/basicauth'
12
- require 'webrick/httpauth/digestauth'
13
- require 'webrick/httpauth/htpasswd'
14
- require 'webrick/httpauth/htdigest'
15
- require 'webrick/httpauth/htgroup'
12
+ require_relative 'httpauth/basicauth'
13
+ require_relative 'httpauth/digestauth'
14
+ require_relative 'httpauth/htpasswd'
15
+ require_relative 'httpauth/htdigest'
16
+ require_relative 'httpauth/htgroup'
16
17
 
17
18
  module WEBrick
18
19
 
@@ -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