rack 2.2.3.1 → 2.2.8

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd07394d5db5fbf3068cc076eea4059190c06a6e466de13383400bec4ff12e52
4
- data.tar.gz: ae077819a035b88761b3fffe4f48d948c05e88d2b4942a6589216d929936a47d
3
+ metadata.gz: 06ce8687cfd4c7e0e35738d32303ed958d62615ee99624ce7fbab2f9268f58cc
4
+ data.tar.gz: 373ee5a240a556f70dace8667543c80329d3f990ade031b79cfb366ff5b3e051
5
5
  SHA512:
6
- metadata.gz: 405db34fbc0eca9a8cf15a7887c73a939b33fc25b1283fbc4791a2fbd25053565a19ad891c0b3704b0120157b118997a08b627b856de1dfc088705759930ced2
7
- data.tar.gz: 98d7b2f6277118a8fa4b7dd7f43eafbc5c4724474b1bb481f798df97b688ec13b61d821d62c04f5839a96ffd298d4a6a2e22f6e2be6d54b0f8485bee37372bc7
6
+ metadata.gz: e9d60d3a4798c7593a48d8bb85996c15f6fac4009d1a90a7545ca80fd38e3ed6515b17dc20f22e6052aa074de9c3502485273a6cebe0e9d6d30be596061bf1e1
7
+ data.tar.gz: 4331edc1a9fbcf9e61f8041bb4626cb2c89d55783aefa90628ae7bd3224f9bfd0ec859f8b9f4bb33709f0f9d66e53804f4296919f7749bd7a51d447d56d2aad6
data/CHANGELOG.md CHANGED
@@ -2,13 +2,54 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. For info on how to format all future additions to this file please reference [Keep A Changelog](https://keepachangelog.com/en/1.0.0/).
4
4
 
5
+ ## [2.2.7] - 2023-03-13
6
+
7
+ - Correct the year number in the changelog ([#2015](https://github.com/rack/rack/pull/2015), [@kimulab](https://github.com/kimulab))
8
+ - Support underscore in host names for Rack 2.2 (Fixes [#2070](https://github.com/rack/rack/issues/2070)) ([#2015](https://github.com/rack/rack/pull/2071), [@jeremyevans](https://github.com/jeremyevans))
9
+
10
+ ## [2.2.6.4] - 2023-03-13
11
+
12
+ - [CVE-2023-27539] Avoid ReDoS in header parsing
13
+
14
+ ## [2.2.6.3] - 2023-03-02
15
+
16
+ - [CVE-2023-27530] Introduce multipart_total_part_limit to limit total parts
17
+
18
+ ## [2.2.6.2] - 2023-01-17
19
+
20
+ - [CVE-2022-44570] Fix ReDoS in Rack::Utils.get_byte_ranges
21
+
22
+ ## [2.2.6.1] - 2023-01-17
23
+
24
+ - [CVE-2022-44571] Fix ReDoS vulnerability in multipart parser
25
+ - [CVE-2022-44572] Forbid control characters in attributes (also ReDoS)
26
+
27
+ ## [2.2.6] - 2023-01-17
28
+
29
+ - Extend `Rack::MethodOverride` to handle `QueryParser::ParamsTooDeepError` error. ([#2011](https://github.com/rack/rack/pull/2011), [@byroot](https://github.com/byroot))
30
+
31
+ ## [2.2.5] - 2022-12-27
32
+
33
+ ### Fixed
34
+
35
+ - `Rack::URLMap` uses non-deprecated form of `Regexp.new`. ([#1998](https://github.com/rack/rack/pull/1998), [@weizheheng](https://github.com/weizheheng))
36
+
37
+ ## [2.2.4] - 2022-06-30
38
+
39
+ - Better support for lower case headers in `Rack::ETag` middleware. ([#1919](https://github.com/rack/rack/pull/1919), [@ioquatix](https://github.com/ioquatix))
40
+ - Use custom exception on params too deep error. ([#1838](https://github.com/rack/rack/pull/1838), [@simi](https://github.com/simi))
41
+
5
42
  ## [2.2.3.1] - 2022-05-27
6
43
 
44
+ ### Security
45
+
7
46
  - [CVE-2022-30123] Fix shell escaping issue in Common Logger
8
47
  - [CVE-2022-30122] Restrict parsing of broken MIME attachments
9
48
 
10
49
  ## [2.2.3] - 2020-02-11
11
50
 
51
+ ### Security
52
+
12
53
  - [CVE-2020-8184] Only decode cookie values
13
54
 
14
55
  ## [2.2.2] - 2020-02-11
data/README.rdoc CHANGED
@@ -202,16 +202,30 @@ Limiting the depth prevents a possible stack overflow when parsing parameters.
202
202
 
203
203
  Defaults to 100.
204
204
 
205
- === multipart_part_limit
205
+ === multipart_file_limit
206
206
 
207
- The maximum number of parts a request can contain.
207
+ The maximum number of parts with a filename a request can contain.
208
208
  Accepting too many part can lead to the server running out of file handles.
209
209
 
210
210
  The default is 128, which means that a single request can't upload more than 128 files at once.
211
211
 
212
212
  Set to 0 for no limit.
213
213
 
214
- Can also be set via the +RACK_MULTIPART_PART_LIMIT+ environment variable.
214
+ Can also be set via the +RACK_MULTIPART_FILE_LIMIT+ environment variable.
215
+
216
+ (This is also aliased as +multipart_part_limit+ and +RACK_MULTIPART_PART_LIMIT+ for compatibility)
217
+
218
+ === multipart_total_part_limit
219
+
220
+ The maximum total number of parts a request can contain of any type, including
221
+ both file and non-file form fields.
222
+
223
+ The default is 4096, which means that a single request can't contain more than
224
+ 4096 parts.
225
+
226
+ Set to 0 for no limit.
227
+
228
+ Can also be set via the +RACK_MULTIPART_TOTAL_PART_LIMIT+ environment variable.
215
229
 
216
230
  == Changelog
217
231
 
data/SPEC.rdoc CHANGED
@@ -42,17 +42,18 @@ below.
42
42
  <tt>QUERY_STRING</tt>:: The portion of the request URL that
43
43
  follows the <tt>?</tt>, if any. May be
44
44
  empty, but is always required!
45
- <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
46
- When combined with <tt>SCRIPT_NAME</tt> and
45
+ <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
47
46
  <tt>PATH_INFO</tt>, these variables can be
48
47
  used to complete the URL. Note, however,
49
48
  that <tt>HTTP_HOST</tt>, if present,
50
49
  should be used in preference to
51
50
  <tt>SERVER_NAME</tt> for reconstructing
52
51
  the request URL.
53
- <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
54
- can never be empty strings, and so
55
- are always required.
52
+ <tt>SERVER_NAME</tt> can never be an empty
53
+ string, and so is always required.
54
+ <tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
55
+ server is running on. Should be specified if
56
+ the server is running on a non-standard port.
56
57
  <tt>HTTP_</tt> Variables:: Variables corresponding to the
57
58
  client-supplied HTTP request
58
59
  headers (i.e., variables whose
@@ -122,6 +123,9 @@ and should be prefixed uniquely. The prefix <tt>rack.</tt>
122
123
  is reserved for use with the Rack core distribution and other
123
124
  accepted specifications and must not be used otherwise.
124
125
 
126
+ The <tt>SERVER_PORT</tt> must be an Integer if set.
127
+ The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
128
+ The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
125
129
  The environment must not contain the keys
126
130
  <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
127
131
  (use the versions without <tt>HTTP_</tt>).
data/lib/rack/etag.rb CHANGED
@@ -26,6 +26,8 @@ module Rack
26
26
  def call(env)
27
27
  status, headers, body = @app.call(env)
28
28
 
29
+ headers = Utils::HeaderHash[headers]
30
+
29
31
  if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
30
32
  original_body = body
31
33
  digest, new_body = digest_body(body)
data/lib/rack/lint.rb CHANGED
@@ -40,7 +40,7 @@ module Rack
40
40
 
41
41
  def _call(env)
42
42
  ## It takes exactly one argument, the *environment*
43
- assert("No env given") { env }
43
+ raise LintError, "No env given" unless env
44
44
  check_env env
45
45
 
46
46
  env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
@@ -48,12 +48,8 @@ module Rack
48
48
 
49
49
  ## and returns an Array of exactly three values:
50
50
  ary = @app.call(env)
51
- assert("response #{ary.inspect} is not an Array , but #{ary.class}") {
52
- ary.kind_of? Array
53
- }
54
- assert("response array #{ary.inspect} has #{ary.size} elements instead of 3") {
55
- ary.size == 3
56
- }
51
+ raise LintError, "response is not an Array, but #{ary.class}" unless ary.kind_of? Array
52
+ raise LintError, "response array has #{ary.size} elements instead of 3" unless ary.size == 3
57
53
 
58
54
  status, headers, @body = ary
59
55
  ## The *status*,
@@ -78,12 +74,8 @@ module Rack
78
74
  ## The environment must be an unfrozen instance of Hash that includes
79
75
  ## CGI-like headers. The application is free to modify the
80
76
  ## environment.
81
- assert("env #{env.inspect} is not a Hash, but #{env.class}") {
82
- env.kind_of? Hash
83
- }
84
- assert("env should not be frozen, but is") {
85
- !env.frozen?
86
- }
77
+ raise LintError, "env #{env.inspect} is not a Hash, but #{env.class}" unless env.kind_of? Hash
78
+ raise LintError, "env should not be frozen, but is" if env.frozen?
87
79
 
88
80
  ##
89
81
  ## The environment is required to include these variables
@@ -195,73 +187,73 @@ module Rack
195
187
  ## The store must implement:
196
188
  if session = env[RACK_SESSION]
197
189
  ## store(key, value) (aliased as []=);
198
- assert("session #{session.inspect} must respond to store and []=") {
199
- session.respond_to?(:store) && session.respond_to?(:[]=)
200
- }
190
+ unless session.respond_to?(:store) && session.respond_to?(:[]=)
191
+ raise LintError, "session #{session.inspect} must respond to store and []="
192
+ end
201
193
 
202
194
  ## fetch(key, default = nil) (aliased as []);
203
- assert("session #{session.inspect} must respond to fetch and []") {
204
- session.respond_to?(:fetch) && session.respond_to?(:[])
205
- }
195
+ unless session.respond_to?(:fetch) && session.respond_to?(:[])
196
+ raise LintError, "session #{session.inspect} must respond to fetch and []"
197
+ end
206
198
 
207
199
  ## delete(key);
208
- assert("session #{session.inspect} must respond to delete") {
209
- session.respond_to?(:delete)
210
- }
200
+ unless session.respond_to?(:delete)
201
+ raise LintError, "session #{session.inspect} must respond to delete"
202
+ end
211
203
 
212
204
  ## clear;
213
- assert("session #{session.inspect} must respond to clear") {
214
- session.respond_to?(:clear)
215
- }
205
+ unless session.respond_to?(:clear)
206
+ raise LintError, "session #{session.inspect} must respond to clear"
207
+ end
216
208
 
217
209
  ## to_hash (returning unfrozen Hash instance);
218
- assert("session #{session.inspect} must respond to to_hash and return unfrozen Hash instance") {
219
- session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
220
- }
210
+ unless session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
211
+ raise LintError, "session #{session.inspect} must respond to to_hash and return unfrozen Hash instance"
212
+ end
221
213
  end
222
214
 
223
215
  ## <tt>rack.logger</tt>:: A common object interface for logging messages.
224
216
  ## The object must implement:
225
217
  if logger = env[RACK_LOGGER]
226
218
  ## info(message, &block)
227
- assert("logger #{logger.inspect} must respond to info") {
228
- logger.respond_to?(:info)
229
- }
219
+ unless logger.respond_to?(:info)
220
+ raise LintError, "logger #{logger.inspect} must respond to info"
221
+ end
230
222
 
231
223
  ## debug(message, &block)
232
- assert("logger #{logger.inspect} must respond to debug") {
233
- logger.respond_to?(:debug)
234
- }
224
+ unless logger.respond_to?(:debug)
225
+ raise LintError, "logger #{logger.inspect} must respond to debug"
226
+ end
235
227
 
236
228
  ## warn(message, &block)
237
- assert("logger #{logger.inspect} must respond to warn") {
238
- logger.respond_to?(:warn)
239
- }
229
+ unless logger.respond_to?(:warn)
230
+ raise LintError, "logger #{logger.inspect} must respond to warn"
231
+ end
240
232
 
241
233
  ## error(message, &block)
242
- assert("logger #{logger.inspect} must respond to error") {
243
- logger.respond_to?(:error)
244
- }
234
+ unless logger.respond_to?(:error)
235
+ raise LintError, "logger #{logger.inspect} must respond to error"
236
+ end
245
237
 
246
238
  ## fatal(message, &block)
247
- assert("logger #{logger.inspect} must respond to fatal") {
248
- logger.respond_to?(:fatal)
249
- }
239
+ unless logger.respond_to?(:fatal)
240
+ raise LintError, "logger #{logger.inspect} must respond to fatal"
241
+ end
250
242
  end
251
243
 
252
244
  ## <tt>rack.multipart.buffer_size</tt>:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
253
245
  if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
254
- assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
255
- bufsize.is_a?(Integer) && bufsize > 0
256
- }
246
+ unless bufsize.is_a?(Integer) && bufsize > 0
247
+ raise LintError, "rack.multipart.buffer_size must be an Integer > 0 if specified"
248
+ end
257
249
  end
258
250
 
259
251
  ## <tt>rack.multipart.tempfile_factory</tt>:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile.
260
252
  if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
261
- assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
253
+ raise LintError, "rack.multipart.tempfile_factory must respond to #call" unless tempfile_factory.respond_to?(:call)
262
254
  env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
263
255
  io = tempfile_factory.call(filename, content_type)
264
- assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
256
+ raise LintError, "rack.multipart.tempfile_factory return value must respond to #<<" unless io.respond_to?(:<<)
265
257
  io
266
258
  end
267
259
  end
@@ -276,32 +268,32 @@ module Rack
276
268
  %w[REQUEST_METHOD SERVER_NAME QUERY_STRING
277
269
  rack.version rack.input rack.errors
278
270
  rack.multithread rack.multiprocess rack.run_once].each { |header|
279
- assert("env missing required key #{header}") { env.include? header }
271
+ raise LintError, "env missing required key #{header}" unless env.include? header
280
272
  }
281
273
 
282
274
  ## The <tt>SERVER_PORT</tt> must be an Integer if set.
283
- assert("env[SERVER_PORT] is not an Integer") do
284
- server_port = env["SERVER_PORT"]
285
- server_port.nil? || (Integer(server_port) rescue false)
275
+ server_port = env["SERVER_PORT"]
276
+ unless server_port.nil? || (Integer(server_port) rescue false)
277
+ raise LintError, "env[SERVER_PORT] is not an Integer"
286
278
  end
287
279
 
288
280
  ## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
289
- assert("#{env[SERVER_NAME]} must be a valid authority") do
290
- URI.parse("http://#{env[SERVER_NAME]}/") rescue false
281
+ unless (URI.parse("http://#{env[SERVER_NAME]}/") rescue false)
282
+ raise LintError, "#{env[SERVER_NAME]} must be a valid authority"
291
283
  end
292
284
 
293
285
  ## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
294
- assert("#{env[HTTP_HOST]} must be a valid authority") do
295
- URI.parse("http://#{env[HTTP_HOST]}/") rescue false
286
+ unless (URI.parse("http://#{env[HTTP_HOST]}/") rescue false)
287
+ raise LintError, "#{env[HTTP_HOST]} must be a valid authority"
296
288
  end
297
289
 
298
290
  ## The environment must not contain the keys
299
291
  ## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
300
292
  ## (use the versions without <tt>HTTP_</tt>).
301
293
  %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
302
- assert("env contains #{header}, must use #{header[5, -1]}") {
303
- not env.include? header
304
- }
294
+ if env.include? header
295
+ raise LintError, "env contains #{header}, must use #{header[5, -1]}"
296
+ end
305
297
  }
306
298
 
307
299
  ## The CGI keys (named without a period) must have String values.
@@ -309,25 +301,25 @@ module Rack
309
301
  ## they should use ASCII-8BIT encoding.
310
302
  env.each { |key, value|
311
303
  next if key.include? "." # Skip extensions
312
- assert("env variable #{key} has non-string value #{value.inspect}") {
313
- value.kind_of? String
314
- }
304
+ unless value.kind_of? String
305
+ raise LintError, "env variable #{key} has non-string value #{value.inspect}"
306
+ end
315
307
  next if value.encoding == Encoding::ASCII_8BIT
316
- assert("env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}") {
317
- value.b !~ /[\x80-\xff]/n
318
- }
308
+ unless value.b !~ /[\x80-\xff]/n
309
+ raise LintError, "env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}"
310
+ end
319
311
  }
320
312
 
321
313
  ## There are the following restrictions:
322
314
 
323
315
  ## * <tt>rack.version</tt> must be an array of Integers.
324
- assert("rack.version must be an Array, was #{env[RACK_VERSION].class}") {
325
- env[RACK_VERSION].kind_of? Array
326
- }
316
+ unless env[RACK_VERSION].kind_of? Array
317
+ raise LintError, "rack.version must be an Array, was #{env[RACK_VERSION].class}"
318
+ end
327
319
  ## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
328
- assert("rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}") {
329
- %w[http https].include?(env[RACK_URL_SCHEME])
330
- }
320
+ unless %w[http https].include?(env[RACK_URL_SCHEME])
321
+ raise LintError, "rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}"
322
+ end
331
323
 
332
324
  ## * There must be a valid input stream in <tt>rack.input</tt>.
333
325
  check_input env[RACK_INPUT]
@@ -337,37 +329,33 @@ module Rack
337
329
  check_hijack env
338
330
 
339
331
  ## * The <tt>REQUEST_METHOD</tt> must be a valid token.
340
- assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD].dump}") {
341
- env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
342
- }
332
+ unless env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
333
+ raise LintError, "REQUEST_METHOD unknown: #{env[REQUEST_METHOD].dump}"
334
+ end
343
335
 
344
336
  ## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
345
- assert("SCRIPT_NAME must start with /") {
346
- !env.include?(SCRIPT_NAME) ||
347
- env[SCRIPT_NAME] == "" ||
348
- env[SCRIPT_NAME] =~ /\A\//
349
- }
337
+ if env.include?(SCRIPT_NAME) && env[SCRIPT_NAME] != "" && env[SCRIPT_NAME] !~ /\A\//
338
+ raise LintError, "SCRIPT_NAME must start with /"
339
+ end
350
340
  ## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
351
- assert("PATH_INFO must start with /") {
352
- !env.include?(PATH_INFO) ||
353
- env[PATH_INFO] == "" ||
354
- env[PATH_INFO] =~ /\A\//
355
- }
341
+ if env.include?(PATH_INFO) && env[PATH_INFO] != "" && env[PATH_INFO] !~ /\A\//
342
+ raise LintError, "PATH_INFO must start with /"
343
+ end
356
344
  ## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
357
- assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
358
- !env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
359
- }
345
+ if env.include?("CONTENT_LENGTH") && env["CONTENT_LENGTH"] !~ /\A\d+\z/
346
+ raise LintError, "Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}"
347
+ end
360
348
 
361
349
  ## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
362
350
  ## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
363
351
  ## <tt>SCRIPT_NAME</tt> is empty.
364
- assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
365
- env[SCRIPT_NAME] || env[PATH_INFO]
366
- }
352
+ unless env[SCRIPT_NAME] || env[PATH_INFO]
353
+ raise LintError, "One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)"
354
+ end
367
355
  ## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
368
- assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
369
- env[SCRIPT_NAME] != "/"
370
- }
356
+ unless env[SCRIPT_NAME] != "/"
357
+ raise LintError, "SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'"
358
+ end
371
359
  end
372
360
 
373
361
  ## === The Input Stream
@@ -377,18 +365,18 @@ module Rack
377
365
  def check_input(input)
378
366
  ## When applicable, its external encoding must be "ASCII-8BIT" and it
379
367
  ## must be opened in binary mode, for Ruby 1.9 compatibility.
380
- assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
381
- input.external_encoding == Encoding::ASCII_8BIT
382
- } if input.respond_to?(:external_encoding)
383
- assert("rack.input #{input} is not opened in binary mode") {
384
- input.binmode?
385
- } if input.respond_to?(:binmode?)
368
+ if input.respond_to?(:external_encoding) && input.external_encoding != Encoding::ASCII_8BIT
369
+ raise LintError, "rack.input #{input} does not have ASCII-8BIT as its external encoding"
370
+ end
371
+ if input.respond_to?(:binmode?) && !input.binmode?
372
+ raise LintError, "rack.input #{input} is not opened in binary mode"
373
+ end
386
374
 
387
375
  ## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
388
376
  [:gets, :each, :read, :rewind].each { |method|
389
- assert("rack.input #{input} does not respond to ##{method}") {
390
- input.respond_to? method
391
- }
377
+ unless input.respond_to? method
378
+ raise LintError, "rack.input #{input} does not respond to ##{method}"
379
+ end
392
380
  }
393
381
  end
394
382
 
@@ -402,11 +390,11 @@ module Rack
402
390
  ## * +gets+ must be called without arguments and return a string,
403
391
  ## or +nil+ on EOF.
404
392
  def gets(*args)
405
- assert("rack.input#gets called with arguments") { args.size == 0 }
393
+ raise LintError, "rack.input#gets called with arguments" unless args.size == 0
406
394
  v = @input.gets
407
- assert("rack.input#gets didn't return a String") {
408
- v.nil? or v.kind_of? String
409
- }
395
+ unless v.nil? or v.kind_of? String
396
+ raise LintError, "rack.input#gets didn't return a String"
397
+ end
410
398
  v
411
399
  end
412
400
 
@@ -428,32 +416,32 @@ module Rack
428
416
  ## If +buffer+ is given, then the read data will be placed
429
417
  ## into +buffer+ instead of a newly created String object.
430
418
  def read(*args)
431
- assert("rack.input#read called with too many arguments") {
432
- args.size <= 2
433
- }
419
+ unless args.size <= 2
420
+ raise LintError, "rack.input#read called with too many arguments"
421
+ end
434
422
  if args.size >= 1
435
- assert("rack.input#read called with non-integer and non-nil length") {
436
- args.first.kind_of?(Integer) || args.first.nil?
437
- }
438
- assert("rack.input#read called with a negative length") {
439
- args.first.nil? || args.first >= 0
440
- }
423
+ unless args.first.kind_of?(Integer) || args.first.nil?
424
+ raise LintError, "rack.input#read called with non-integer and non-nil length"
425
+ end
426
+ unless args.first.nil? || args.first >= 0
427
+ raise LintError, "rack.input#read called with a negative length"
428
+ end
441
429
  end
442
430
  if args.size >= 2
443
- assert("rack.input#read called with non-String buffer") {
444
- args[1].kind_of?(String)
445
- }
431
+ unless args[1].kind_of?(String)
432
+ raise LintError, "rack.input#read called with non-String buffer"
433
+ end
446
434
  end
447
435
 
448
436
  v = @input.read(*args)
449
437
 
450
- assert("rack.input#read didn't return nil or a String") {
451
- v.nil? or v.kind_of? String
452
- }
438
+ unless v.nil? or v.kind_of? String
439
+ raise LintError, "rack.input#read didn't return nil or a String"
440
+ end
453
441
  if args[0].nil?
454
- assert("rack.input#read(nil) returned nil on EOF") {
455
- !v.nil?
456
- }
442
+ unless !v.nil?
443
+ raise LintError, "rack.input#read(nil) returned nil on EOF"
444
+ end
457
445
  end
458
446
 
459
447
  v
@@ -461,11 +449,11 @@ module Rack
461
449
 
462
450
  ## * +each+ must be called without arguments and only yield Strings.
463
451
  def each(*args)
464
- assert("rack.input#each called with arguments") { args.size == 0 }
452
+ raise LintError, "rack.input#each called with arguments" unless args.size == 0
465
453
  @input.each { |line|
466
- assert("rack.input#each didn't yield a String") {
467
- line.kind_of? String
468
- }
454
+ unless line.kind_of? String
455
+ raise LintError, "rack.input#each didn't yield a String"
456
+ end
469
457
  yield line
470
458
  }
471
459
  end
@@ -476,20 +464,18 @@ module Rack
476
464
  ## developers must buffer the input data into some rewindable object
477
465
  ## if the underlying input stream is not rewindable.
478
466
  def rewind(*args)
479
- assert("rack.input#rewind called with arguments") { args.size == 0 }
480
- assert("rack.input#rewind raised Errno::ESPIPE") {
481
- begin
482
- @input.rewind
483
- true
484
- rescue Errno::ESPIPE
485
- false
486
- end
487
- }
467
+ raise LintError, "rack.input#rewind called with arguments" unless args.size == 0
468
+ begin
469
+ @input.rewind
470
+ true
471
+ rescue Errno::ESPIPE
472
+ raise LintError, "rack.input#rewind raised Errno::ESPIPE"
473
+ end
488
474
  end
489
475
 
490
476
  ## * +close+ must never be called on the input stream.
491
477
  def close(*args)
492
- assert("rack.input#close must not be called") { false }
478
+ raise LintError, "rack.input#close must not be called"
493
479
  end
494
480
  end
495
481
 
@@ -497,9 +483,9 @@ module Rack
497
483
  def check_error(error)
498
484
  ## The error stream must respond to +puts+, +write+ and +flush+.
499
485
  [:puts, :write, :flush].each { |method|
500
- assert("rack.error #{error} does not respond to ##{method}") {
501
- error.respond_to? method
502
- }
486
+ unless error.respond_to? method
487
+ raise LintError, "rack.error #{error} does not respond to ##{method}"
488
+ end
503
489
  }
504
490
  end
505
491
 
@@ -517,7 +503,7 @@ module Rack
517
503
 
518
504
  ## * +write+ must be called with a single argument that is a String.
519
505
  def write(str)
520
- assert("rack.errors#write not called with a String") { str.kind_of? String }
506
+ raise LintError, "rack.errors#write not called with a String" unless str.kind_of? String
521
507
  @error.write str
522
508
  end
523
509
 
@@ -529,7 +515,7 @@ module Rack
529
515
 
530
516
  ## * +close+ must never be called on the error stream.
531
517
  def close(*args)
532
- assert("rack.errors#close must not be called") { false }
518
+ raise LintError, "rack.errors#close must not be called"
533
519
  end
534
520
  end
535
521
 
@@ -547,7 +533,7 @@ module Rack
547
533
  def initialize(io)
548
534
  @io = io
549
535
  REQUIRED_METHODS.each do |meth|
550
- assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth }
536
+ raise LintError, "rack.hijack_io must respond to #{meth}" unless io.respond_to? meth
551
537
  end
552
538
  end
553
539
  end
@@ -563,7 +549,7 @@ module Rack
563
549
  if env[RACK_IS_HIJACK]
564
550
  ## If rack.hijack? is true then rack.hijack must respond to #call.
565
551
  original_hijack = env[RACK_HIJACK]
566
- assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
552
+ raise LintError, "rack.hijack must respond to call" unless original_hijack.respond_to?(:call)
567
553
  env[RACK_HIJACK] = proc do
568
554
  ## rack.hijack must return the io that will also be assigned (or is
569
555
  ## already present, in rack.hijack_io.
@@ -596,10 +582,10 @@ module Rack
596
582
  else
597
583
  ##
598
584
  ## If rack.hijack? is false, then rack.hijack should not be set.
599
- assert("rack.hijack? is false, but rack.hijack is present") { env[RACK_HIJACK].nil? }
585
+ raise LintError, "rack.hijack? is false, but rack.hijack is present" unless env[RACK_HIJACK].nil?
600
586
  ##
601
587
  ## If rack.hijack? is false, then rack.hijack_io should not be set.
602
- assert("rack.hijack? is false, but rack.hijack_io is present") { env[RACK_HIJACK_IO].nil? }
588
+ raise LintError, "rack.hijack? is false, but rack.hijack_io is present" unless env[RACK_HIJACK_IO].nil?
603
589
  end
604
590
  end
605
591
 
@@ -630,9 +616,9 @@ module Rack
630
616
  ## the <tt>rack.hijack</tt> response API is in use.
631
617
 
632
618
  if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
633
- assert('rack.hijack header must respond to #call') {
634
- headers[RACK_HIJACK].respond_to? :call
635
- }
619
+ unless headers[RACK_HIJACK].respond_to? :call
620
+ raise LintError, 'rack.hijack header must respond to #call'
621
+ end
636
622
  original_hijack = headers[RACK_HIJACK]
637
623
  proc do |io|
638
624
  original_hijack.call HijackWrapper.new(io)
@@ -641,9 +627,9 @@ module Rack
641
627
  ##
642
628
  ## The special response header <tt>rack.hijack</tt> must only be set
643
629
  ## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
644
- assert('rack.hijack header must not be present if server does not support hijacking') {
645
- headers[RACK_HIJACK].nil?
646
- }
630
+ unless headers[RACK_HIJACK].nil?
631
+ raise LintError, 'rack.hijack header must not be present if server does not support hijacking'
632
+ end
647
633
 
648
634
  nil
649
635
  end
@@ -661,42 +647,45 @@ module Rack
661
647
  def check_status(status)
662
648
  ## This is an HTTP status. When parsed as integer (+to_i+), it must be
663
649
  ## greater than or equal to 100.
664
- assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
650
+ unless status.to_i >= 100
651
+ raise LintError, "Status must be >=100 seen as integer"
652
+ end
665
653
  end
666
654
 
667
655
  ## === The Headers
668
656
  def check_headers(header)
669
657
  ## The header must respond to +each+, and yield values of key and value.
670
- assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
671
- header.respond_to? :each
672
- }
658
+ unless header.respond_to? :each
659
+ raise LintError, "headers object should respond to #each, but doesn't (got #{header.class} as headers)"
660
+ end
673
661
 
674
662
  header.each { |key, value|
675
663
  ## The header keys must be Strings.
676
- assert("header key must be a string, was #{key.class}") {
677
- key.kind_of? String
678
- }
664
+ unless key.kind_of? String
665
+ raise LintError, "header key must be a string, was #{key.class}"
666
+ end
679
667
 
680
668
  ## Special headers starting "rack." are for communicating with the
681
669
  ## server, and must not be sent back to the client.
682
670
  next if key =~ /^rack\..+$/
683
671
 
684
672
  ## The header must not contain a +Status+ key.
685
- assert("header must not contain Status") { key.downcase != "status" }
673
+ raise LintError, "header must not contain Status" if key.downcase == "status"
686
674
  ## The header must conform to RFC7230 token specification, i.e. cannot
687
675
  ## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
688
- assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ }
676
+ raise LintError, "invalid header name: #{key}" if key =~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/
689
677
 
690
678
  ## The values of the header must be Strings,
691
- assert("a header value must be a String, but the value of " +
692
- "'#{key}' is a #{value.class}") { value.kind_of? String }
679
+ unless value.kind_of? String
680
+ raise LintError, "a header value must be a String, but the value of '#{key}' is a #{value.class}"
681
+ end
693
682
  ## consisting of lines (for multiple header values, e.g. multiple
694
683
  ## <tt>Set-Cookie</tt> values) separated by "\\n".
695
684
  value.split("\n").each { |item|
696
685
  ## The lines must not contain characters below 037.
697
- assert("invalid header value #{key}: #{item.inspect}") {
698
- item !~ /[\000-\037]/
699
- }
686
+ if item =~ /[\000-\037]/
687
+ raise LintError, "invalid header value #{key}: #{item.inspect}"
688
+ end
700
689
  }
701
690
  }
702
691
  end
@@ -707,9 +696,9 @@ module Rack
707
696
  ## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
708
697
  ## 204 or 304.
709
698
  if key.downcase == "content-type"
710
- assert("Content-Type header found in #{status} response, not allowed") {
711
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
712
- }
699
+ if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
700
+ raise LintError, "Content-Type header found in #{status} response, not allowed"
701
+ end
713
702
  return
714
703
  end
715
704
  }
@@ -721,9 +710,9 @@ module Rack
721
710
  if key.downcase == 'content-length'
722
711
  ## There must not be a <tt>Content-Length</tt> header when the
723
712
  ## +Status+ is 1xx, 204 or 304.
724
- assert("Content-Length header found in #{status} response, not allowed") {
725
- not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
726
- }
713
+ if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
714
+ raise LintError, "Content-Length header found in #{status} response, not allowed"
715
+ end
727
716
  @content_length = value
728
717
  end
729
718
  }
