rack 2.2.8.1 → 3.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +301 -81
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +328 -0
  6. data/SPEC.rdoc +204 -131
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -1
  9. data/lib/rack/auth/basic.rb +1 -4
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +21 -3
  12. data/lib/rack/builder.rb +102 -69
  13. data/lib/rack/cascade.rb +2 -3
  14. data/lib/rack/common_logger.rb +23 -18
  15. data/lib/rack/conditional_get.rb +18 -15
  16. data/lib/rack/constants.rb +67 -0
  17. data/lib/rack/content_length.rb +12 -16
  18. data/lib/rack/content_type.rb +8 -5
  19. data/lib/rack/deflater.rb +40 -26
  20. data/lib/rack/directory.rb +9 -3
  21. data/lib/rack/etag.rb +14 -23
  22. data/lib/rack/events.rb +4 -0
  23. data/lib/rack/files.rb +15 -17
  24. data/lib/rack/head.rb +9 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +838 -644
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/logger.rb +3 -0
  29. data/lib/rack/method_override.rb +5 -1
  30. data/lib/rack/mime.rb +14 -5
  31. data/lib/rack/mock.rb +1 -271
  32. data/lib/rack/mock_request.rb +171 -0
  33. data/lib/rack/mock_response.rb +124 -0
  34. data/lib/rack/multipart/generator.rb +7 -5
  35. data/lib/rack/multipart/parser.rb +215 -90
  36. data/lib/rack/multipart/uploaded_file.rb +4 -0
  37. data/lib/rack/multipart.rb +53 -40
  38. data/lib/rack/null_logger.rb +9 -0
  39. data/lib/rack/query_parser.rb +81 -102
  40. data/lib/rack/recursive.rb +2 -0
  41. data/lib/rack/reloader.rb +0 -2
  42. data/lib/rack/request.rb +250 -123
  43. data/lib/rack/response.rb +146 -66
  44. data/lib/rack/rewindable_input.rb +24 -5
  45. data/lib/rack/runtime.rb +7 -6
  46. data/lib/rack/sendfile.rb +30 -25
  47. data/lib/rack/show_exceptions.rb +21 -4
  48. data/lib/rack/show_status.rb +17 -7
  49. data/lib/rack/static.rb +8 -8
  50. data/lib/rack/tempfile_reaper.rb +15 -4
  51. data/lib/rack/urlmap.rb +3 -1
  52. data/lib/rack/utils.rb +232 -233
  53. data/lib/rack/version.rb +1 -9
  54. data/lib/rack.rb +13 -89
  55. metadata +15 -41
  56. data/README.rdoc +0 -320
  57. data/Rakefile +0 -130
  58. data/bin/rackup +0 -5
  59. data/contrib/rack.png +0 -0
  60. data/contrib/rack.svg +0 -150
  61. data/contrib/rack_logo.svg +0 -164
  62. data/contrib/rdoc.css +0 -412
  63. data/example/lobster.ru +0 -6
  64. data/example/protectedlobster.rb +0 -16
  65. data/example/protectedlobster.ru +0 -10
  66. data/lib/rack/auth/digest/md5.rb +0 -131
  67. data/lib/rack/auth/digest/nonce.rb +0 -54
  68. data/lib/rack/auth/digest/params.rb +0 -54
  69. data/lib/rack/auth/digest/request.rb +0 -43
  70. data/lib/rack/chunked.rb +0 -117
  71. data/lib/rack/core_ext/regexp.rb +0 -14
  72. data/lib/rack/file.rb +0 -7
  73. data/lib/rack/handler/cgi.rb +0 -59
  74. data/lib/rack/handler/fastcgi.rb +0 -100
  75. data/lib/rack/handler/lsws.rb +0 -61
  76. data/lib/rack/handler/scgi.rb +0 -71
  77. data/lib/rack/handler/thin.rb +0 -36
  78. data/lib/rack/handler/webrick.rb +0 -129
  79. data/lib/rack/handler.rb +0 -104
  80. data/lib/rack/lobster.rb +0 -70
  81. data/lib/rack/server.rb +0 -466
  82. data/lib/rack/session/abstract/id.rb +0 -523
  83. data/lib/rack/session/cookie.rb +0 -204
  84. data/lib/rack/session/memcache.rb +0 -10
  85. data/lib/rack/session/pool.rb +0 -85
  86. data/rack.gemspec +0 -46
