rack 3.1.21 → 3.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.

Potentially problematic release.


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

data/lib/rack/utils.rb CHANGED
@@ -146,77 +146,17 @@ module Rack
146
146
  end
147
147
  end
148
148
 
149
- ALLOWED_FORWARED_PARAMS = %w[by for host proto].to_h { |name| [name, name.to_sym] }.freeze
150
- private_constant :ALLOWED_FORWARED_PARAMS
151
-
152
149
  def forwarded_values(forwarded_header)
153
- return unless forwarded_header
154
- header = forwarded_header.to_s.tr("\n", ";")
155
- header.sub!(/\A[\s;,]+/, '')
156
- num_params = num_escapes = 0
157
- max_params = max_escapes = 1024
158
- params = {}
159
-
160
- # Parse parameter list
161
- while i = header.index('=')
162
- # Only parse up to max parameters, to avoid potential denial of service
163
- num_params += 1
164
- return if num_params > max_params
165
-
166
- # Found end of parameter name, ensure forward progress in loop
167
- param = header.slice!(0, i+1)
168
-
169
- # Remove ending equals and preceding whitespace from parameter name
170
- param.chomp!('=')
171
- param.strip!
172
- param.downcase!
173
- return unless param = ALLOWED_FORWARED_PARAMS[param]
174
-
175
- if header[0] == '"'
176
- # Parameter value is quoted, parse it, handling backslash escapes
177
- header.slice!(0, 1)
178
- value = String.new
179
-
180
- while i = header.index(/(["\\])/)
181
- c = $1
182
-
183
- # Append all content until ending quote or escape
184
- value << header.slice!(0, i)
185
-
186
- # Remove either backslash or ending quote,
187
- # ensures forward progress in loop
188
- header.slice!(0, 1)
189
-
190
- # stop parsing parameter value if found ending quote
191
- break if c == '"'
192
-
193
- # Only allow up to max escapes, to avoid potential denial of service
194
- num_escapes += 1
195
- return if num_escapes > max_escapes
196
- escaped_char = header.slice!(0, 1)
197
- value << escaped_char
198
- end
199
- else
200
- if i = header.index(/[;,]/)
201
- # Parameter value unquoted (which may be invalid), value ends at comma or semicolon
202
- value = header.slice!(0, i)
203
- value.sub!(/[\s;,]+\z/, '')
204
- else
205
- # If no ending semicolon, assume remainder of line is value and stop parsing
206
- header.strip!
207
- value = header
208
- header = ''
209
- end
210
- value.lstrip!
150
+ return nil unless forwarded_header
151
+ forwarded_header = forwarded_header.to_s.gsub("\n", ";")
152
+
153
+ forwarded_header.split(';').each_with_object({}) do |field, values|
154
+ field.split(',').each do |pair|
155
+ pair = pair.split('=').map(&:strip).join('=')
156
+ return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\Z/i
157
+ (values[$1.downcase.to_sym] ||= []) << $2
211
158
  end
212
-
213
- (params[param] ||= []) << value
214
-
215
- # skip trailing semicolons/commas/whitespace, to proceed to next parameter
216
- header.sub!(/\A[\s;,]+/, '') unless header.empty?
217
159
  end
218
-
219
- params
220
160
  end
221
161
  module_function :forwarded_values
222
162
 
@@ -241,49 +181,29 @@ module Rack
241
181
  # doesn't get monkey-patched by rails
242
182
  if defined?(ERB::Escape) && ERB::Escape.instance_method(:html_escape)
243
183
  define_method(:escape_html, ERB::Escape.instance_method(:html_escape))
184
+ # :nocov:
185
+ # Ruby 3.2/ERB 4.0 added ERB::Escape#html_escape, so the else
186
+ # branch cannot be hit on the current Ruby version.
244
187
  else
245
188
  require 'cgi/escape'
246
189
  # Escape ampersands, brackets and quotes to their HTML/XML entities.
247
190
  def escape_html(string)
248
191
  CGI.escapeHTML(string.to_s)
249
192
  end
193
+ # :nocov:
250
194
  end
251
195
 
252
- # Given an array of available encoding strings, and an array of
253
- # acceptable encodings for a request, where each element of the
254
- # acceptable encodings array is an array where the first element
255
- # is an encoding name and the second element is the numeric
256
- # priority for the encoding, return the available encoding with
257
- # the highest priority.
258
- #
259
- # The accept_encoding argument is typically generated by calling
260
- # Request#accept_encoding.
261
- #
262
- # Example:
263
- #
264
- # select_best_encoding(%w(compress gzip identity),
265
- # [["compress", 0.5], ["gzip", 1.0]])
266
- # # => "gzip"
267
- #
268
- # To reduce denial of service potential, only the first 16
269
- # acceptable encodings are considered.
270
196
  def select_best_encoding(available_encodings, accept_encoding)
271
197
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
272
198
 
273
- # Only process the first 16 encodings
274
- accept_encoding = accept_encoding[0...16]
275
199
  expanded_accept_encoding = []
276
- wildcard_seen = false
277
200
 
278
201
  accept_encoding.each do |m, q|
279
202
  preference = available_encodings.index(m) || available_encodings.size
280
203
 
281
204
  if m == "*"
282
- unless wildcard_seen
283
- (available_encodings - accept_encoding.map(&:first)).each do |m2|
284
- expanded_accept_encoding << [m2, q, preference]
285
- end
286
- wildcard_seen = true
205
+ (available_encodings - accept_encoding.map(&:first)).each do |m2|
206
+ expanded_accept_encoding << [m2, q, preference]
287
207
  end
288
208
  else
289
209
  expanded_accept_encoding << [m, q, preference]
@@ -291,13 +211,7 @@ module Rack
291
211
  end
292
212
 
293
213
  encoding_candidates = expanded_accept_encoding
294
- .sort do |(_, q1, p1), (_, q2, p2)|
295
- if r = (q1 <=> q2).nonzero?
296
- -r
297
- else
298
- (p1 <=> p2).nonzero? || 0
299
- end
300
- end
214
+ .sort_by { |_, q, p| [-q, p] }
301
215
  .map!(&:first)
302
216
 
303
217
  unless encoding_candidates.include?("identity")
@@ -344,26 +258,18 @@ module Rack
344
258
  parse_cookies_header env[HTTP_COOKIE]
345
259
  end
346
260
 
347
- # A valid cookie key according to RFC2616.
261
+ # A valid cookie key according to RFC6265 and RFC2616.
348
262
  # A <cookie-name> can be any US-ASCII characters, except control characters, spaces, or tabs. It also must not contain a separator character like the following: ( ) < > @ , ; : \ " / [ ] ? = { }.
349
263
  VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
350
264
  private_constant :VALID_COOKIE_KEY
351
265
 
352
- private def escape_cookie_key(key)
353
- if key =~ VALID_COOKIE_KEY
354
- key
355
- else
356
- warn "Cookie key #{key.inspect} is not valid according to RFC2616; it will be escaped. This behaviour is deprecated and will be removed in a future version of Rack.", uplevel: 2
357
- escape(key)
358
- end
359
- end
360
-
361
266
  # :call-seq:
362
267
  # set_cookie_header(key, value) -> encoded string
363
268
  #
364
269
  # Generate an encoded string using the provided +key+ and +value+ suitable
365
270
  # for the +set-cookie+ header according to RFC6265. The +value+ may be an
366
- # instance of either +String+ or +Hash+.
271
+ # instance of either +String+ or +Hash+. If the cookie key is invalid (as
272
+ # defined by RFC6265), an +ArgumentError+ will be raised.
367
273
  #
368
274
  # If the cookie +value+ is an instance of +Hash+, it considers the following
369
275
  # cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
@@ -371,10 +277,6 @@ module Rack
371
277
  # details about the interpretation of these fields, consult
372
278
  # [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
373
279
  #
374
- # An extra cookie attribute +escape_key+ can be provided to control whether
375
- # or not the cookie key is URL encoded. If explicitly set to +false+, the
376
- # cookie key name will not be url encoded (escaped). The default is +true+.
377
- #
378
280
  # set_cookie_header("myname", "myvalue")
379
281
  # # => "myname=myvalue"
380
282
  #
@@ -382,9 +284,12 @@ module Rack
382
284
  # # => "myname=myvalue; max-age=10"
383
285
  #
384
286
  def set_cookie_header(key, value)
287
+ unless key =~ VALID_COOKIE_KEY
288
+ raise ArgumentError, "invalid cookie key: #{key.inspect}"
289
+ end
290
+
385
291
  case value
386
292
  when Hash
387
- key = escape_cookie_key(key) unless value[:escape_key] == false
388
293
  domain = "; domain=#{value[:domain]}" if value[:domain]
389
294
  path = "; path=#{value[:path]}" if value[:path]
390
295
  max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
@@ -406,8 +311,6 @@ module Rack
406
311
  end
407
312
  partitioned = "; partitioned" if value[:partitioned]
408
313
  value = value[:value]
409
- else
410
- key = escape_cookie_key(key)
411
314
  end
412
315
 
413
316
  value = [value] unless Array === value
@@ -496,19 +399,17 @@ module Rack
496
399
  # Parses the "Range:" header, if present, into an array of Range objects.
497
400
  # Returns nil if the header is missing or syntactically invalid.
498
401
  # Returns an empty array if none of the ranges are satisfiable.
499
- def byte_ranges(env, size, max_ranges: 100)
500
- get_byte_ranges env['HTTP_RANGE'], size, max_ranges: max_ranges
402
+ def byte_ranges(env, size)
403
+ get_byte_ranges env['HTTP_RANGE'], size
501
404
  end
502
405
 
503
- def get_byte_ranges(http_range, size, max_ranges: 100)
406
+ def get_byte_ranges(http_range, size)
504
407
  # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
505
408
  # Ignore Range when file size is 0 to avoid a 416 error.
506
409
  return nil if size.zero?
507
410
  return nil unless http_range && http_range =~ /bytes=([^;]+)/
508
- byte_range = $1
509
- return nil if byte_range.count(',') >= max_ranges
510
411
  ranges = []
511
- byte_range.split(/,[ \t]*/).each do |range_spec|
412
+ $1.split(/,[ \t]*/).each do |range_spec|
512
413
  return nil unless range_spec.include?('-')
513
414
  range = range_spec.split('-')
514
415
  r0, r1 = range[0], range[1]
@@ -684,11 +585,9 @@ module Rack
684
585
  fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
685
586
  message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
686
587
  if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
687
- # message = "#{message} Please use #{canonical_symbol.inspect} instead."
688
- # For now, let's not emit any warning when there is a mapping.
689
- else
690
- warn message, uplevel: 3
588
+ message = "#{message} Please use #{canonical_symbol.inspect} instead."
691
589
  end
590
+ warn message, uplevel: 3
692
591
  fallback_code
693
592
  end
694
593
  else
data/lib/rack/version.rb CHANGED
@@ -5,17 +5,13 @@
5
5
  # Rack is freely distributable under the terms of an MIT-style license.
6
6
  # See MIT-LICENSE or https://opensource.org/licenses/MIT.
7
7
 
8
- # The Rack main module, serving as a namespace for all core Rack
9
- # modules and classes.
10
- #
11
- # All modules meant for use in your application are <tt>autoload</tt>ed here,
12
- # so it should be enough just to <tt>require 'rack'</tt> in your code.
13
-
14
8
  module Rack
15
- RELEASE = "3.1.21"
9
+ VERSION = "3.2.0"
10
+
11
+ RELEASE = VERSION
16
12
 
17
13
  # Return the Rack release as a dotted string.
18
14
  def self.release
19
- RELEASE
15
+ VERSION
20
16
  end
21
17
  end
data/lib/rack.rb CHANGED
@@ -34,7 +34,6 @@ module Rack
34
34
  autoload :Headers, "rack/headers"
35
35
  autoload :Lint, "rack/lint"
36
36
  autoload :Lock, "rack/lock"
37
- autoload :Logger, "rack/logger"
38
37
  autoload :MediaType, "rack/media_type"
39
38
  autoload :MethodOverride, "rack/method_override"
40
39
  autoload :Mime, "rack/mime"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.21
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leah Neukirchen
@@ -107,7 +107,6 @@ files:
107
107
  - lib/rack/headers.rb
108
108
  - lib/rack/lint.rb
109
109
  - lib/rack/lock.rb
110
- - lib/rack/logger.rb
111
110
  - lib/rack/media_type.rb
112
111
  - lib/rack/method_override.rb
113
112
  - lib/rack/mime.rb
@@ -142,6 +141,7 @@ metadata:
142
141
  changelog_uri: https://github.com/rack/rack/blob/main/CHANGELOG.md
143
142
  documentation_uri: https://rubydoc.info/github/rack/rack
144
143
  source_code_uri: https://github.com/rack/rack
144
+ rubygems_mfa_required: 'true'
145
145
  rdoc_options: []
146
146
  require_paths:
147
147
  - lib
@@ -156,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
156
  - !ruby/object:Gem::Version
157
157
  version: '0'
158
158
  requirements: []
159
- rubygems_version: 4.0.6
159
+ rubygems_version: 3.6.7
160
160
  specification_version: 4
161
161
  summary: A modular Ruby webserver interface.
162
162
  test_files: []
data/lib/rack/logger.rb DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'logger'
4
- require_relative 'constants'
5
-
6
- warn "Rack::Logger is deprecated and will be removed in Rack 3.2.", uplevel: 1
7
-
8
- module Rack
9
- # Sets up rack.logger to write to rack.errors stream
10
- class Logger
11
- def initialize(app, level = ::Logger::INFO)
12
- @app, @level = app, level
13
- end
14
-
15
- def call(env)
16
- logger = ::Logger.new(env[RACK_ERRORS])
17
- logger.level = @level
18
-
19
- env[RACK_LOGGER] = logger
20
- @app.call(env)
21
- end
22
- end
23
- end