@@ -731,13 +720,13 @@ module Rack
731
720
 
732
721
  def verify_content_length(bytes)
733
722
  if @head_request
734
- assert("Response body was given for HEAD request, but should be empty") {
735
- bytes == 0
736
- }
723
+ unless bytes == 0
724
+ raise LintError, "Response body was given for HEAD request, but should be empty"
725
+ end
737
726
  elsif @content_length
738
- assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
739
- @content_length == bytes.to_s
740
- }
727
+ unless @content_length == bytes.to_s
728
+ raise LintError, "Content-Length header was #{@content_length}, but should be #{bytes}"
729
+ end
741
730
  end
742
731
  end
743
732
 
@@ -747,15 +736,15 @@ module Rack
747
736
  bytes = 0
748
737
 
749
738
  ## The Body must respond to +each+
750
- assert("Response body must respond to each") do
751
- @body.respond_to?(:each)
739
+ unless @body.respond_to?(:each)
740
+ raise LintError, "Response body must respond to each"
752
741
  end
753
742
 
754
743
  @body.each { |part|
755
744
  ## and must only yield String values.
756
- assert("Body yielded non-string value #{part.inspect}") {
757
- part.kind_of? String
758
- }
745
+ unless part.kind_of? String
746
+ raise LintError, "Body yielded non-string value #{part.inspect}"
747
+ end
759
748
  bytes += part.bytesize
760
749
  yield part
761
750
  }
