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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -79
- data/README.md +41 -28
- data/SPEC.rdoc +199 -306
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/builder.rb +6 -0
- data/lib/rack/conditional_get.rb +4 -3
- data/lib/rack/constants.rb +1 -0
- data/lib/rack/directory.rb +3 -6
- data/lib/rack/files.rb +1 -1
- data/lib/rack/head.rb +2 -3
- data/lib/rack/lint.rb +430 -457
- data/lib/rack/media_type.rb +6 -7
- data/lib/rack/mock_response.rb +19 -25
- data/lib/rack/multipart/parser.rb +37 -94
- data/lib/rack/multipart/uploaded_file.rb +42 -5
- data/lib/rack/query_parser.rb +41 -26
- data/lib/rack/request.rb +48 -60
- data/lib/rack/rewindable_input.rb +4 -1
- data/lib/rack/sendfile.rb +21 -51
- data/lib/rack/show_exceptions.rb +4 -2
- data/lib/rack/show_status.rb +0 -2
- data/lib/rack/static.rb +3 -7
- data/lib/rack/utils.rb +28 -129
- data/lib/rack/version.rb +4 -8
- data/lib/rack.rb +0 -1
- metadata +3 -3
- data/lib/rack/logger.rb +0 -23
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
283
|
-
|
|
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
|
-
.
|
|
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
|
|
500
|
-
get_byte_ranges env['HTTP_RANGE'], size
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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
|