rack 2.0.9.3 → 3.0.0

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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +808 -0
  3. data/CONTRIBUTING.md +142 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.md +293 -0
  6. data/SPEC.rdoc +340 -0
  7. data/lib/rack/auth/abstract/handler.rb +6 -2
  8. data/lib/rack/auth/abstract/request.rb +4 -2
  9. data/lib/rack/auth/basic.rb +7 -4
  10. data/lib/rack/auth/digest/md5.rb +1 -129
  11. data/lib/rack/auth/digest/nonce.rb +1 -51
  12. data/lib/rack/auth/digest/params.rb +1 -52
  13. data/lib/rack/auth/digest/request.rb +1 -41
  14. data/lib/rack/auth/digest.rb +256 -0
  15. data/lib/rack/body_proxy.rb +18 -15
  16. data/lib/rack/builder.rb +151 -40
  17. data/lib/rack/cascade.rb +30 -12
  18. data/lib/rack/chunked.rb +74 -23
  19. data/lib/rack/common_logger.rb +49 -36
  20. data/lib/rack/conditional_get.rb +33 -26
  21. data/lib/rack/config.rb +2 -0
  22. data/lib/rack/constants.rb +63 -0
  23. data/lib/rack/content_length.rb +13 -16
  24. data/lib/rack/content_type.rb +12 -8
  25. data/lib/rack/deflater.rb +84 -45
  26. data/lib/rack/directory.rb +90 -64
  27. data/lib/rack/etag.rb +17 -23
  28. data/lib/rack/events.rb +23 -20
  29. data/lib/rack/file.rb +5 -172
  30. data/lib/rack/files.rb +216 -0
  31. data/lib/rack/head.rb +10 -9
  32. data/lib/rack/headers.rb +154 -0
  33. data/lib/rack/lint.rb +786 -645
  34. data/lib/rack/lock.rb +4 -6
  35. data/lib/rack/logger.rb +4 -0
  36. data/lib/rack/media_type.rb +10 -5
  37. data/lib/rack/method_override.rb +8 -2
  38. data/lib/rack/mime.rb +17 -1
  39. data/lib/rack/mock.rb +2 -195
  40. data/lib/rack/mock_request.rb +166 -0
  41. data/lib/rack/mock_response.rb +126 -0
  42. data/lib/rack/multipart/generator.rb +21 -15
  43. data/lib/rack/multipart/parser.rb +161 -118
  44. data/lib/rack/multipart/uploaded_file.rb +19 -7
  45. data/lib/rack/multipart.rb +23 -41
  46. data/lib/rack/null_logger.rb +11 -0
  47. data/lib/rack/query_parser.rb +126 -65
  48. data/lib/rack/recursive.rb +9 -5
  49. data/lib/rack/reloader.rb +6 -4
  50. data/lib/rack/request.rb +331 -74
  51. data/lib/rack/response.rb +223 -70
  52. data/lib/rack/rewindable_input.rb +28 -8
  53. data/lib/rack/runtime.rb +11 -8
  54. data/lib/rack/sendfile.rb +42 -33
  55. data/lib/rack/show_exceptions.rb +35 -18
  56. data/lib/rack/show_status.rb +25 -15
  57. data/lib/rack/static.rb +30 -18
  58. data/lib/rack/tempfile_reaper.rb +16 -5
  59. data/lib/rack/urlmap.rb +14 -6
  60. data/lib/rack/utils.rb +268 -260
  61. data/lib/rack/version.rb +34 -0
  62. data/lib/rack.rb +15 -92
  63. metadata +44 -207
  64. data/HISTORY.md +0 -520
  65. data/README.rdoc +0 -316
  66. data/Rakefile +0 -116
  67. data/SPEC +0 -263
  68. data/bin/rackup +0 -4
  69. data/contrib/rack.png +0 -0
  70. data/contrib/rack.svg +0 -150
  71. data/contrib/rack_logo.svg +0 -164
  72. data/contrib/rdoc.css +0 -412
  73. data/example/lobster.ru +0 -4
  74. data/example/protectedlobster.rb +0 -14
  75. data/example/protectedlobster.ru +0 -8
  76. data/lib/rack/handler/cgi.rb +0 -60
  77. data/lib/rack/handler/fastcgi.rb +0 -100
  78. data/lib/rack/handler/lsws.rb +0 -61
  79. data/lib/rack/handler/scgi.rb +0 -70
  80. data/lib/rack/handler/thin.rb +0 -36
  81. data/lib/rack/handler/webrick.rb +0 -120
  82. data/lib/rack/handler.rb +0 -99
  83. data/lib/rack/lobster.rb +0 -70
  84. data/lib/rack/server.rb +0 -395
  85. data/lib/rack/session/abstract/id.rb +0 -510
  86. data/lib/rack/session/cookie.rb +0 -204
  87. data/lib/rack/session/memcache.rb +0 -99
  88. data/lib/rack/session/pool.rb +0 -83
  89. data/rack.gemspec +0 -34
  90. data/test/builder/an_underscore_app.rb +0 -5
  91. data/test/builder/anything.rb +0 -5
  92. data/test/builder/comment.ru +0 -4
  93. data/test/builder/end.ru +0 -5
  94. data/test/builder/line.ru +0 -1
  95. data/test/builder/options.ru +0 -2
  96. data/test/cgi/assets/folder/test.js +0 -1
  97. data/test/cgi/assets/fonts/font.eot +0 -1
  98. data/test/cgi/assets/images/image.png +0 -1
  99. data/test/cgi/assets/index.html +0 -1
  100. data/test/cgi/assets/javascripts/app.js +0 -1
  101. data/test/cgi/assets/stylesheets/app.css +0 -1
  102. data/test/cgi/lighttpd.conf +0 -26
  103. data/test/cgi/rackup_stub.rb +0 -6
  104. data/test/cgi/sample_rackup.ru +0 -5
  105. data/test/cgi/test +0 -9
  106. data/test/cgi/test+directory/test+file +0 -1
  107. data/test/cgi/test.fcgi +0 -9
  108. data/test/cgi/test.gz +0 -0
  109. data/test/cgi/test.ru +0 -5
  110. data/test/gemloader.rb +0 -10
  111. data/test/helper.rb +0 -34
  112. data/test/multipart/bad_robots +0 -259
  113. data/test/multipart/binary +0 -0
  114. data/test/multipart/content_type_and_no_filename +0 -6
  115. data/test/multipart/empty +0 -10
  116. data/test/multipart/fail_16384_nofile +0 -814
  117. data/test/multipart/file1.txt +0 -1
  118. data/test/multipart/filename_and_modification_param +0 -7
  119. data/test/multipart/filename_and_no_name +0 -6
  120. data/test/multipart/filename_with_encoded_words +0 -7
  121. data/test/multipart/filename_with_escaped_quotes +0 -6
  122. data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
  123. data/test/multipart/filename_with_null_byte +0 -7
  124. data/test/multipart/filename_with_percent_escaped_quotes +0 -6
  125. data/test/multipart/filename_with_single_quote +0 -7
  126. data/test/multipart/filename_with_unescaped_percentages +0 -6
  127. data/test/multipart/filename_with_unescaped_percentages2 +0 -6
  128. data/test/multipart/filename_with_unescaped_percentages3 +0 -6
  129. data/test/multipart/filename_with_unescaped_quotes +0 -6
  130. data/test/multipart/ie +0 -6
  131. data/test/multipart/invalid_character +0 -6
  132. data/test/multipart/mixed_files +0 -21
  133. data/test/multipart/nested +0 -10
  134. data/test/multipart/none +0 -9
  135. data/test/multipart/quoted +0 -15
  136. data/test/multipart/rack-logo.png +0 -0
  137. data/test/multipart/semicolon +0 -6
  138. data/test/multipart/text +0 -15
  139. data/test/multipart/three_files_three_fields +0 -31
  140. data/test/multipart/unity3d_wwwform +0 -11
  141. data/test/multipart/webkit +0 -32
  142. data/test/rackup/config.ru +0 -31
  143. data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
  144. data/test/spec_auth_basic.rb +0 -89
  145. data/test/spec_auth_digest.rb +0 -260
  146. data/test/spec_body_proxy.rb +0 -85
  147. data/test/spec_builder.rb +0 -233
  148. data/test/spec_cascade.rb +0 -63
  149. data/test/spec_cgi.rb +0 -84
  150. data/test/spec_chunked.rb +0 -103
  151. data/test/spec_common_logger.rb +0 -107
  152. data/test/spec_conditional_get.rb +0 -103
  153. data/test/spec_config.rb +0 -23
  154. data/test/spec_content_length.rb +0 -86
  155. data/test/spec_content_type.rb +0 -46
  156. data/test/spec_deflater.rb +0 -375
  157. data/test/spec_directory.rb +0 -148
  158. data/test/spec_etag.rb +0 -108
  159. data/test/spec_events.rb +0 -133
  160. data/test/spec_fastcgi.rb +0 -85
  161. data/test/spec_file.rb +0 -264
  162. data/test/spec_handler.rb +0 -57
  163. data/test/spec_head.rb +0 -46
  164. data/test/spec_lint.rb +0 -520
  165. data/test/spec_lobster.rb +0 -59
  166. data/test/spec_lock.rb +0 -204
  167. data/test/spec_logger.rb +0 -24
  168. data/test/spec_media_type.rb +0 -42
  169. data/test/spec_method_override.rb +0 -110
  170. data/test/spec_mime.rb +0 -51
  171. data/test/spec_mock.rb +0 -359
  172. data/test/spec_multipart.rb +0 -721
  173. data/test/spec_null_logger.rb +0 -21
  174. data/test/spec_recursive.rb +0 -75
  175. data/test/spec_request.rb +0 -1423
  176. data/test/spec_response.rb +0 -528
  177. data/test/spec_rewindable_input.rb +0 -128
  178. data/test/spec_runtime.rb +0 -50
  179. data/test/spec_sendfile.rb +0 -125
  180. data/test/spec_server.rb +0 -193
  181. data/test/spec_session_abstract_id.rb +0 -31
  182. data/test/spec_session_abstract_session_hash.rb +0 -45
  183. data/test/spec_session_cookie.rb +0 -442
  184. data/test/spec_session_memcache.rb +0 -357
  185. data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
  186. data/test/spec_session_pool.rb +0 -247
  187. data/test/spec_show_exceptions.rb +0 -93
  188. data/test/spec_show_status.rb +0 -104
  189. data/test/spec_static.rb +0 -184
  190. data/test/spec_tempfile_reaper.rb +0 -64
  191. data/test/spec_thin.rb +0 -96
  192. data/test/spec_urlmap.rb +0 -237
  193. data/test/spec_utils.rb +0 -742
  194. data/test/spec_version.rb +0 -11
  195. data/test/spec_webrick.rb +0 -206
  196. data/test/static/another/index.html +0 -1
  197. data/test/static/foo.html +0 -1
  198. data/test/static/index.html +0 -1
  199. data/test/testrequest.rb +0 -78
  200. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  201. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/file.rb CHANGED