@@ -768,7 +757,7 @@ module Rack
768
757
  ## If the Body responds to +close+, it will be called after iteration. If
769
758
  ## the body is replaced by a middleware after action, the original body
770
759
  ## must be closed first, if it responds to close.
771
- # XXX howto: assert("Body has not been closed") { @closed }
760
+ # XXX howto: raise LintError, "Body has not been closed" unless @closed
772
761
 
773
762
 
774
763
  ##
@@ -779,9 +768,9 @@ module Rack
779
768
  ## transport the response.
780
769
 
781
770
  if @body.respond_to?(:to_path)
782
- assert("The file identified by body.to_path does not exist") {
783
- ::File.exist? @body.to_path
784
- }
771
+ unless ::File.exist? @body.to_path
772
+ raise LintError, "The file identified by body.to_path does not exist"
773
+ end
785
774
  end
786
775
 
787
776
  ##
@@ -43,7 +43,7 @@ module Rack
43
43
 
44
44
  def method_override_param(req)
45
45
  req.POST[METHOD_OVERRIDE_PARAM_KEY]
46
- rescue Utils::InvalidParameterError, Utils::ParameterTypeError
46
+ rescue Utils::InvalidParameterError, Utils::ParameterTypeError, QueryParser::ParamsTooDeepError
47
47
  req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params"