data/lib/rack/deflater.rb CHANGED
@@ -3,6 +3,11 @@
3
3
  require "zlib"
4
4
  require "time" # for Time.httpdate
5
5
 
6
+ require_relative 'constants'
7
+ require_relative 'utils'
8
+ require_relative 'request'
9
+ require_relative 'body_proxy'
10
+
6
11
  module Rack
7
12
  # This middleware enables content encoding of http responses,
8
13
  # usually for purposes of compression.
@@ -21,8 +26,6 @@ module Rack
21
26
  # Note that despite the name, Deflater does not support the +deflate+
22
27
  # encoding.
23
28
  class Deflater
24
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
25
-
26
29
  # Creates Rack::Deflater middleware. Options:
27
30
  #
28
31
  # :if :: a lambda enabling / disabling deflation based on returned boolean value
@@ -41,11 +44,10 @@ module Rack
41
44
  end
42
45
 
43
46
  def call(env)
44
- status, headers, body = @app.call(env)
45
- headers = Utils::HeaderHash[headers]
47
+ status, headers, body = response = @app.call(env)
46
48
 
47
49
  unless should_deflate?(env, status, headers, body)
48
- return [status, headers, body]
50
+ return response
49
51
  end
50
52
 
51
53
  request = Request.new(env)
@@ -54,21 +56,23 @@ module Rack
54
56
  request.accept_encoding)
55
57
 
56
58
  # Set the Vary HTTP header.
57
- vary = headers["Vary"].to_s.split(",").map(&:strip)
58
- unless vary.include?("*") || vary.include?("Accept-Encoding")
59
- headers["Vary"] = vary.push("Accept-Encoding").join(",")
59
+ vary = headers["vary"].to_s.split(",").map(&:strip)
60
+ unless vary.include?("*") || vary.any?{|v| v.downcase == 'accept-encoding'}
61
+ headers["vary"] = vary.push("Accept-Encoding").join(",")
60
62
  end
61
63
 
62
64
  case encoding
63
65
  when "gzip"
64
- headers['Content-Encoding'] = "gzip"
66
+ headers['content-encoding'] = "gzip"
65
67
  headers.delete(CONTENT_LENGTH)
66
- mtime = headers["Last-Modified"]
68
+ mtime = headers["last-modified"]
67
69
  mtime = Time.httpdate(mtime).to_i if mtime
68
- [status, headers, GzipStream.new(body, mtime, @sync)]
70
+ response[2] = GzipStream.new(body, mtime, @sync)
71
+ response
69
72
  when "identity"
70
- [status, headers, body]
71
- when nil
73
+ response
74
+ else # when nil
75
+ # Only possible encoding values here are 'gzip', 'identity', and nil
72
76
  message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
73
77
  bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
74
78
  [406, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s }, bp]
@@ -77,6 +81,9 @@ module Rack
77
81
 
78
82
  # Body class used for gzip encoded responses.
79
83
  class GzipStream
84
+
85
+ BUFFER_LENGTH = 128 * 1_024
86
+
80
87
  # Initialize the gzip stream. Arguments:
81
88
  # body :: Response body to compress with gzip
82
89
  # mtime :: The modification time of the body, used to set the
@@ -93,19 +100,26 @@ module Rack
93
100
  @writer = block
94
101
  gzip = ::Zlib::GzipWriter.new(self)
95
102
  gzip.mtime = @mtime if @mtime
96
- @body.each { |part|
97
- # Skip empty strings, as they would result in no output,
98
- # and flushing empty parts would raise Zlib::BufError.
99
- next if part.empty?
100
-
101
- gzip.write(part)
102
- gzip.flush if @sync
103
- }
103
+ # @body.each is equivalent to @body.gets (slow)
104
+ if @body.is_a? ::File # XXX: Should probably be ::IO
105
+ while part = @body.read(BUFFER_LENGTH)
106
+ gzip.write(part)
107
+ gzip.flush if @sync
108
+ end
109
+ else
110
+ @body.each { |part|
111
+ # Skip empty strings, as they would result in no output,
112
+ # and flushing empty parts would raise Zlib::BufError.
113
+ next if part.empty?
114
+ gzip.write(part)
115
+ gzip.flush if @sync
116
+ }
117
+ end
104
118
  ensure
105
- gzip.close
119
+ gzip.finish
106
120
  end
