rack 2.2.7 → 3.1.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +291 -78
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +328 -0
  6. data/SPEC.rdoc +213 -136
  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 +864 -681
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/logger.rb +3 -0
  29. data/lib/rack/media_type.rb +9 -4
  30. data/lib/rack/method_override.rb +5 -1
  31. data/lib/rack/mime.rb +14 -5
  32. data/lib/rack/mock.rb +1 -271
  33. data/lib/rack/mock_request.rb +171 -0
  34. data/lib/rack/mock_response.rb +124 -0
  35. data/lib/rack/multipart/generator.rb +7 -5
  36. data/lib/rack/multipart/parser.rb +218 -91
  37. data/lib/rack/multipart/uploaded_file.rb +4 -0
  38. data/lib/rack/multipart.rb +53 -40
  39. data/lib/rack/null_logger.rb +9 -0
  40. data/lib/rack/query_parser.rb +81 -102
  41. data/lib/rack/recursive.rb +2 -0
  42. data/lib/rack/reloader.rb +0 -2
  43. data/lib/rack/request.rb +248 -123
  44. data/lib/rack/response.rb +146 -66
  45. data/lib/rack/rewindable_input.rb +24 -5
  46. data/lib/rack/runtime.rb +7 -6
  47. data/lib/rack/sendfile.rb +30 -25
  48. data/lib/rack/show_exceptions.rb +21 -4
  49. data/lib/rack/show_status.rb +17 -7
  50. data/lib/rack/static.rb +8 -8
  51. data/lib/rack/tempfile_reaper.rb +15 -4
  52. data/lib/rack/urlmap.rb +3 -1
  53. data/lib/rack/utils.rb +237 -235
  54. data/lib/rack/version.rb +1 -9
  55. data/lib/rack.rb +13 -89
  56. metadata +15 -41
  57. data/README.rdoc +0 -320
  58. data/Rakefile +0 -130
  59. data/bin/rackup +0 -5
  60. data/contrib/rack.png +0 -0
  61. data/contrib/rack.svg +0 -150
  62. data/contrib/rack_logo.svg +0 -164
  63. data/contrib/rdoc.css +0 -412
  64. data/example/lobster.ru +0 -6
  65. data/example/protectedlobster.rb +0 -16
  66. data/example/protectedlobster.ru +0 -10
  67. data/lib/rack/auth/digest/md5.rb +0 -131
  68. data/lib/rack/auth/digest/nonce.rb +0 -54
  69. data/lib/rack/auth/digest/params.rb +0 -54
  70. data/lib/rack/auth/digest/request.rb +0 -43
  71. data/lib/rack/chunked.rb +0 -117
  72. data/lib/rack/core_ext/regexp.rb +0 -14
  73. data/lib/rack/file.rb +0 -7
  74. data/lib/rack/handler/cgi.rb +0 -59
  75. data/lib/rack/handler/fastcgi.rb +0 -100
  76. data/lib/rack/handler/lsws.rb +0 -61
  77. data/lib/rack/handler/scgi.rb +0 -71
  78. data/lib/rack/handler/thin.rb +0 -36
  79. data/lib/rack/handler/webrick.rb +0 -129
  80. data/lib/rack/handler.rb +0 -104
  81. data/lib/rack/lobster.rb +0 -70
  82. data/lib/rack/server.rb +0 -466
  83. data/lib/rack/session/abstract/id.rb +0 -523
  84. data/lib/rack/session/cookie.rb +0 -203
  85. data/lib/rack/session/memcache.rb +0 -10
  86. data/lib/rack/session/pool.rb +0 -85
  87. 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