48
48
  rescue EOFError
49
49
  req.get_header(RACK_ERRORS).puts "Bad request content body"
@@ -5,6 +5,7 @@ require 'strscan'
5
5
  module Rack
6
6
  module Multipart
7
7
  class MultipartPartLimitError < Errno::EMFILE; end
8
+ class MultipartTotalPartLimitError < StandardError; end
8
9
 
9
10
  class Parser
10
11
  (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
@@ -12,7 +13,9 @@ module Rack
12
13
  BUFSIZE = 1_048_576
13
14
  TEXT_PLAIN = "text/plain"
14
15
  TEMPFILE_FACTORY = lambda { |filename, content_type|
15
- Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
16
+ extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
17
+
18
+ Tempfile.new(["RackMultipart", extension])
16
19
  }
17
20
 
18
21
  BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
@@ -140,7 +143,7 @@ module Rack
140
143
 
141
144
  @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
142
145
 
143
- check_open_files
146
+ check_part_limits
144
147
  end
145
148
 
146
149
  def on_mime_body(mime_index, content)
@@ -152,13 +155,23 @@ module Rack
152
155
 
153
156
  private
154
157
 
155
- def check_open_files
156
- if Utils.multipart_part_limit > 0
157
- if @open_files >= Utils.multipart_part_limit
158
+ def check_part_limits
159
+ file_limit = Utils.multipart_file_limit
160
+ part_limit = Utils.multipart_total_part_limit
161
+
162
+ if file_limit && file_limit > 0
163
+ if @open_files >= file_limit
158
164
  @mime_parts.each(&:close)
159
165
  raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
160
166
  end
161
167
  end
168
+
169
+ if part_limit && part_limit > 0
170
+ if @mime_parts.size >= part_limit
171
+ @mime_parts.each(&:close)
172
+ raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
173
+ end
174
+ end
162
175
  end
163
176
  end
164
177
 
@@ -18,10 +18,10 @@ module Rack
18
18
  VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
19
19
  BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
20
20
  MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
21
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
21
+ MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
22
22
  MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
23
23
  # Updated definitions from RFC 2231
24
- ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
24
+ ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
25
25
  ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
26
26
  SECTION = /\*[0-9]+/
27
27
  REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
@@ -16,6 +16,10 @@ module Rack
16
16
  # sequence.
17
17
  class InvalidParameterError < ArgumentError; end
18
18
 
19
+ # ParamsTooDeepError is the error that is raised when params are recursively
20
+ # nested over the specified limit.
21
+ class ParamsTooDeepError < RangeError; end
22
+
19
23
  def self.make_default(key_space_limit, param_depth_limit)
20
24
  new Params, key_space_limit, param_depth_limit
21
25
  end
@@ -81,7 +85,7 @@ module Rack
81
85
  # the structural types represented by two different parameter names are in
82
86
  # conflict, a ParameterTypeError is raised.
83
87
  def normalize_params(params, name, v, depth)
84
- raise RangeError if depth <= 0
88
+ raise ParamsTooDeepError if depth <= 0
85
89
 
86
90
  name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
87
91
  k = $1 || ''
@@ -168,7 +172,7 @@ module Rack
168
172
 
169
173
  def []=(key, value)
170
174
  @size += key.size if key && !@params.key?(key)
171
- raise RangeError, 'exceeded available parameter key space' if @size > @limit
175
+ raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
172
176
  @params[key] = value
173
177
  end
174
178
 
data/lib/rack/request.rb CHANGED
@@ -572,8 +572,8 @@ module Rack
572
572
  end
573
573
 
574
574
  def parse_http_accept_header(header)
575
- header.to_s.split(/\s*,\s*/).map do |part|
576
- attribute, parameters = part.split(/\s*;\s*/, 2)
575
+ header.to_s.split(",").each(&:strip!).map do |part|
576
+ attribute, parameters = part.split(";", 2).each(&:strip!)
577
577
  quality = 1.0
578
578
  if parameters and /\Aq=([\d.]+)/ =~ parameters
579
579
  quality = $1.to_f
@@ -608,7 +608,7 @@ module Rack
608
608
  (?<ip4>[\d\.]+)
609
609
  |
610
610
  # A hostname:
611
- (?<name>[a-zA-Z0-9\.\-]+)
611
+ (?<name>[a-zA-Z0-9\.\-_]+)
612
612
  )