@@ -1,176 +1,9 @@
1
- require 'time'
2
- require 'rack/utils'
3
- require 'rack/mime'
4
- require 'rack/request'
5
- require 'rack/head'
1
+ # frozen_string_literal: true
6
2
 
7
- module Rack
8
- # Rack::File serves files below the +root+ directory given, according to the
9
- # path info of the Rack request.
10
- # e.g. when Rack::File.new("/etc") is used, you can access 'passwd' file
11
- # as http://localhost:9292/passwd
12
- #
13
- # Handlers can detect if bodies are a Rack::File, and use mechanisms
14
- # like sendfile on the +path+.
15
-
16
- class File
17
- ALLOWED_VERBS = %w[GET HEAD OPTIONS]
18
- ALLOW_HEADER = ALLOWED_VERBS.join(', ')
19
-
20
- attr_reader :root
21
-
22
- def initialize(root, headers={}, default_mime = 'text/plain')
23
- @root = root
24
- @headers = headers
25
- @default_mime = default_mime
26
- @head = Rack::Head.new(lambda { |env| get env })
27
- end
28
-
29
- def call(env)
30
- # HEAD requests drop the response body, including 4xx error messages.
31
- @head.call env
32
- end
33
-
34
- def get(env)
35
- request = Rack::Request.new env
36
- unless ALLOWED_VERBS.include? request.request_method
37
- return fail(405, "Method Not Allowed", {'Allow' => ALLOW_HEADER})
38
- end
39
-
40
- path_info = Utils.unescape_path request.path_info
41
- return fail(400, "Bad Request") unless Utils.valid_path?(path_info)
42
-
43
- clean_path_info = Utils.clean_path_info(path_info)
44
- path = ::File.join(@root, clean_path_info)
45
-
46
- available = begin
47
- ::File.file?(path) && ::File.readable?(path)
48
- rescue SystemCallError
49
- false
50
- end
51
-
52
- if available
53
- serving(request, path)
54
- else
55
- fail(404, "File not found: #{path_info}")
56
- end
57
- end
58
-
59
- def serving(request, path)
60
- if request.options?
61
- return [200, {'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0'}, []]
62
- end
63
- last_modified = ::File.mtime(path).httpdate
64
- return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
65
-
66
- headers = { "Last-Modified" => last_modified }
67
- mime_type = mime_type path, @default_mime
68
- headers[CONTENT_TYPE] = mime_type if mime_type
69
-
70
- # Set custom headers
71
- @headers.each { |field, content| headers[field] = content } if @headers
72
-
73
- response = [ 200, headers ]
74
-
75
- size = filesize path
3
+ require_relative 'files'
76
4
 