107
121
 
108
- # Call the block passed to #each with the the gzipped data.
122
+ # Call the block passed to #each with the gzipped data.
109
123
  def write(data)
110
124
  @writer.call(data)
111
125
  end
@@ -123,13 +137,13 @@ module Rack
123
137
  # Skip compressing empty entity body responses and responses with
124
138
  # no-transform set.
125
139
  if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
126
- /\bno-transform\b/.match?(headers['Cache-Control'].to_s) ||
127
- headers['Content-Encoding']&.!~(/\bidentity\b/)
140
+ /\bno-transform\b/.match?(headers[CACHE_CONTROL].to_s) ||
141
+ headers['content-encoding']&.!~(/\bidentity\b/)
128
142
  return false
129
143
  end
130
144
 
131
145
  # Skip if @compressible_types are given and does not include request's content type
132
- return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
146
+ return false if @compressible_types && !(headers.has_key?(CONTENT_TYPE) && @compressible_types.include?(headers[CONTENT_TYPE][/[^;]*/]))
133
147
 
134
148
  # Skip if @condition lambda is given and evaluates to false
135
149
  return false if @condition && !@condition.call(env, status, headers, body)
@@ -2,6 +2,12 @@
2
2
 
3
3
  require 'time'
4
4
 
5
+ require_relative 'constants'
6
+ require_relative 'utils'
7
+ require_relative 'head'
8
+ require_relative 'mime'
9
+ require_relative 'files'
10
+
5
11
  module Rack
6
12
  # Rack::Directory serves entries below the +root+ given, according to the
7
13
  # path info of the Rack request. If a directory is found, the file's contents
@@ -106,7 +112,7 @@ table { width:100%%; }
106
112
  body = "Bad Request\n"
107
113
  [400, { CONTENT_TYPE => "text/plain",
108
114
  CONTENT_LENGTH => body.bytesize.to_s,
109
- "X-Cascade" => "pass" }, [body]]
115
+ "x-cascade" => "pass" }, [body]]
110
116
  end
111
117
 
112
118
  # Rack response to use for requests with paths outside the root, or nil if path is inside the root.
@@ -117,7 +123,7 @@ table { width:100%%; }
117
123
  body = "Forbidden\n"
118
124
  [403, { CONTENT_TYPE => "text/plain",
119
125
  CONTENT_LENGTH => body.bytesize.to_s,
120
- "X-Cascade" => "pass" }, [body]]
126
+ "x-cascade" => "pass" }, [body]]
121
127
  end
122
128
 
123
129
  # Rack response to use for directories under the root.
@@ -176,7 +182,7 @@ table { width:100%%; }
176
182
  body = "Entity not found: #{path_info}\n"
177
183
  [404, { CONTENT_TYPE => "text/plain",
178
184
  CONTENT_LENGTH => body.bytesize.to_s,
179
- "X-Cascade" => "pass" }, [body]]
185
+ "x-cascade" => "pass" }, [body]]
180
186
  end
181
187
 
182
188
  # Stolen from Ramaze
data/lib/rack/etag.rb CHANGED
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../rack'
4
3
  require 'digest/sha2'
5
4
 
5
+ require_relative 'constants'
6
+ require_relative 'utils'
7
+
6
8
  module Rack
7
- # Automatically sets the ETag header on all String bodies.
9
+ # Automatically sets the etag header on all String bodies.
8
10
  #
9
- # The ETag header is skipped if ETag or Last-Modified headers are sent or if
11
+ # The etag header is skipped if etag or last-modified headers are sent or if
10
12
  # a sendfile body (body.responds_to :to_path) is given (since such cases
11
13
  # should be handled by apache/nginx).
12
14
  #
13
- # On initialization, you can pass two parameters: a Cache-Control directive
14
- # used when Etag is absent and a directive when it is present. The first
15
+ # On initialization, you can pass two parameters: a cache-control directive
16
+ # used when etag is absent and a directive when it is present. The first
15
17
  # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
16
18
  class ETag
17
19
  ETAG_STRING = Rack::ETAG
@@ -24,16 +26,11 @@ module Rack
24
26
  end
25
27
 
26
28
  def call(env)
27
- status, headers, body = @app.call(env)
28
-
29
- headers = Utils::HeaderHash[headers]
29
+ status, headers, body = response = @app.call(env)
30
30
 