613
613
  # The optional port:
614
614
  (:(?<port>\d+))?
@@ -5,6 +5,7 @@ require 'zlib'
5
5
  require_relative 'abstract/id'
6
6
  require 'json'
7
7
  require 'base64'
8
+ require 'delegate'
8
9
 
9
10
  module Rack
10
11
 
data/lib/rack/urlmap.rb CHANGED
@@ -35,7 +35,7 @@ module Rack
35
35
  end
36
36
 
37
37
  location = location.chomp('/')
38
- match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
38
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
39
39
 
40
40
  [host, location, match, app]
41
41
  }.sort_by do |(host, location, _, _)|
data/lib/rack/utils.rb CHANGED
@@ -22,6 +22,9 @@ module Rack
22
22
  COMMON_SEP = QueryParser::COMMON_SEP
23
23
  KeySpaceConstrainedParams = QueryParser::Params
24
24
 
25
+ RFC2822_DAY_NAME = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
26
+ RFC2822_MONTH_NAME = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
27
+
25
28
  class << self
26
29
  attr_accessor :default_query_parser
27
30
  end
@@ -55,13 +58,24 @@ module Rack
55
58
  end
56
59
 
57
60
  class << self
58
- attr_accessor :multipart_part_limit
61
+ attr_accessor :multipart_total_part_limit
62
+
63
+ attr_accessor :multipart_file_limit
64
+
65
+ # multipart_part_limit is the original name of multipart_file_limit, but
66
+ # the limit only counts parts with filenames.
67
+ alias multipart_part_limit multipart_file_limit
68
+ alias multipart_part_limit= multipart_file_limit=
59
69
  end