77
- range = nil
78
- ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
79
- if ranges.nil? || ranges.length > 1
80
- # No ranges, or multiple ranges (which we don't support):
81
- # TODO: Support multiple byte-ranges
82
- response[0] = 200
83
- range = 0..size-1
84
- elsif ranges.empty?
85
- # Unsatisfiable. Return error, and file size:
86
- response = fail(416, "Byte range unsatisfiable")
87
- response[1]["Content-Range"] = "bytes */#{size}"
88
- return response
89
- else
90
- # Partial content:
91
- range = ranges[0]
92
- response[0] = 206
93
- response[1]["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
94
- size = range.end - range.begin + 1
95
- end
96
-
97
- response[2] = [response_body] unless response_body.nil?
98
-
99
- response[1][CONTENT_LENGTH] = size.to_s
100
- response[2] = make_body request, path, range
101
- response
102
- end
103
-
104
- class Iterator
105
- attr_reader :path, :range
106
- alias :to_path :path
107
-
108
- def initialize path, range
109
- @path = path
110
- @range = range
111
- end
112
-
113
- def each
114
- ::File.open(path, "rb") do |file|
115
- file.seek(range.begin)
116
- remaining_len = range.end-range.begin+1
117
- while remaining_len > 0
118
- part = file.read([8192, remaining_len].min)
119
- break unless part
120
- remaining_len -= part.length
121
-
122
- yield part
123
- end
124
- end
125
- end
126
-
127
- def close; end
128
- end
129
-
130
- private
131
-
132
- def make_body request, path, range
133
- if request.head?
134
- []
135
- else
136
- Iterator.new path, range
137
- end
138
- end
139
-
140
- def fail(status, body, headers = {})
141
- body += "\n"
142
-
143
- [
144
- status,
145
- {
146
- CONTENT_TYPE => "text/plain",
147
- CONTENT_LENGTH => body.size.to_s,
148
- "X-Cascade" => "pass"
149
- }.merge!(headers),
150
- [body]
151
- ]
152
- end
153
-
154
- # The MIME type for the contents of the file located at @path
155
- def mime_type path, default_mime
156
- Mime.mime_type(::File.extname(path), default_mime)
157
- end
158
-
159
- def filesize path
160
- # If response_body is present, use its size.
161
- return response_body.bytesize if response_body
162
-
163
- # We check via File::size? whether this file provides size info
164
- # via stat (e.g. /proc files often don't), otherwise we have to
165
- # figure it out by reading the whole file into memory.
166
- ::File.size?(path) || ::File.read(path).bytesize
167
- end
5
+ module Rack
6
+ warn "Rack::File is deprecated and will be removed in Rack 3.1", uplevel: 1
168
7
 
169
- # By default, the response body for file requests is nil.
170
- # In this case, the response body will be generated later
171
- # from the file at @path
172
- def response_body
173
- nil
174
- end
175
- end
8
+ File = Files
176
9
  end
data/lib/rack/files.rb ADDED
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ require_relative 'constants'
6
+ require_relative 'head'
7
+ require_relative 'utils'
8
+ require_relative 'request'
9
+ require_relative 'mime'
10
+
11
+ module Rack
12
+ # Rack::Files serves files below the +root+ directory given, according to the
13
+ # path info of the Rack request.
14
+ # e.g. when Rack::Files.new("/etc") is used, you can access 'passwd' file
15
+ # as http://localhost:9292/passwd
16
+ #
17
+ # Handlers can detect if bodies are a Rack::Files, and use mechanisms
18
+ # like sendfile on the +path+.
19
+
20
+ class Files
21
+ ALLOWED_VERBS = %w[GET HEAD OPTIONS]
22
+ ALLOW_HEADER = ALLOWED_VERBS.join(', ')
23
+ MULTIPART_BOUNDARY = 'AaB03x'
24
+
25
+ attr_reader :root
26
+
27
+ def initialize(root, headers = {}, default_mime = 'text/plain')
28
+ @root = (::File.expand_path(root) if root)
29
+ @headers = headers
30
+ @default_mime = default_mime
31
+ @head = Rack::Head.new(lambda { |env| get env })
32
+ end
33
+
34
+ def call(env)
35
+ # HEAD requests drop the response body, including 4xx error messages.
36
+ @head.call env
37
+ end
38
+
39
+ def get(env)
40
+ request = Rack::Request.new env
41
+ unless ALLOWED_VERBS.include? request.request_method
42
+ return fail(405, "Method Not Allowed", { 'allow' => ALLOW_HEADER })
43
+ end
44
+
45
+ path_info = Utils.unescape_path request.path_info
46
+ return fail(400, "Bad Request") unless Utils.valid_path?(path_info)
47
+
48
+ clean_path_info = Utils.clean_path_info(path_info)
49
+ path = ::File.join(@root, clean_path_info)
50
+
51
+ available = begin
52
+ ::File.file?(path) && ::File.readable?(path)
53
+ rescue SystemCallError
54
+ # Not sure in what conditions this exception can occur, but this
55
+ # is a safe way to handle such an error.
56
+ # :nocov:
57
+ false
58
+ # :nocov:
59
+ end
60
+
61
+ if available
62
+ serving(request, path)
63
+ else
64
+ fail(404, "File not found: #{path_info}")
65
+ end
66
+ end
67
+
68
+ def serving(request, path)
69
+ if request.options?
70
+ return [200, { 'allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
71
+ end
72
+ last_modified = ::File.mtime(path).httpdate
73
+ return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
74
+
75
+ headers = { "last-modified" => last_modified }
76
+ mime_type = mime_type path, @default_mime
77
+ headers[CONTENT_TYPE] = mime_type if mime_type
78
+
79
+ # Set custom headers
80
+ headers.merge!(@headers) if @headers
81
+
82
+ status = 200
83
+ size = filesize path
84
+
85
+ ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
86
+ if ranges.nil?
87
+ # No ranges:
88
+ ranges = [0..size - 1]
89
+ elsif ranges.empty?
90
+ # Unsatisfiable. Return error, and file size:
91
+ response = fail(416, "Byte range unsatisfiable")
92
+ response[1]["content-range"] = "bytes */#{size}"
93
+ return response
94
+ else
95
+ # Partial content
96
+ partial_content = true
97
+
98
+ if ranges.size == 1
99
+ range = ranges[0]
100
+ headers["content-range"] = "bytes #{range.begin}-#{range.end}/#{size}"
101
+ else
102
+ headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}"
103
+ end
104
+
105
+ status = 206
106
+ body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size)
107
+ size = body.bytesize
108
+ end
109
+
110
+ headers[CONTENT_LENGTH] = size.to_s
111
+
112
+ if request.head?
113
+ body = []
114
+ elsif !partial_content
115
+ body = Iterator.new(path, ranges, mime_type: mime_type, size: size)
116
+ end
117
+
118
+ [status, headers, body]
119
+ end
120
+
121
+ class BaseIterator
122
+ attr_reader :path, :ranges, :options
123
+
124
+ def initialize(path, ranges, options)
125
+ @path = path
126
+ @ranges = ranges
127
+ @options = options
128
+ end
129
+
130
+ def each
131
+ ::File.open(path, "rb") do |file|
132
+ ranges.each do |range|
133
+ yield multipart_heading(range) if multipart?
134
+
135
+ each_range_part(file, range) do |part|
136
+ yield part
137
+ end
138
+ end
139
+
140
+ yield "\r\n--#{MULTIPART_BOUNDARY}--\r\n" if multipart?
141
+ end
142
+ end
143
+
144
+ def bytesize
145
+ size = ranges.inject(0) do |sum, range|
146
+ sum += multipart_heading(range).bytesize if multipart?
147
+ sum += range.size
148
+ end
149
+ size += "\r\n--#{MULTIPART_BOUNDARY}--\r\n".bytesize if multipart?
150
+ size
151
+ end
152
+
153
+ def close; end
154
+
155
+ private
156
+
157
+ def multipart?
158
+ ranges.size > 1
159
+ end
160
+
161
+ def multipart_heading(range)
162
+ <<-EOF
163
+ \r
164
+ --#{MULTIPART_BOUNDARY}\r
165
+ content-type: #{options[:mime_type]}\r
166
+ content-range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
167
+ \r
168
+ EOF
169
+ end
170
+
171
+ def each_range_part(file, range)
172
+ file.seek(range.begin)
173
+ remaining_len = range.end - range.begin + 1
174
+ while remaining_len > 0
175
+ part = file.read([8192, remaining_len].min)
176
+ break unless part
177
+ remaining_len -= part.length
178
+
179
+ yield part
180
+ end
181
+ end
182
+ end
183
+
184
+ class Iterator < BaseIterator
185
+ alias :to_path :path
186
+ end
187
+
188
+ private
189
+
190
+ def fail(status, body, headers = {})
191
+ body += "\n"
192
+
193
+ [
194
+ status,
195
+ {
196
+ CONTENT_TYPE => "text/plain",
197
+ CONTENT_LENGTH => body.size.to_s,
198
+ "x-cascade" => "pass"
199
+ }.merge!(headers),
200
+ [body]
201
+ ]
202
+ end
203
+
204
+ # The MIME type for the contents of the file located at @path
205
+ def mime_type(path, default_mime)
206
+ Mime.mime_type(::File.extname(path), default_mime)
207
+ end
208
+
209
+ def filesize(path)
210
+ # We check via File::size? whether this file provides size info
211
+ # via stat (e.g. /proc files often don't), otherwise we have to
212
+ # figure it out by reading the whole file into memory.
213
+ ::File.size?(path) || ::File.read(path).bytesize
214
+ end
215
+ end
216
+ end
data/lib/rack/head.rb CHANGED
@@ -1,4 +1,7 @@
1
- require 'rack/body_proxy'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'constants'
4
+ require_relative 'body_proxy'
2
5
 
3
6
  module Rack
4
7
  # Rack::Head returns an empty body for all HEAD requests. It leaves
@@ -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,154 @@
1
+ module Rack
2
+ # Rack::Headers is a Hash subclass that downcases all keys. It's designed
3
+ # to be used by rack applications that don't implement the Rack 3 SPEC
4
+ # (by using non-lowercase response header keys), automatically handling
5
+ # the downcasing of keys.
6
+ class Headers < Hash
7
+ def self.[](*items)
8
+ if items.length % 2 != 0
9
+ if items.length == 1 && items.first.is_a?(Hash)
10
+ new.merge!(items.first)
11
+ else
12
+ raise ArgumentError, "odd number of arguments for Rack::Headers"
13
+ end
14
+ else
15
+ hash = new
16
+ loop do
17
+ break if items.length == 0
18
+ key = items.shift
19
+ value = items.shift
20
+ hash[key] = value
21
+ end
22
+ hash
23
+ end
24
+ end
25
+
26
+ def [](key)
27
+ super(downcase_key(key))
28
+ end
29
+
30
+ def []=(key, value)
31
+ super(key.downcase.freeze, value)
32
+ end
33
+ alias store []=
34
+
35
+ def assoc(key)
36
+ super(downcase_key(key))
37
+ end
38
+
39
+ def compare_by_identity
40
+ raise TypeError, "Rack::Headers cannot compare by identity, use regular Hash"
41
+ end
42
+
43
+ def delete(key)
44
+ super(downcase_key(key))
45
+ end
46
+
47
+ def dig(key, *a)
48
+ super(downcase_key(key), *a)
49
+ end
50
+
51
+ def fetch(key, *default, &block)
52
+ key = downcase_key(key)
53
+ super
54
+ end
55
+
56
+ def fetch_values(*a)
57
+ super(*a.map!{|key| downcase_key(key)})
58
+ end
59
+
60
+ def has_key?(key)
61
+ super(downcase_key(key))
62
+ end
63
+ alias include? has_key?
64
+ alias key? has_key?
65
+ alias member? has_key?
66
+
67
+ def invert
68
+ hash = self.class.new
69
+ each{|key, value| hash[value] = key}
70
+ hash
71
+ end
72
+
73
+ def merge(hash, &block)
74
+ dup.merge!(hash, &block)
75
+ end
76
+
77
+ def reject(&block)
78
+ hash = dup
79
+ hash.reject!(&block)
80
+ hash
81
+ end
82
+
83
+ def replace(hash)
84
+ clear
85
+ update(hash)
86
+ end
87
+
88
+ def select(&block)
89
+ hash = dup
90
+ hash.select!(&block)
91
+ hash
92
+ end
93
+
94
+ def to_proc
95
+ lambda{|x| self[x]}
96
+ end
97
+
98
+ def transform_values(&block)
99
+ dup.transform_values!(&block)
100
+ end
101
+
102
+ def update(hash, &block)
103
+ hash.each do |key, value|
104
+ self[key] = if block_given? && include?(key)
105
+ block.call(key, self[key], value)
106
+ else
107
+ value
108
+ end
109
+ end
110
+ self
111
+ end
112
+ alias merge! update
113
+
114
+ def values_at(*keys)
115
+ keys.map{|key| self[key]}
116
+ end
117
+
118
+ # :nocov:
119
+ if RUBY_VERSION >= '2.5'
120
+ # :nocov:
121
+ def slice(*a)
122
+ h = self.class.new
123
+ a.each{|k| h[k] = self[k] if has_key?(k)}
124
+ h
125
+ end
126
+
127
+ def transform_keys(&block)
128
+ dup.transform_keys!(&block)
129
+ end
130
+
131
+ def transform_keys!
132
+ hash = self.class.new
133
+ each do |k, v|
134
+ hash[yield k] = v
135
+ end
136
+ replace(hash)
137
+ end
138
+ end
139
+
140
+ # :nocov:
141
+ if RUBY_VERSION >= '3.0'
142
+ # :nocov:
143
+ def except(*a)
144
+ super(*a.map!{|key| downcase_key(key)})
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def downcase_key(key)
151
+ key.is_a?(String) ? key.downcase : key
152
+ end
153
+ end
154
+ end