31
- if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
32
- original_body = body
33
- digest, new_body = digest_body(body)
34
- body = Rack::BodyProxy.new(new_body) do
35
- original_body.close if original_body.respond_to?(:close)
36
- end
31
+ if etag_status?(status) && body.respond_to?(:to_ary) && !skip_caching?(headers)
32
+ body = body.to_ary
33
+ digest = digest_body(body)
37
34
  headers[ETAG_STRING] = %(W/"#{digest}") if digest
38
35
  end
39
36
 
@@ -45,7 +42,7 @@ module Rack
45
42
  end
46
43
  end
47
44
 
48
- [status, headers, body]
45
+ response
49
46
  end
50
47
 
51
48
  private
@@ -54,24 +51,18 @@ module Rack
54
51
  status == 200 || status == 201
55
52
  end
56
53
 
57
- def etag_body?(body)
58
- !body.respond_to?(:to_path)
59
- end
60
-
61
54
  def skip_caching?(headers)
62
- headers.key?(ETAG_STRING) || headers.key?('Last-Modified')
55
+ headers.key?(ETAG_STRING) || headers.key?('last-modified')
63
56
  end
64
57
 
65
58
  def digest_body(body)
66
- parts = []
67
59
  digest = nil
68
60
 
69
61
  body.each do |part|
70
- parts << part
71
62
  (digest ||= Digest::SHA256.new) << part unless part.empty?
72
63
  end
73
64
 
74
- [digest && digest.hexdigest.byteslice(0, 32), parts]
65
+ digest && digest.hexdigest.byteslice(0,32)
75
66
  end
76
67
  end
77
68
  end
data/lib/rack/events.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'body_proxy'
4
+ require_relative 'request'
5
+ require_relative 'response'
6
+
3
7
  module Rack
4
8
  ### This middleware provides hooks to certain places in the request /
5
9
  # response lifecycle. This is so that middleware that don't need to filter
data/lib/rack/files.rb CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  require 'time'
4
4
 
5
+ require_relative 'constants'
6
+ require_relative 'head'
7
+ require_relative 'utils'
8
+ require_relative 'request'
9
+ require_relative 'mime'
10
+
5
11
  module Rack
6
12
  # Rack::Files serves files below the +root+ directory given, according to the
7
13
  # path info of the Rack request.
@@ -16,14 +22,6 @@ module Rack
16
22
  ALLOW_HEADER = ALLOWED_VERBS.join(', ')
17
23
  MULTIPART_BOUNDARY = 'AaB03x'
18
24
 
19
- # @todo remove in 3.0
20
- def self.method_added(name)
21
- if name == :response_body
22
- raise "#{self.class}\#response_body is no longer supported."
23
- end
24
- super
25
- end
26
-
27
25
  attr_reader :root
28
26
 
29
27
  def initialize(root, headers = {}, default_mime = 'text/plain')
@@ -41,7 +39,7 @@ module Rack
41
39
  def get(env)
42
40
  request = Rack::Request.new env
43
41
  unless ALLOWED_VERBS.include? request.request_method
44
- return fail(405, "Method Not Allowed", { 'Allow' => ALLOW_HEADER })
42
+ return fail(405, "Method Not Allowed", { 'allow' => ALLOW_HEADER })
45
43
  end
46
44
 
47
45
  path_info = Utils.unescape_path request.path_info
@@ -69,12 +67,12 @@ module Rack
69
67
 
70
68
  def serving(request, path)
71
69
  if request.options?