60
70
 
61
- # The maximum number of parts a request can contain. Accepting too many part
62
- # can lead to the server running out of file handles.
71
+ # The maximum number of file parts a request can contain. Accepting too
72
+ # many parts can lead to the server running out of file handles.
63
73
  # Set to `0` for no limit.
64
- self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
74
+ self.multipart_file_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_FILE_LIMIT'] || 128).to_i
75
+
76
+ # The maximum total number of parts a request can contain. Accepting too
77
+ # many can lead to excessive memory use and parsing time.
78
+ self.multipart_total_part_limit = (ENV['RACK_MULTIPART_TOTAL_PART_LIMIT'] || 4096).to_i
65
79
 
66
80
  def self.param_depth_limit
67
81
  default_query_parser.param_depth_limit
@@ -327,8 +341,8 @@ module Rack
327
341
  # weekday and month.
328
342
  #
329
343
  def rfc2109(time)
330
- wday = Time::RFC2822_DAY_NAME[time.wday]
331
- mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
344
+ wday = RFC2822_DAY_NAME[time.wday]
345
+ mon = RFC2822_MONTH_NAME[time.mon - 1]
332
346
  time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
333
347
  end
334
348
 
@@ -345,17 +359,18 @@ module Rack
345
359
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
346
360
  ranges = []
