rack 2.0.9.3 → 2.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +675 -0
- data/CONTRIBUTING.md +136 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +152 -162
- data/Rakefile +37 -23
- data/{SPEC → SPEC.rdoc} +44 -15
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +1 -1
- data/lib/rack/auth/basic.rb +7 -4
- data/lib/rack/auth/digest/md5.rb +13 -11
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +4 -2
- data/lib/rack/auth/digest/request.rb +5 -3
- data/lib/rack/body_proxy.rb +15 -14
- data/lib/rack/builder.rb +116 -23
- data/lib/rack/cascade.rb +28 -12
- data/lib/rack/chunked.rb +68 -20
- data/lib/rack/common_logger.rb +33 -28
- data/lib/rack/conditional_get.rb +20 -16
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +8 -7
- data/lib/rack/content_type.rb +5 -4
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +59 -34
- data/lib/rack/directory.rb +84 -64
- data/lib/rack/etag.rb +5 -4
- data/lib/rack/events.rb +19 -20
- data/lib/rack/file.rb +4 -173
- data/lib/rack/files.rb +218 -0
- data/lib/rack/handler/cgi.rb +2 -3
- data/lib/rack/handler/fastcgi.rb +4 -4
- data/lib/rack/handler/lsws.rb +3 -3
- data/lib/rack/handler/scgi.rb +9 -8
- data/lib/rack/handler/thin.rb +17 -11
- data/lib/rack/handler/webrick.rb +15 -6
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/head.rb +1 -1
- data/lib/rack/lint.rb +72 -26
- data/lib/rack/lobster.rb +10 -10
- data/lib/rack/lock.rb +2 -1
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +97 -20
- data/lib/rack/multipart/generator.rb +17 -13
- data/lib/rack/multipart/parser.rb +58 -73
- data/lib/rack/multipart/uploaded_file.rb +15 -7
- data/lib/rack/multipart.rb +7 -4
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +53 -28
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +8 -4
- data/lib/rack/request.rb +210 -61
- data/lib/rack/response.rb +127 -44
- data/lib/rack/rewindable_input.rb +4 -3
- data/lib/rack/runtime.rb +6 -4
- data/lib/rack/sendfile.rb +13 -9
- data/lib/rack/server.rb +95 -24
- data/lib/rack/session/abstract/id.rb +33 -21
- data/lib/rack/session/cookie.rb +12 -12
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +5 -3
- data/lib/rack/show_exceptions.rb +17 -13
- data/lib/rack/show_status.rb +5 -5
- data/lib/rack/static.rb +23 -11
- data/lib/rack/tempfile_reaper.rb +1 -1
- data/lib/rack/urlmap.rb +12 -6
- data/lib/rack/utils.rb +105 -130
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +67 -73
- data/rack.gemspec +40 -28
- metadata +39 -182
- data/HISTORY.md +0 -520
- data/test/builder/an_underscore_app.rb +0 -5
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_encoded_words +0 -7
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_single_quote +0 -7
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -107
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -520
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -721
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1423
- data/test/spec_response.rb +0 -528
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -357
- data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
- data/test/spec_session_pool.rb +0 -247
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
data/lib/rack/request.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
require 'rack/media_type'
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
module Rack
|
|
5
4
|
# Rack::Request provides a convenient interface to a Rack
|
|
@@ -11,7 +10,18 @@ module Rack
|
|
|
11
10
|
# req.params["data"]
|
|
12
11
|
|
|
13
12
|
class Request
|
|
14
|
-
|
|
13
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
attr_accessor :ip_filter
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) }
|
|
20
|
+
ALLOWED_SCHEMES = %w(https http).freeze
|
|
21
|
+
SCHEME_WHITELIST = ALLOWED_SCHEMES
|
|
22
|
+
if Object.respond_to?(:deprecate_constant)
|
|
23
|
+
deprecate_constant :SCHEME_WHITELIST
|
|
24
|
+
end
|
|
15
25
|
|
|
16
26
|
def initialize(env)
|
|
17
27
|
@params = nil
|
|
@@ -78,7 +88,7 @@ module Rack
|
|
|
78
88
|
# assert_equal 'image/png,*/*', request.get_header('Accept')
|
|
79
89
|
#
|
|
80
90
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
|
81
|
-
def add_header
|
|
91
|
+
def add_header(key, v)
|
|
82
92
|
if v.nil?
|
|
83
93
|
get_header key
|
|
84
94
|
elsif has_header? key
|
|
@@ -100,7 +110,7 @@ module Rack
|
|
|
100
110
|
|
|
101
111
|
module Helpers
|
|
102
112
|
# The set of form-data media-types. Requests that do not indicate
|
|
103
|
-
# one of the media types
|
|
113
|
+
# one of the media types present in this list will not be eligible
|
|
104
114
|
# for form-data / param parsing.
|
|
105
115
|
FORM_DATA_MEDIA_TYPES = [
|
|
106
116
|
'application/x-www-form-urlencoded',
|
|
@@ -108,7 +118,7 @@ module Rack
|
|
|
108
118
|
]
|
|
109
119
|
|
|
110
120
|
# The set of media-types. Requests that do not indicate
|
|
111
|
-
# one of the media types
|
|
121
|
+
# one of the media types present in this list will not be eligible
|
|
112
122
|
# for param parsing like soap attachments or generic multiparts
|
|
113
123
|
PARSEABLE_DATA_MEDIA_TYPES = [
|
|
114
124
|
'multipart/related',
|
|
@@ -119,11 +129,23 @@ module Rack
|
|
|
119
129
|
# to include the port in a generated URI.
|
|
120
130
|
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
|
121
131
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
# The address of the client which connected to the proxy.
|
|
133
|
+
HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'
|
|
134
|
+
|
|
135
|
+
# The contents of the host/:authority header sent to the proxy.
|
|
136
|
+
HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
|
|
137
|
+
|
|
138
|
+
# The value of the scheme sent to the proxy.
|
|
139
|
+
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
|
|
140
|
+
|
|
141
|
+
# The protocol used to connect to the proxy.
|
|
142
|
+
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
|
|
143
|
+
|
|
144
|
+
# The port used to connect to the proxy.
|
|
145
|
+
HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
|
|
146
|
+
|
|
147
|
+
# Another way for specifing https scheme was used.
|
|
148
|
+
HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
|
|
127
149
|
|
|
128
150
|
def body; get_header(RACK_INPUT) end
|
|
129
151
|
def script_name; get_header(SCRIPT_NAME).to_s end
|
|
@@ -159,10 +181,10 @@ module Rack
|
|
|
159
181
|
def delete?; request_method == DELETE end
|
|
160
182
|
|
|
161
183
|
# Checks the HTTP request method (or verb) to see if it was of type GET
|
|
162
|
-
def get?; request_method == GET
|
|
184
|
+
def get?; request_method == GET end
|
|
163
185
|
|
|
164
186
|
# Checks the HTTP request method (or verb) to see if it was of type HEAD
|
|
165
|
-
def head?; request_method == HEAD
|
|
187
|
+
def head?; request_method == HEAD end
|
|
166
188
|
|
|
167
189
|
# Checks the HTTP request method (or verb) to see if it was of type OPTIONS
|
|
168
190
|
def options?; request_method == OPTIONS end
|
|
@@ -197,19 +219,52 @@ module Rack
|
|
|
197
219
|
end
|
|
198
220
|
end
|
|
199
221
|
|
|
222
|
+
# The authority of the incoming request as defined by RFC3976.
|
|
223
|
+
# https://tools.ietf.org/html/rfc3986#section-3.2
|
|
224
|
+
#
|
|
225
|
+
# In HTTP/1, this is the `host` header.
|
|
226
|
+
# In HTTP/2, this is the `:authority` pseudo-header.
|
|
200
227
|
def authority
|
|
201
|
-
|
|
228
|
+
forwarded_authority || host_authority || server_authority
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# The authority as defined by the `SERVER_NAME` and `SERVER_PORT`
|
|
232
|
+
# variables.
|
|
233
|
+
def server_authority
|
|
234
|
+
host = self.server_name
|
|
235
|
+
port = self.server_port
|
|
236
|
+
|
|
237
|
+
if host
|
|
238
|
+
if port
|
|
239
|
+
"#{host}:#{port}"
|
|
240
|
+
else
|
|
241
|
+
host
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def server_name
|
|
247
|
+
get_header(SERVER_NAME)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def server_port
|
|
251
|
+
if port = get_header(SERVER_PORT)
|
|
252
|
+
Integer(port)
|
|
253
|
+
end
|
|
202
254
|
end
|
|
203
255
|
|
|
204
256
|
def cookies
|
|
205
|
-
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |
|
|
206
|
-
set_header(
|
|
257
|
+
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key|
|
|
258
|
+
set_header(key, {})
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
string = get_header(HTTP_COOKIE)
|
|
262
|
+
|
|
263
|
+
unless string == get_header(RACK_REQUEST_COOKIE_STRING)
|
|
264
|
+
hash.replace Utils.parse_cookies_header(string)
|
|
265
|
+
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
|
207
266
|
end
|
|
208
|
-
string = get_header HTTP_COOKIE
|
|
209
267
|
|
|
210
|
-
return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
|
|
211
|
-
hash.replace Utils.parse_cookies_header get_header HTTP_COOKIE
|
|
212
|
-
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
|
213
268
|
hash
|
|
214
269
|
end
|
|
215
270
|
|
|
@@ -222,46 +277,91 @@ module Rack
|
|
|
222
277
|
get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
|
|
223
278
|
end
|
|
224
279
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
280
|
+
# The `HTTP_HOST` header.
|
|
281
|
+
def host_authority
|
|
282
|
+
get_header(HTTP_HOST)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def host_with_port(authority = self.authority)
|
|
286
|
+
host, _, port = split_authority(authority)
|
|
287
|
+
|
|
288
|
+
if port == DEFAULT_PORTS[self.scheme]
|
|
289
|
+
host
|
|
228
290
|
else
|
|
229
|
-
|
|
291
|
+
authority
|
|
230
292
|
end
|
|
231
293
|
end
|
|
232
294
|
|
|
295
|
+
# Returns a formatted host, suitable for being used in a URI.
|
|
233
296
|
def host
|
|
234
|
-
|
|
235
|
-
|
|
297
|
+
split_authority(self.authority)[0]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Returns an address suitable for being to resolve to an address.
|
|
301
|
+
# In the case of a domain name or IPv4 address, the result is the same
|
|
302
|
+
# as +host+. In the case of IPv6 or future address formats, the square
|
|
303
|
+
# brackets are removed.
|
|
304
|
+
def hostname
|
|
305
|
+
split_authority(self.authority)[1]
|
|
236
306
|
end
|
|
237
307
|
|
|
238
308
|
def port
|
|
239
|
-
if
|
|
240
|
-
port.
|
|
241
|
-
|
|
242
|
-
port
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
309
|
+
if authority = self.authority
|
|
310
|
+
_, _, port = split_authority(self.authority)
|
|
311
|
+
|
|
312
|
+
if port
|
|
313
|
+
return port
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
if forwarded_port = self.forwarded_port
|
|
318
|
+
return forwarded_port.first
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
if scheme = self.scheme
|
|
322
|
+
if port = DEFAULT_PORTS[self.scheme]
|
|
323
|
+
return port
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
self.server_port
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def forwarded_for
|
|
331
|
+
if value = get_header(HTTP_X_FORWARDED_FOR)
|
|
332
|
+
split_header(value).map do |authority|
|
|
333
|
+
split_authority(wrap_ipv6(authority))[1]
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def forwarded_port
|
|
339
|
+
if value = get_header(HTTP_X_FORWARDED_PORT)
|
|
340
|
+
split_header(value).map(&:to_i)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def forwarded_authority
|
|
345
|
+
if value = get_header(HTTP_X_FORWARDED_HOST)
|
|
346
|
+
wrap_ipv6(split_header(value).first)
|
|
249
347
|
end
|
|
250
348
|
end
|
|
251
349
|
|
|
252
350
|
def ssl?
|
|
253
|
-
scheme == 'https'
|
|
351
|
+
scheme == 'https' || scheme == 'wss'
|
|
254
352
|
end
|
|
255
353
|
|
|
256
354
|
def ip
|
|
257
|
-
remote_addrs =
|
|
355
|
+
remote_addrs = split_header(get_header('REMOTE_ADDR'))
|
|
258
356
|
remote_addrs = reject_trusted_ip_addresses(remote_addrs)
|
|
259
357
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
358
|
+
if remote_addrs.any?
|
|
359
|
+
remote_addrs.first
|
|
360
|
+
else
|
|
361
|
+
forwarded_ips = self.forwarded_for
|
|
263
362
|
|
|
264
|
-
|
|
363
|
+
reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
|
|
364
|
+
end
|
|
265
365
|
end
|
|
266
366
|
|
|
267
367
|
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
|
@@ -302,6 +402,7 @@ module Rack
|
|
|
302
402
|
def form_data?
|
|
303
403
|
type = media_type
|
|
304
404
|
meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
|
|
405
|
+
|
|
305
406
|
(meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
|
306
407
|
end
|
|
307
408
|
|
|
@@ -337,7 +438,7 @@ module Rack
|
|
|
337
438
|
|
|
338
439
|
# Fix for Safari Ajax postings that always append \0
|
|
339
440
|
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
|
340
|
-
form_vars.slice!(-1) if form_vars
|
|
441
|
+
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
|
341
442
|
|
|
342
443
|
set_header RACK_REQUEST_FORM_VARS, form_vars
|
|
343
444
|
set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
|
|
@@ -356,8 +457,6 @@ module Rack
|
|
|
356
457
|
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
|
357
458
|
def params
|
|
358
459
|
self.GET.merge(self.POST)
|
|
359
|
-
rescue EOFError
|
|
360
|
-
self.GET.dup
|
|
361
460
|
end
|
|
362
461
|
|
|
363
462
|
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
|
@@ -386,13 +485,12 @@ module Rack
|
|
|
386
485
|
#
|
|
387
486
|
# <tt>env['rack.input']</tt> is not touched.
|
|
388
487
|
def delete_param(k)
|
|
389
|
-
|
|
488
|
+
post_value, get_value = self.POST.delete(k), self.GET.delete(k)
|
|
489
|
+
post_value || get_value
|
|
390
490
|
end
|
|
391
491
|
|
|
392
492
|
def base_url
|
|
393
|
-
|
|
394
|
-
url << ":#{port}" if port != DEFAULT_PORTS[scheme]
|
|
395
|
-
url
|
|
493
|
+
"#{scheme}://#{host_with_port}"
|
|
396
494
|
end
|
|
397
495
|
|
|
398
496
|
# Tries to return a remake of the original request URL as a string.
|
|
@@ -417,7 +515,7 @@ module Rack
|
|
|
417
515
|
end
|
|
418
516
|
|
|
419
517
|
def trusted_proxy?(ip)
|
|
420
|
-
ip
|
|
518
|
+
Rack::Request.ip_filter.call(ip)
|
|
421
519
|
end
|
|
422
520
|
|
|
423
521
|
# shortcut for <tt>request.params[key]</tt>
|
|
@@ -449,6 +547,20 @@ module Rack
|
|
|
449
547
|
|
|
450
548
|
def default_session; {}; end
|
|
451
549
|
|
|
550
|
+
# Assist with compatibility when processing `X-Forwarded-For`.
|
|
551
|
+
def wrap_ipv6(host)
|
|
552
|
+
# Even thought IPv6 addresses should be wrapped in square brackets,
|
|
553
|
+
# sometimes this is not done in various legacy/underspecified headers.
|
|
554
|
+
# So we try to fix this situation for compatibility reasons.
|
|
555
|
+
|
|
556
|
+
# Try to detect IPv6 addresses which aren't escaped yet:
|
|
557
|
+
if !host.start_with?('[') && host.count(':') > 1
|
|
558
|
+
"[#{host}]"
|
|
559
|
+
else
|
|
560
|
+
host
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
|
|
452
564
|
def parse_http_accept_header(header)
|
|
453
565
|
header.to_s.split(/\s*,\s*/).map do |part|
|
|
454
566
|
attribute, parameters = part.split(/\s*;\s*/, 2)
|
|
@@ -464,7 +576,7 @@ module Rack
|
|
|
464
576
|
Utils.default_query_parser
|
|
465
577
|
end
|
|
466
578
|
|
|
467
|
-
def parse_query(qs, d='&')
|
|
579
|
+
def parse_query(qs, d = '&')
|
|
468
580
|
query_parser.parse_nested_query(qs, d)
|
|
469
581
|
end
|
|
470
582
|
|
|
@@ -472,8 +584,39 @@ module Rack
|
|
|
472
584
|
Rack::Multipart.extract_multipart(self, query_parser)
|
|
473
585
|
end
|
|
474
586
|
|
|
475
|
-
def
|
|
476
|
-
|
|
587
|
+
def split_header(value)
|
|
588
|
+
value ? value.strip.split(/[,\s]+/) : []
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
AUTHORITY = /
|
|
592
|
+
# The host:
|
|
593
|
+
(?<host>
|
|
594
|
+
# An IPv6 address:
|
|
595
|
+
(\[(?<ip6>.*)\])
|
|
596
|
+
|
|
|
597
|
+
# An IPv4 address:
|
|
598
|
+
(?<ip4>[\d\.]+)
|
|
599
|
+
|
|
|
600
|
+
# A hostname:
|
|
601
|
+
(?<name>[a-zA-Z0-9\.\-]+)
|
|
602
|
+
)
|
|
603
|
+
# The optional port:
|
|
604
|
+
(:(?<port>\d+))?
|
|
605
|
+
/x
|
|
606
|
+
|
|
607
|
+
private_constant :AUTHORITY
|
|
608
|
+
|
|
609
|
+
def split_authority(authority)
|
|
610
|
+
if match = AUTHORITY.match(authority)
|
|
611
|
+
if address = match[:ip6]
|
|
612
|
+
return match[:host], address, match[:port]&.to_i
|
|
613
|
+
else
|
|
614
|
+
return match[:host], match[:host], match[:port]&.to_i
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# Give up!
|
|
619
|
+
return authority, authority, nil
|
|
477
620
|
end
|
|
478
621
|
|
|
479
622
|
def reject_trusted_ip_addresses(ip_addresses)
|
|
@@ -481,16 +624,22 @@ module Rack
|
|
|
481
624
|
end
|
|
482
625
|
|
|
483
626
|
def forwarded_scheme
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
]
|
|
627
|
+
allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
|
|
628
|
+
allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
|
|
629
|
+
end
|
|
488
630
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
631
|
+
def allowed_scheme(header)
|
|
632
|
+
header if ALLOWED_SCHEMES.include?(header)
|
|
633
|
+
end
|
|
492
634
|
|
|
493
|
-
|
|
635
|
+
def extract_proto_header(header)
|
|
636
|
+
if header
|
|
637
|
+
if (comma_index = header.index(','))
|
|
638
|
+
header[0, comma_index]
|
|
639
|
+
else
|
|
640
|
+
header
|
|
641
|
+
end
|
|
642
|
+
end
|
|
494
643
|
end
|
|
495
644
|
end
|
|
496
645
|
|
data/lib/rack/response.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require 'rack/body_proxy'
|
|
4
|
-
require 'rack/media_type'
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
5
3
|
require 'time'
|
|
6
4
|
|
|
7
5
|
module Rack
|
|
@@ -17,38 +15,57 @@ module Rack
|
|
|
17
15
|
# +write+ are synchronous with the Rack response.
|
|
18
16
|
#
|
|
19
17
|
# Your application's +call+ should end returning Response#finish.
|
|
20
|
-
|
|
21
18
|
class Response
|
|
19
|
+
def self.[](status, headers, body)
|
|
20
|
+
self.new(body, status, headers)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
CHUNKED = 'chunked'
|
|
24
|
+
STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
|
|
25
|
+
|
|
22
26
|
attr_accessor :length, :status, :body
|
|
23
|
-
attr_reader :
|
|
24
|
-
alias headers header
|
|
27
|
+
attr_reader :headers
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
# @deprecated Use {#headers} instead.
|
|
30
|
+
alias header headers
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
# Initialize the response object with the specified body, status
|
|
33
|
+
# and headers.
|
|
34
|
+
#
|
|
35
|
+
# @param body [nil, #each, #to_str] the response body.
|
|
36
|
+
# @param status [Integer] the integer status as defined by the
|
|
37
|
+
# HTTP protocol RFCs.
|
|
38
|
+
# @param headers [#each] a list of key-value header pairs which
|
|
39
|
+
# conform to the HTTP protocol RFCs.
|
|
40
|
+
#
|
|
41
|
+
# Providing a body which responds to #to_str is legacy behaviour.
|
|
42
|
+
def initialize(body = nil, status = 200, headers = {})
|
|
29
43
|
@status = status.to_i
|
|
30
|
-
@
|
|
44
|
+
@headers = Utils::HeaderHash[headers]
|
|
31
45
|
|
|
32
|
-
@writer
|
|
33
|
-
@block = nil
|
|
34
|
-
@length = 0
|
|
46
|
+
@writer = self.method(:append)
|
|
35
47
|
|
|
36
|
-
@
|
|
48
|
+
@block = nil
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
# Keep track of whether we have expanded the user supplied body.
|
|
51
|
+
if body.nil?
|
|
52
|
+
@body = []
|
|
53
|
+
@buffered = true
|
|
54
|
+
@length = 0
|
|
55
|
+
elsif body.respond_to?(:to_str)
|
|
56
|
+
@body = [body]
|
|
57
|
+
@buffered = true
|
|
58
|
+
@length = body.to_str.bytesize
|
|
44
59
|
else
|
|
45
|
-
|
|
60
|
+
@body = body
|
|
61
|
+
@buffered = false
|
|
62
|
+
@length = 0
|
|
46
63
|
end
|
|
47
64
|
|
|
48
|
-
yield self
|
|
65
|
+
yield self if block_given?
|
|
49
66
|
end
|
|
50
67
|
|
|
51
|
-
def redirect(target, status=302)
|
|
68
|
+
def redirect(target, status = 302)
|
|
52
69
|
self.status = status
|
|
53
70
|
self.location = target
|
|
54
71
|
end
|
|
@@ -57,42 +74,49 @@ module Rack
|
|
|
57
74
|
CHUNKED == get_header(TRANSFER_ENCODING)
|
|
58
75
|
end
|
|
59
76
|
|
|
77
|
+
# Generate a response array consistent with the requirements of the SPEC.
|
|
78
|
+
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
|
79
|
+
# which is suitable to be returned from the middleware `#call(env)` method.
|
|
60
80
|
def finish(&block)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if [204, 304].include?(status.to_i)
|
|
81
|
+
if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
|
|
64
82
|
delete_header CONTENT_TYPE
|
|
65
83
|
delete_header CONTENT_LENGTH
|
|
66
84
|
close
|
|
67
|
-
[status
|
|
85
|
+
return [@status, @headers, []]
|
|
68
86
|
else
|
|
69
|
-
|
|
87
|
+
if block_given?
|
|
88
|
+
@block = block
|
|
89
|
+
return [@status, @headers, self]
|
|
90
|
+
else
|
|
91
|
+
return [@status, @headers, @body]
|
|
92
|
+
end
|
|
70
93
|
end
|
|
71
94
|
end
|
|
95
|
+
|
|
72
96
|
alias to_a finish # For *response
|
|
73
|
-
alias to_ary finish # For implicit-splat on Ruby 1.9.2
|
|
74
97
|
|
|
75
98
|
def each(&callback)
|
|
76
99
|
@body.each(&callback)
|
|
77
|
-
@
|
|
78
|
-
|
|
100
|
+
@buffered = true
|
|
101
|
+
|
|
102
|
+
if @block
|
|
103
|
+
@writer = callback
|
|
104
|
+
@block.call(self)
|
|
105
|
+
end
|
|
79
106
|
end
|
|
80
107
|
|
|
81
108
|
# Append to body and update Content-Length.
|
|
82
109
|
#
|
|
83
110
|
# NOTE: Do not mix #write and direct #body access!
|
|
84
111
|
#
|
|
85
|
-
def write(
|
|
86
|
-
|
|
87
|
-
@length += s.bytesize unless chunked?
|
|
88
|
-
@writer.call s
|
|
112
|
+
def write(chunk)
|
|
113
|
+
buffered_body!
|
|
89
114
|
|
|
90
|
-
|
|
91
|
-
str
|
|
115
|
+
@writer.call(chunk.to_s)
|
|
92
116
|
end
|
|
93
117
|
|
|
94
118
|
def close
|
|
95
|
-
body.close if body.respond_to?(:close)
|
|
119
|
+
@body.close if @body.respond_to?(:close)
|
|
96
120
|
end
|
|
97
121
|
|
|
98
122
|
def empty?
|
|
@@ -144,7 +168,7 @@ module Rack
|
|
|
144
168
|
# assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
|
|
145
169
|
#
|
|
146
170
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
|
147
|
-
def add_header
|
|
171
|
+
def add_header(key, v)
|
|
148
172
|
if v.nil?
|
|
149
173
|
get_header key
|
|
150
174
|
elsif has_header? key
|
|
@@ -154,10 +178,16 @@ module Rack
|
|
|
154
178
|
end
|
|
155
179
|
end
|
|
156
180
|
|
|
181
|
+
# Get the content type of the response.
|
|
157
182
|
def content_type
|
|
158
183
|
get_header CONTENT_TYPE
|
|
159
184
|
end
|
|
160
185
|
|
|
186
|
+
# Set the content type of the response.
|
|
187
|
+
def content_type=(content_type)
|
|
188
|
+
set_header CONTENT_TYPE, content_type
|
|
189
|
+
end
|
|
190
|
+
|
|
161
191
|
def media_type
|
|
162
192
|
MediaType.type(content_type)
|
|
163
193
|
end
|
|
@@ -184,7 +214,7 @@ module Rack
|
|
|
184
214
|
set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
|
|
185
215
|
end
|
|
186
216
|
|
|
187
|
-
def delete_cookie(key, value={})
|
|
217
|
+
def delete_cookie(key, value = {})
|
|
188
218
|
set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
|
|
189
219
|
end
|
|
190
220
|
|
|
@@ -192,7 +222,7 @@ module Rack
|
|
|
192
222
|
get_header SET_COOKIE
|
|
193
223
|
end
|
|
194
224
|
|
|
195
|
-
def set_cookie_header=
|
|
225
|
+
def set_cookie_header=(v)
|
|
196
226
|
set_header SET_COOKIE, v
|
|
197
227
|
end
|
|
198
228
|
|
|
@@ -200,17 +230,70 @@ module Rack
|
|
|
200
230
|
get_header CACHE_CONTROL
|
|
201
231
|
end
|
|
202
232
|
|
|
203
|
-
def cache_control=
|
|
233
|
+
def cache_control=(v)
|
|
204
234
|
set_header CACHE_CONTROL, v
|
|
205
235
|
end
|
|
206
236
|
|
|
237
|
+
# Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
|
|
238
|
+
def do_not_cache!
|
|
239
|
+
set_header CACHE_CONTROL, "no-cache, must-revalidate"
|
|
240
|
+
set_header EXPIRES, Time.now.httpdate
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Specify that the content should be cached.
|
|
244
|
+
# @param duration [Integer] The number of seconds until the cache expires.
|
|
245
|
+
# @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store".
|
|
246
|
+
def cache!(duration = 3600, directive: "public")
|
|
247
|
+
unless headers[CACHE_CONTROL] =~ /no-cache/
|
|
248
|
+
set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}"
|
|
249
|
+
set_header EXPIRES, (Time.now + duration).httpdate
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
207
253
|
def etag
|
|
208
254
|
get_header ETAG
|
|
209
255
|
end
|
|
210
256
|
|
|
211
|
-
def etag=
|
|
257
|
+
def etag=(v)
|
|
212
258
|
set_header ETAG, v
|
|
213
259
|
end
|
|
260
|
+
|
|
261
|
+
protected
|
|
262
|
+
|
|
263
|
+
def buffered_body!
|
|
264
|
+
return if @buffered
|
|
265
|
+
|
|
266
|
+
if @body.is_a?(Array)
|
|
267
|
+
# The user supplied body was an array:
|
|
268
|
+
@body = @body.compact
|
|
269
|
+
@body.each do |part|
|
|
270
|
+
@length += part.to_s.bytesize
|
|
271
|
+
end
|
|
272
|
+
else
|
|
273
|
+
# Turn the user supplied body into a buffered array:
|
|
274
|
+
body = @body
|
|
275
|
+
@body = Array.new
|
|
276
|
+
|
|
277
|
+
body.each do |part|
|
|
278
|
+
@writer.call(part.to_s)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
body.close if body.respond_to?(:close)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
@buffered = true
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def append(chunk)
|
|
288
|
+
@body << chunk
|
|
289
|
+
|
|
290
|
+
unless chunked?
|
|
291
|
+
@length += chunk.bytesize
|
|
292
|
+
set_header(CONTENT_LENGTH, @length.to_s)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
return chunk
|
|
296
|
+
end
|
|
214
297
|
end
|
|
215
298
|
|
|
216
299
|
include Helpers
|
|
@@ -221,7 +304,7 @@ module Rack
|
|
|
221
304
|
attr_reader :headers
|
|
222
305
|
attr_accessor :status
|
|
223
306
|
|
|
224
|
-
def initialize
|
|
307
|
+
def initialize(status, headers)
|
|
225
308
|
@status = status
|
|
226
309
|
@headers = headers
|
|
227
310
|
end
|