72
- return [200, { 'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
70
+ return [200, { 'allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
73
71
  end
74
72
  last_modified = ::File.mtime(path).httpdate
75
73
  return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
76
74
 
77
- headers = { "Last-Modified" => last_modified }
75
+ headers = { "last-modified" => last_modified }
78
76
  mime_type = mime_type path, @default_mime
79
77
  headers[CONTENT_TYPE] = mime_type if mime_type
80
78
 
@@ -91,15 +89,15 @@ module Rack
91
89
  elsif ranges.empty?
92
90
  # Unsatisfiable. Return error, and file size:
93
91
  response = fail(416, "Byte range unsatisfiable")
94
- response[1]["Content-Range"] = "bytes */#{size}"
92
+ response[1]["content-range"] = "bytes */#{size}"
95
93
  return response
96
- elsif ranges.size >= 1
94
+ else
97
95
  # Partial content
98
96
  partial_content = true
99
97
 
100
98
  if ranges.size == 1
101
99
  range = ranges[0]
102
- headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
100
+ headers["content-range"] = "bytes #{range.begin}-#{range.end}/#{size}"
103
101
  else
104
102
  headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}"
105
103
  end
@@ -164,8 +162,8 @@ module Rack
164
162
  <<-EOF
165
163
  \r
166
164
  --#{MULTIPART_BOUNDARY}\r
167
- Content-Type: #{options[:mime_type]}\r
168
- Content-Range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
165
+ content-type: #{options[:mime_type]}\r
166
+ content-range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
169
167
  \r
170
168
  EOF
171
169
  end
@@ -197,7 +195,7 @@ EOF
197
195
  {
198
196
  CONTENT_TYPE => "text/plain",
199
197
  CONTENT_LENGTH => body.size.to_s,
200
- "X-Cascade" => "pass"
198
+ "x-cascade" => "pass"
201
199
  }.merge!(headers),
202
200
  [body]
203
201
  ]
data/lib/rack/head.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'body_proxy'
5
+
3
6
  module Rack
4
7
  # Rack::Head returns an empty body for all HEAD requests. It leaves
5
8
  # all other requests unchanged.
@@ -9,17 +12,15 @@ module Rack
9
12
  end
10
13
 
11
14
  def call(env)
12
- status, headers, body = @app.call(env)
15
+ _, _, body = response = @app.call(env)
13
16
 
14
17
  if env[REQUEST_METHOD] == HEAD
15
- [
16
- status, headers, Rack::BodyProxy.new([]) do
17
- body.close if body.respond_to? :close
18
- end
19
- ]
20
- else
21
- [status, headers, body]
18
+ response[2] = Rack::BodyProxy.new([]) do
19
+ body.close if body.respond_to? :close
20
+ end
22
21
  end
22
+
23
+ response
23
24
  end
24
25
  end
25
26
  end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Rack::Headers is a Hash subclass that downcases all keys. It's designed
5
+ # to be used by rack applications that don't implement the Rack 3 SPEC
6
+ # (by using non-lowercase response header keys), automatically handling
7
+ # the downcasing of keys.
8
+ class Headers < Hash
9
+ KNOWN_HEADERS = {}
10
+ %w(
11
+ Accept-CH
12
+ Accept-Patch
13
+ Accept-Ranges
14
+ Access-Control-Allow-Credentials
15
+ Access-Control-Allow-Headers
16
+ Access-Control-Allow-Methods
17
+ Access-Control-Allow-Origin
18
+ Access-Control-Expose-Headers
19
+ Access-Control-Max-Age
20
+ Age
21
+ Allow
22
+ Alt-Svc
23
+ Cache-Control
24
+ Connection
25
+ Content-Disposition
26
+ Content-Encoding
27
+ Content-Language
28
+ Content-Length
29
+ Content-Location
30
+ Content-MD5
31
+ Content-Range
32
+ Content-Security-Policy
33
+ Content-Security-Policy-Report-Only
34
+ Content-Type
35
+ Date
36
+ Delta-Base
37
+ ETag
38
+ Expect-CT
39
+ Expires
40
+ Feature-Policy
41
+ IM
42
+ Last-Modified
43
+ Link
44
+ Location
45
+ NEL
46
+ P3P
47
+ Permissions-Policy
48
+ Pragma
49
+ Preference-Applied
50
+ Proxy-Authenticate
51
+ Public-Key-Pins
52
+ Referrer-Policy
53
+ Refresh
54
+ Report-To
55
+ Retry-After
56
+ Server
57
+ Set-Cookie
58
+ Status
59
+ Strict-Transport-Security
60
+ Timing-Allow-Origin
61
+ Tk
62
+ Trailer
63
+ Transfer-Encoding
64
+ Upgrade
65
+ Vary
66
+ Via
67
+ WWW-Authenticate
68
+ Warning
69
+ X-Cascade
70
+ X-Content-Duration
71
+ X-Content-Security-Policy
72
+ X-Content-Type-Options
73
+ X-Correlation-ID
74
+ X-Correlation-Id
75
+ X-Download-Options
76
+ X-Frame-Options
77
+ X-Permitted-Cross-Domain-Policies
78
+ X-Powered-By
79
+ X-Redirect-By
80
+ X-Request-ID
81
+ X-Request-Id
82
+ X-Runtime
83
+ X-UA-Compatible
84
+ X-WebKit-CS
85
+ X-XSS-Protection
86
+ ).each do |str|
87
+ downcased = str.downcase.freeze
88
+ KNOWN_HEADERS[str] = KNOWN_HEADERS[downcased] = downcased
89
+ end
90
+
91
+ def self.[](*items)
92
+ if items.length % 2 != 0
93
+ if items.length == 1 && items.first.is_a?(Hash)
94
+ new.merge!(items.first)
95
+ else
96
+ raise ArgumentError, "odd number of arguments for Rack::Headers"
97
+ end
98
+ else
99
+ hash = new
100
+ loop do
101
+ break if items.length == 0
102
+ key = items.shift
103
+ value = items.shift
104
+ hash[key] = value
105
+ end
106
+ hash
107
+ end
108
+ end
109
+
110
+ def [](key)
111
+ super(downcase_key(key))
112
+ end
113
+
114
+ def []=(key, value)
115
+ super(KNOWN_HEADERS[key] || key.downcase.freeze, value)
116
+ end
117
+ alias store []=
118
+
119
+ def assoc(key)
120
+ super(downcase_key(key))
121
+ end
122
+
123
+ def compare_by_identity
124
+ raise TypeError, "Rack::Headers cannot compare by identity, use regular Hash"
125
+ end
126
+
127
+ def delete(key)
128
+ super(downcase_key(key))
129
+ end
130
+
131
+ def dig(key, *a)
132
+ super(downcase_key(key), *a)
133
+ end
134
+
135
+ def fetch(key, *default, &block)
136
+ key = downcase_key(key)
137
+ super
138
+ end
139
+
140
+ def fetch_values(*a)
141
+ super(*a.map!{|key| downcase_key(key)})
142
+ end
143
+
144
+ def has_key?(key)
145
+ super(downcase_key(key))
146
+ end
147
+ alias include? has_key?
148
+ alias key? has_key?
149
+ alias member? has_key?
150
+
151
+ def invert
152
+ hash = self.class.new
153
+ each{|key, value| hash[value] = key}
154
+ hash
155
+ end
156
+
157
+ def merge(hash, &block)
158
+ dup.merge!(hash, &block)
159
+ end
160
+
161
+ def reject(&block)
162
+ hash = dup
163
+ hash.reject!(&block)
164
+ hash
165
+ end
166
+
167
+ def replace(hash)
168
+ clear
169
+ update(hash)
170
+ end
171
+
172
+ def select(&block)
173
+ hash = dup
174
+ hash.select!(&block)
175
+ hash
176
+ end
177
+
178
+ def to_proc
179
+ lambda{|x| self[x]}
180
+ end
181
+
182
+ def transform_values(&block)
183
+ dup.transform_values!(&block)
184
+ end
185
+
186
+ def update(hash, &block)
187
+ hash.each do |key, value|
188
+ self[key] = if block_given? && include?(key)
189
+ block.call(key, self[key], value)
190
+ else
191
+ value
192
+ end
193
+ end
194
+ self
195
+ end
196
+ alias merge! update
197
+
198
+ def values_at(*keys)
199
+ keys.map{|key| self[key]}
200
+ end
201
+
202
+ # :nocov:
203
+ if RUBY_VERSION >= '2.5'
204
+ # :nocov:
205
+ def slice(*a)
206
+ h = self.class.new
207
+ a.each{|k| h[k] = self[k] if has_key?(k)}
208
+ h
209
+ end
210
+
211
+ def transform_keys(&block)
212
+ dup.transform_keys!(&block)
213
+ end
214
+
215
+ def transform_keys!
216
+ hash = self.class.new
217
+ each do |k, v|
218
+ hash[yield k] = v
219
+ end
220
+ replace(hash)
221
+ end
222
+ end
223
+
224
+ # :nocov:
225
+ if RUBY_VERSION >= '3.0'
226
+ # :nocov:
227
+ def except(*a)
228
+ super(*a.map!{|key| downcase_key(key)})
229
+ end
230
+ end
231
+
232
+ private
233
+
234
+ def downcase_key(key)
235
+ key.is_a?(String) ? KNOWN_HEADERS[key] || key.downcase : key
236
+ end
237
+ end
238
+ end