347
361
  $1.split(/,\s*/).each do |range_spec|
348
- return nil unless range_spec =~ /(\d*)-(\d*)/
349
- r0, r1 = $1, $2
350
- if r0.empty?
351
- return nil if r1.empty?
362
+ return nil unless range_spec.include?('-')
363
+ range = range_spec.split('-')
364
+ r0, r1 = range[0], range[1]
365
+ if r0.nil? || r0.empty?
366
+ return nil if r1.nil?
352
367
  # suffix-byte-range-spec, represents trailing suffix of file
353
368
  r0 = size - r1.to_i
354
369
  r0 = 0 if r0 < 0
355
370
  r1 = size - 1
356
371
  else
357
372
  r0 = r0.to_i
358
- if r1.empty?
373
+ if r1.nil?
359
374
  r1 = size - 1
360
375
  else
361
376
  r1 = r1.to_i
data/lib/rack/version.rb CHANGED
@@ -20,7 +20,7 @@ module Rack
20
20
  VERSION.join(".")
21
21
  end
22
22
 
23
- RELEASE = "2.2.3.1"
23
+ RELEASE = "2.2.8"
24
24
 
25
25
  # Return the Rack release as a dotted string.
26
26
  def self.release
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.3.1
4
+ version: 2.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leah Neukirchen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-27 00:00:00.000000000 Z
11
+ date: 2023-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -169,7 +169,7 @@ metadata:
169
169
  changelog_uri: https://github.com/rack/rack/blob/master/CHANGELOG.md
170
170
  documentation_uri: https://rubydoc.info/github/rack/rack
171
171
  source_code_uri: https://github.com/rack/rack
172
- post_install_message:
172
+ post_install_message:
173
173
  rdoc_options: []
174
174
  require_paths:
175
175
  - lib
@@ -184,8 +184,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
184
  - !ruby/object:Gem::Version
185
185
  version: '0'
186
186
  requirements: []
187
- rubygems_version: 3.0.3.1
188
- signing_key:
187
+ rubygems_version: 3.4.10
188
+ signing_key:
189
189
  specification_version: 4
190
190
  summary: A modular Ruby webserver interface.
191
191
  test_files: []