rack 3.0.5 → 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 +383 -1
- data/CONTRIBUTING.md +11 -9
- data/README.md +95 -28
- data/SPEC.rdoc +206 -288
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/auth/basic.rb +1 -2
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/body_proxy.rb +18 -2
- data/lib/rack/builder.rb +29 -10
- data/lib/rack/cascade.rb +0 -3
- data/lib/rack/common_logger.rb +3 -2
- data/lib/rack/conditional_get.rb +4 -3
- data/lib/rack/constants.rb +3 -1
- data/lib/rack/etag.rb +3 -0
- data/lib/rack/head.rb +2 -3
- data/lib/rack/headers.rb +86 -2
- data/lib/rack/lint.rb +482 -425
- data/lib/rack/media_type.rb +18 -9
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +10 -15
- data/lib/rack/mock_response.rb +39 -18
- data/lib/rack/multipart/parser.rb +169 -73
- data/lib/rack/multipart/uploaded_file.rb +42 -5
- data/lib/rack/multipart.rb +9 -1
- data/lib/rack/query_parser.rb +86 -97
- data/lib/rack/request.rb +67 -100
- data/lib/rack/response.rb +29 -19
- data/lib/rack/rewindable_input.rb +4 -1
- data/lib/rack/sendfile.rb +2 -2
- data/lib/rack/show_exceptions.rb +10 -4
- data/lib/rack/show_status.rb +0 -2
- data/lib/rack/static.rb +2 -1
- data/lib/rack/utils.rb +78 -110
- data/lib/rack/version.rb +3 -20
- data/lib/rack.rb +11 -17
- metadata +6 -15
- data/lib/rack/auth/digest/md5.rb +0 -1
- data/lib/rack/auth/digest/nonce.rb +0 -1
- data/lib/rack/auth/digest/params.rb +0 -1
- data/lib/rack/auth/digest/request.rb +0 -1
- data/lib/rack/auth/digest.rb +0 -256
- data/lib/rack/chunked.rb +0 -120
- data/lib/rack/file.rb +0 -9
- data/lib/rack/logger.rb +0 -22
data/lib/rack/response.rb
CHANGED
|
@@ -31,13 +31,6 @@ module Rack
|
|
|
31
31
|
attr_accessor :length, :status, :body
|
|
32
32
|
attr_reader :headers
|
|
33
33
|
|
|
34
|
-
# Deprecated, use headers instead.
|
|
35
|
-
def header
|
|
36
|
-
warn 'Rack::Response#header is deprecated and will be removed in Rack 3.1', uplevel: 1
|
|
37
|
-
|
|
38
|
-
headers
|
|
39
|
-
end
|
|
40
|
-
|
|
41
34
|
# Initialize the response object with the specified +body+, +status+
|
|
42
35
|
# and +headers+.
|
|
43
36
|
#
|
|
@@ -62,7 +55,7 @@ module Rack
|
|
|
62
55
|
@status = status.to_i
|
|
63
56
|
|
|
64
57
|
unless headers.is_a?(Hash)
|
|
65
|
-
|
|
58
|
+
raise ArgumentError, "Headers must be a Hash!"
|
|
66
59
|
end
|
|
67
60
|
|
|
68
61
|
@headers = Headers.new
|
|
@@ -79,7 +72,8 @@ module Rack
|
|
|
79
72
|
if body.nil?
|
|
80
73
|
@body = []
|
|
81
74
|
@buffered = true
|
|
82
|
-
|
|
75
|
+
# Body is unspecified - it may be a buffered response, or it may be a HEAD response.
|
|
76
|
+
@length = nil
|
|
83
77
|
elsif body.respond_to?(:to_str)
|
|
84
78
|
@body = [body]
|
|
85
79
|
@buffered = true
|
|
@@ -87,7 +81,7 @@ module Rack
|
|
|
87
81
|
else
|
|
88
82
|
@body = body
|
|
89
83
|
@buffered = nil # undetermined as of yet.
|
|
90
|
-
@length =
|
|
84
|
+
@length = nil
|
|
91
85
|
end
|
|
92
86
|
|
|
93
87
|
yield self if block_given?
|
|
@@ -106,7 +100,7 @@ module Rack
|
|
|
106
100
|
# The response body is an enumerable body and it is not allowed to have an entity body.
|
|
107
101
|
@body.respond_to?(:each) && STATUS_WITH_NO_ENTITY_BODY[@status]
|
|
108
102
|
end
|
|
109
|
-
|
|
103
|
+
|
|
110
104
|
# Generate a response array consistent with the requirements of the SPEC.
|
|
111
105
|
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
|
112
106
|
# which is suitable to be returned from the middleware `#call(env)` method.
|
|
@@ -118,9 +112,14 @@ module Rack
|
|
|
118
112
|
return [@status, @headers, []]
|
|
119
113
|
else
|
|
120
114
|
if block_given?
|
|
115
|
+
# We don't add the content-length here as the user has provided a block that can #write additional chunks to the body.
|
|
121
116
|
@block = block
|
|
122
117
|
return [@status, @headers, self]
|
|
123
118
|
else
|
|
119
|
+
# If we know the length of the body, set the content-length header... except if we are chunked? which is a legacy special case where the body might already be encoded and thus the actual encoded body length and the content-length are likely to be different.
|
|
120
|
+
if @length && !chunked?
|
|
121
|
+
@headers[CONTENT_LENGTH] = @length.to_s
|
|
122
|
+
end
|
|
124
123
|
return [@status, @headers, @body]
|
|
125
124
|
end
|
|
126
125
|
end
|
|
@@ -138,7 +137,9 @@ module Rack
|
|
|
138
137
|
end
|
|
139
138
|
end
|
|
140
139
|
|
|
141
|
-
# Append to
|
|
140
|
+
# Append a chunk to the response body.
|
|
141
|
+
#
|
|
142
|
+
# Converts the response into a buffered response if it wasn't already.
|
|
142
143
|
#
|
|
143
144
|
# NOTE: Do not mix #write and direct #body access!
|
|
144
145
|
#
|
|
@@ -320,27 +321,34 @@ module Rack
|
|
|
320
321
|
|
|
321
322
|
protected
|
|
322
323
|
|
|
324
|
+
# Convert the body of this response into an internally buffered Array if possible.
|
|
325
|
+
#
|
|
326
|
+
# `@buffered` is a ternary value which indicates whether the body is buffered. It can be:
|
|
327
|
+
# * `nil` - The body has not been buffered yet.
|
|
328
|
+
# * `true` - The body is buffered as an Array instance.
|
|
329
|
+
# * `false` - The body is not buffered and cannot be buffered.
|
|
330
|
+
#
|
|
331
|
+
# @return [Boolean] whether the body is buffered as an Array instance.
|
|
323
332
|
def buffered_body!
|
|
324
333
|
if @buffered.nil?
|
|
325
334
|
if @body.is_a?(Array)
|
|
326
335
|
# The user supplied body was an array:
|
|
327
336
|
@body = @body.compact
|
|
328
|
-
@body.
|
|
329
|
-
|
|
330
|
-
end
|
|
337
|
+
@length = @body.sum{|part| part.bytesize}
|
|
338
|
+
@buffered = true
|
|
331
339
|
elsif @body.respond_to?(:each)
|
|
332
340
|
# Turn the user supplied body into a buffered array:
|
|
333
341
|
body = @body
|
|
334
342
|
@body = Array.new
|
|
343
|
+
@buffered = true
|
|
335
344
|
|
|
336
345
|
body.each do |part|
|
|
337
346
|
@writer.call(part.to_s)
|
|
338
347
|
end
|
|
339
348
|
|
|
340
349
|
body.close if body.respond_to?(:close)
|
|
341
|
-
|
|
342
|
-
@buffered = true
|
|
343
350
|
else
|
|
351
|
+
# We don't know how to buffer the user-supplied body:
|
|
344
352
|
@buffered = false
|
|
345
353
|
end
|
|
346
354
|
end
|
|
@@ -349,11 +357,13 @@ module Rack
|
|
|
349
357
|
end
|
|
350
358
|
|
|
351
359
|
def append(chunk)
|
|
360
|
+
chunk = chunk.dup unless chunk.frozen?
|
|
352
361
|
@body << chunk
|
|
353
362
|
|
|
354
|
-
|
|
363
|
+
if @length
|
|
355
364
|
@length += chunk.bytesize
|
|
356
|
-
|
|
365
|
+
elsif @buffered
|
|
366
|
+
@length = chunk.bytesize
|
|
357
367
|
end
|
|
358
368
|
|
|
359
369
|
return chunk
|
data/lib/rack/sendfile.rb
CHANGED
|
@@ -111,7 +111,7 @@ module Rack
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
def call(env)
|
|
114
|
-
|
|
114
|
+
_, headers, body = response = @app.call(env)
|
|
115
115
|
|
|
116
116
|
if body.respond_to?(:to_path)
|
|
117
117
|
case type = variation(env)
|
|
@@ -138,7 +138,7 @@ module Rack
|
|
|
138
138
|
end
|
|
139
139
|
when '', nil
|
|
140
140
|
else
|
|
141
|
-
env[RACK_ERRORS].puts "Unknown x-sendfile variation:
|
|
141
|
+
env[RACK_ERRORS].puts "Unknown x-sendfile variation: #{type.inspect}"
|
|
142
142
|
end
|
|
143
143
|
end
|
|
144
144
|
response
|
data/lib/rack/show_exceptions.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'ostruct'
|
|
4
3
|
require 'erb'
|
|
5
4
|
|
|
6
5
|
require_relative 'constants'
|
|
@@ -19,6 +18,11 @@ module Rack
|
|
|
19
18
|
class ShowExceptions
|
|
20
19
|
CONTEXT = 7
|
|
21
20
|
|
|
21
|
+
Frame = Struct.new(:filename, :lineno, :function,
|
|
22
|
+
:pre_context_lineno, :pre_context,
|
|
23
|
+
:context_line, :post_context_lineno,
|
|
24
|
+
:post_context)
|
|
25
|
+
|
|
22
26
|
def initialize(app)
|
|
23
27
|
@app = app
|
|
24
28
|
end
|
|
@@ -61,8 +65,12 @@ module Rack
|
|
|
61
65
|
def dump_exception(exception)
|
|
62
66
|
if exception.respond_to?(:detailed_message)
|
|
63
67
|
message = exception.detailed_message(highlight: false)
|
|
68
|
+
# :nocov:
|
|
69
|
+
# Ruby 3.2 added Exception#detailed_message, so the else
|
|
70
|
+
# branch cannot be hit on the current Ruby version.
|
|
64
71
|
else
|
|
65
72
|
message = exception.message
|
|
73
|
+
# :nocov:
|
|
66
74
|
end
|
|
67
75
|
string = "#{exception.class}: #{message}\n".dup
|
|
68
76
|
string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
|
|
@@ -79,7 +87,7 @@ module Rack
|
|
|
79
87
|
# This double assignment is to prevent an "unused variable" warning.
|
|
80
88
|
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
|
81
89
|
frames = frames = exception.backtrace.map { |line|
|
|
82
|
-
frame =
|
|
90
|
+
frame = Frame.new
|
|
83
91
|
if line =~ /(.*?):(\d+)(:in `(.*)')?/
|
|
84
92
|
frame.filename = $1
|
|
85
93
|
frame.lineno = $2.to_i
|
|
@@ -397,7 +405,5 @@ module Rack
|
|
|
397
405
|
</body>
|
|
398
406
|
</html>
|
|
399
407
|
HTML
|
|
400
|
-
|
|
401
|
-
# :startdoc:
|
|
402
408
|
end
|
|
403
409
|
end
|
data/lib/rack/show_status.rb
CHANGED
data/lib/rack/static.rb
CHANGED
|
@@ -124,8 +124,9 @@ module Rack
|
|
|
124
124
|
|
|
125
125
|
def call(env)
|
|
126
126
|
path = env[PATH_INFO]
|
|
127
|
+
actual_path = Utils.clean_path_info(Utils.unescape_path(path))
|
|
127
128
|
|
|
128
|
-
if can_serve(
|
|
129
|
+
if can_serve(actual_path)
|
|
129
130
|
if overwrite_file_path(path)
|
|
130
131
|
env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
|
|
131
132
|
elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
|
data/lib/rack/utils.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'fileutils'
|
|
|
6
6
|
require 'set'
|
|
7
7
|
require 'tempfile'
|
|
8
8
|
require 'time'
|
|
9
|
+
require 'erb'
|
|
9
10
|
|
|
10
11
|
require_relative 'query_parser'
|
|
11
12
|
require_relative 'mime'
|
|
@@ -23,6 +24,7 @@ module Rack
|
|
|
23
24
|
DEFAULT_SEP = QueryParser::DEFAULT_SEP
|
|
24
25
|
COMMON_SEP = QueryParser::COMMON_SEP
|
|
25
26
|
KeySpaceConstrainedParams = QueryParser::Params
|
|
27
|
+
URI_PARSER = defined?(::URI::RFC2396_PARSER) ? ::URI::RFC2396_PARSER : ::URI::DEFAULT_PARSER
|
|
26
28
|
|
|
27
29
|
class << self
|
|
28
30
|
attr_accessor :default_query_parser
|
|
@@ -42,13 +44,13 @@ module Rack
|
|
|
42
44
|
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
|
43
45
|
# true URI escaping.
|
|
44
46
|
def escape_path(s)
|
|
45
|
-
|
|
47
|
+
URI_PARSER.escape s
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
# Unescapes the **path** component of a URI. See Rack::Utils.unescape for
|
|
49
51
|
# unescaping query parameters or form components.
|
|
50
52
|
def unescape_path(s)
|
|
51
|
-
|
|
53
|
+
URI_PARSER.unescape s
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
|
@@ -85,15 +87,6 @@ module Rack
|
|
|
85
87
|
self.default_query_parser = self.default_query_parser.new_depth_limit(v)
|
|
86
88
|
end
|
|
87
89
|
|
|
88
|
-
def self.key_space_limit
|
|
89
|
-
warn("`Rack::Utils.key_space_limit` is deprecated as this value no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
|
|
90
|
-
65536
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def self.key_space_limit=(v)
|
|
94
|
-
warn("`Rack::Utils.key_space_limit=` is deprecated and no longer has an effect. It will be removed in Rack 3.1", uplevel: 1)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
90
|
if defined?(Process::CLOCK_MONOTONIC)
|
|
98
91
|
def clock_time
|
|
99
92
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
@@ -143,8 +136,8 @@ module Rack
|
|
|
143
136
|
end
|
|
144
137
|
|
|
145
138
|
def q_values(q_value_header)
|
|
146
|
-
q_value_header.to_s.split(
|
|
147
|
-
value, parameters = part.split(
|
|
139
|
+
q_value_header.to_s.split(',').map do |part|
|
|
140
|
+
value, parameters = part.split(';', 2).map(&:strip)
|
|
148
141
|
quality = 1.0
|
|
149
142
|
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
|
150
143
|
quality = md[1].to_f
|
|
@@ -157,9 +150,10 @@ module Rack
|
|
|
157
150
|
return nil unless forwarded_header
|
|
158
151
|
forwarded_header = forwarded_header.to_s.gsub("\n", ";")
|
|
159
152
|
|
|
160
|
-
forwarded_header.split(
|
|
161
|
-
field.split(
|
|
162
|
-
|
|
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
|
|
163
157
|
(values[$1.downcase.to_sym] ||= []) << $2
|
|
164
158
|
end
|
|
165
159
|
end
|
|
@@ -183,20 +177,20 @@ module Rack
|
|
|
183
177
|
matches&.first
|
|
184
178
|
end
|
|
185
179
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
180
|
+
# Introduced in ERB 4.0. ERB::Escape is an alias for ERB::Utils which
|
|
181
|
+
# doesn't get monkey-patched by rails
|
|
182
|
+
if defined?(ERB::Escape) && ERB::Escape.instance_method(:html_escape)
|
|
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.
|
|
187
|
+
else
|
|
188
|
+
require 'cgi/escape'
|
|
189
|
+
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
|
190
|
+
def escape_html(string)
|
|
191
|
+
CGI.escapeHTML(string.to_s)
|
|
192
|
+
end
|
|
193
|
+
# :nocov:
|
|
200
194
|
end
|
|
201
195
|
|
|
202
196
|
def select_best_encoding(available_encodings, accept_encoding)
|
|
@@ -251,21 +245,6 @@ module Rack
|
|
|
251
245
|
end
|
|
252
246
|
end
|
|
253
247
|
|
|
254
|
-
def add_cookie_to_header(header, key, value)
|
|
255
|
-
warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
|
|
256
|
-
|
|
257
|
-
case header
|
|
258
|
-
when nil, ''
|
|
259
|
-
return set_cookie_header(key, value)
|
|
260
|
-
when String
|
|
261
|
-
[header, set_cookie_header(key, value)]
|
|
262
|
-
when Array
|
|
263
|
-
header + [set_cookie_header(key, value)]
|
|
264
|
-
else
|
|
265
|
-
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
248
|
# :call-seq:
|
|
270
249
|
# parse_cookies(env) -> hash
|
|
271
250
|
#
|
|
@@ -279,12 +258,18 @@ module Rack
|
|
|
279
258
|
parse_cookies_header env[HTTP_COOKIE]
|
|
280
259
|
end
|
|
281
260
|
|
|
261
|
+
# A valid cookie key according to RFC6265 and RFC2616.
|
|
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: ( ) < > @ , ; : \ " / [ ] ? = { }.
|
|
263
|
+
VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
|
|
264
|
+
private_constant :VALID_COOKIE_KEY
|
|
265
|
+
|
|
282
266
|
# :call-seq:
|
|
283
267
|
# set_cookie_header(key, value) -> encoded string
|
|
284
268
|
#
|
|
285
269
|
# Generate an encoded string using the provided +key+ and +value+ suitable
|
|
286
270
|
# for the +set-cookie+ header according to RFC6265. The +value+ may be an
|
|
287
|
-
# 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.
|
|
288
273
|
#
|
|
289
274
|
# If the cookie +value+ is an instance of +Hash+, it considers the following
|
|
290
275
|
# cookie attribute keys: +domain+, +max_age+, +expires+ (must be instance
|
|
@@ -292,10 +277,6 @@ module Rack
|
|
|
292
277
|
# details about the interpretation of these fields, consult
|
|
293
278
|
# [RFC6265 Section 5.2](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2).
|
|
294
279
|
#
|
|
295
|
-
# An extra cookie attribute +escape_key+ can be provided to control whether
|
|
296
|
-
# or not the cookie key is URL encoded. If explicitly set to +false+, the
|
|
297
|
-
# cookie key name will not be url encoded (escaped). The default is +true+.
|
|
298
|
-
#
|
|
299
280
|
# set_cookie_header("myname", "myvalue")
|
|
300
281
|
# # => "myname=myvalue"
|
|
301
282
|
#
|
|
@@ -303,9 +284,12 @@ module Rack
|
|
|
303
284
|
# # => "myname=myvalue; max-age=10"
|
|
304
285
|
#
|
|
305
286
|
def set_cookie_header(key, value)
|
|
287
|
+
unless key =~ VALID_COOKIE_KEY
|
|
288
|
+
raise ArgumentError, "invalid cookie key: #{key.inspect}"
|
|
289
|
+
end
|
|
290
|
+
|
|
306
291
|
case value
|
|
307
292
|
when Hash
|
|
308
|
-
key = escape(key) unless value[:escape_key] == false
|
|
309
293
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
|
310
294
|
path = "; path=#{value[:path]}" if value[:path]
|
|
311
295
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
|
@@ -317,23 +301,22 @@ module Rack
|
|
|
317
301
|
when false, nil
|
|
318
302
|
nil
|
|
319
303
|
when :none, 'None', :None
|
|
320
|
-
';
|
|
304
|
+
'; samesite=none'
|
|
321
305
|
when :lax, 'Lax', :Lax
|
|
322
|
-
';
|
|
306
|
+
'; samesite=lax'
|
|
323
307
|
when true, :strict, 'Strict', :Strict
|
|
324
|
-
';
|
|
308
|
+
'; samesite=strict'
|
|
325
309
|
else
|
|
326
|
-
raise ArgumentError, "Invalid
|
|
310
|
+
raise ArgumentError, "Invalid :same_site value: #{value[:same_site].inspect}"
|
|
327
311
|
end
|
|
312
|
+
partitioned = "; partitioned" if value[:partitioned]
|
|
328
313
|
value = value[:value]
|
|
329
|
-
else
|
|
330
|
-
key = escape(key)
|
|
331
314
|
end
|
|
332
315
|
|
|
333
316
|
value = [value] unless Array === value
|
|
334
317
|
|
|
335
318
|
return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
|
336
|
-
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
|
319
|
+
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}"
|
|
337
320
|
end
|
|
338
321
|
|
|
339
322
|
# :call-seq:
|
|
@@ -374,24 +357,12 @@ module Rack
|
|
|
374
357
|
set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
|
|
375
358
|
end
|
|
376
359
|
|
|
377
|
-
def make_delete_cookie_header(header, key, value)
|
|
378
|
-
warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
|
379
|
-
|
|
380
|
-
delete_set_cookie_header!(header, key, value)
|
|
381
|
-
end
|
|
382
|
-
|
|
383
360
|
def delete_cookie_header!(headers, key, value = {})
|
|
384
361
|
headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
|
|
385
362
|
|
|
386
363
|
return nil
|
|
387
364
|
end
|
|
388
365
|
|
|
389
|
-
def add_remove_cookie_to_header(header, key, value = {})
|
|
390
|
-
warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
|
391
|
-
|
|
392
|
-
delete_set_cookie_header!(header, key, value)
|
|
393
|
-
end
|
|
394
|
-
|
|
395
366
|
# :call-seq:
|
|
396
367
|
# delete_set_cookie_header!(header, key, value = {}) -> header value
|
|
397
368
|
#
|
|
@@ -434,9 +405,11 @@ module Rack
|
|
|
434
405
|
|
|
435
406
|
def get_byte_ranges(http_range, size)
|
|
436
407
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
|
408
|
+
# Ignore Range when file size is 0 to avoid a 416 error.
|
|
409
|
+
return nil if size.zero?
|
|
437
410
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
|
438
411
|
ranges = []
|
|
439
|
-
$1.split(
|
|
412
|
+
$1.split(/,[ \t]*/).each do |range_spec|
|
|
440
413
|
return nil unless range_spec.include?('-')
|
|
441
414
|
range = range_spec.split('-')
|
|
442
415
|
r0, r1 = range[0], range[1]
|
|
@@ -458,6 +431,9 @@ module Rack
|
|
|
458
431
|
end
|
|
459
432
|
ranges << (r0..r1) if r0 <= r1
|
|
460
433
|
end
|
|
434
|
+
|
|
435
|
+
return [] if ranges.map(&:size).sum > size
|
|
436
|
+
|
|
461
437
|
ranges
|
|
462
438
|
end
|
|
463
439
|
|
|
@@ -513,39 +489,12 @@ module Rack
|
|
|
513
489
|
end
|
|
514
490
|
end
|
|
515
491
|
|
|
516
|
-
# A wrapper around Headers
|
|
517
|
-
# header when set.
|
|
518
|
-
#
|
|
519
|
-
# @api private
|
|
520
|
-
class HeaderHash < Hash # :nodoc:
|
|
521
|
-
def self.[](headers)
|
|
522
|
-
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
|
523
|
-
if headers.is_a?(Headers) && !headers.frozen?
|
|
524
|
-
return headers
|
|
525
|
-
end
|
|
526
|
-
|
|
527
|
-
new_headers = Headers.new
|
|
528
|
-
headers.each{|k,v| new_headers[k] = v}
|
|
529
|
-
new_headers
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
def self.new(hash = {})
|
|
533
|
-
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
|
534
|
-
headers = Headers.new
|
|
535
|
-
hash.each{|k,v| headers[k] = v}
|
|
536
|
-
headers
|
|
537
|
-
end
|
|
538
|
-
|
|
539
|
-
def self.allocate
|
|
540
|
-
raise TypeError, "cannot allocate HeaderHash"
|
|
541
|
-
end
|
|
542
|
-
end
|
|
543
|
-
|
|
544
492
|
# Every standard HTTP code mapped to the appropriate message.
|
|
545
493
|
# Generated with:
|
|
546
|
-
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv
|
|
547
|
-
# ruby -
|
|
548
|
-
#
|
|
494
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv \
|
|
495
|
+
# | ruby -rcsv -e "puts CSV.parse(STDIN, headers: true) \
|
|
496
|
+
# .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \
|
|
497
|
+
# .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)"
|
|
549
498
|
HTTP_STATUS_CODES = {
|
|
550
499
|
100 => 'Continue',
|
|
551
500
|
101 => 'Switching Protocols',
|
|
@@ -567,7 +516,6 @@ module Rack
|
|
|
567
516
|
303 => 'See Other',
|
|
568
517
|
304 => 'Not Modified',
|
|
569
518
|
305 => 'Use Proxy',
|
|
570
|
-
306 => '(Unused)',
|
|
571
519
|
307 => 'Temporary Redirect',
|
|
572
520
|
308 => 'Permanent Redirect',
|
|
573
521
|
400 => 'Bad Request',
|
|
@@ -583,13 +531,13 @@ module Rack
|
|
|
583
531
|
410 => 'Gone',
|
|
584
532
|
411 => 'Length Required',
|
|
585
533
|
412 => 'Precondition Failed',
|
|
586
|
-
413 => '
|
|
534
|
+
413 => 'Content Too Large',
|
|
587
535
|
414 => 'URI Too Long',
|
|
588
536
|
415 => 'Unsupported Media Type',
|
|
589
537
|
416 => 'Range Not Satisfiable',
|
|
590
538
|
417 => 'Expectation Failed',
|
|
591
539
|
421 => 'Misdirected Request',
|
|
592
|
-
422 => 'Unprocessable
|
|
540
|
+
422 => 'Unprocessable Content',
|
|
593
541
|
423 => 'Locked',
|
|
594
542
|
424 => 'Failed Dependency',
|
|
595
543
|
425 => 'Too Early',
|
|
@@ -597,7 +545,7 @@ module Rack
|
|
|
597
545
|
428 => 'Precondition Required',
|
|
598
546
|
429 => 'Too Many Requests',
|
|
599
547
|
431 => 'Request Header Fields Too Large',
|
|
600
|
-
451 => 'Unavailable
|
|
548
|
+
451 => 'Unavailable For Legal Reasons',
|
|
601
549
|
500 => 'Internal Server Error',
|
|
602
550
|
501 => 'Not Implemented',
|
|
603
551
|
502 => 'Bad Gateway',
|
|
@@ -607,8 +555,6 @@ module Rack
|
|
|
607
555
|
506 => 'Variant Also Negotiates',
|
|
608
556
|
507 => 'Insufficient Storage',
|
|
609
557
|
508 => 'Loop Detected',
|
|
610
|
-
509 => 'Bandwidth Limit Exceeded',
|
|
611
|
-
510 => 'Not Extended',
|
|
612
558
|
511 => 'Network Authentication Required'
|
|
613
559
|
}
|
|
614
560
|
|
|
@@ -616,12 +562,34 @@ module Rack
|
|
|
616
562
|
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
|
617
563
|
|
|
618
564
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
|
619
|
-
[message.downcase.gsub(/\s
|
|
565
|
+
[message.downcase.gsub(/\s|-/, '_').to_sym, code]
|
|
620
566
|
}.flatten]
|
|
621
567
|
|
|
568
|
+
OBSOLETE_SYMBOLS_TO_STATUS_CODES = {
|
|
569
|
+
payload_too_large: 413,
|
|
570
|
+
unprocessable_entity: 422,
|
|
571
|
+
bandwidth_limit_exceeded: 509,
|
|
572
|
+
not_extended: 510
|
|
573
|
+
}.freeze
|
|
574
|
+
private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES
|
|
575
|
+
|
|
576
|
+
OBSOLETE_SYMBOL_MAPPINGS = {
|
|
577
|
+
payload_too_large: :content_too_large,
|
|
578
|
+
unprocessable_entity: :unprocessable_content
|
|
579
|
+
}.freeze
|
|
580
|
+
private_constant :OBSOLETE_SYMBOL_MAPPINGS
|
|
581
|
+
|
|
622
582
|
def status_code(status)
|
|
623
583
|
if status.is_a?(Symbol)
|
|
624
|
-
SYMBOL_TO_STATUS_CODE.fetch(status)
|
|
584
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) do
|
|
585
|
+
fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
|
586
|
+
message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
|
|
587
|
+
if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
|
|
588
|
+
message = "#{message} Please use #{canonical_symbol.inspect} instead."
|
|
589
|
+
end
|
|
590
|
+
warn message, uplevel: 3
|
|
591
|
+
fallback_code
|
|
592
|
+
end
|
|
625
593
|
else
|
|
626
594
|
status.to_i
|
|
627
595
|
end
|
data/lib/rack/version.rb
CHANGED
|
@@ -5,30 +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
|
-
|
|
16
|
-
VERSION = [1, 3].freeze
|
|
17
|
-
deprecate_constant :VERSION
|
|
9
|
+
VERSION = "3.2.0"
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
deprecate_constant :VERSION_STRING
|
|
21
|
-
|
|
22
|
-
# The Rack protocol version number implemented.
|
|
23
|
-
def self.version
|
|
24
|
-
warn "Rack.version is deprecated and will be removed in Rack 3.1!", uplevel: 1
|
|
25
|
-
VERSION
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
RELEASE = "3.0.5"
|
|
11
|
+
RELEASE = VERSION
|
|
29
12
|
|
|
30
13
|
# Return the Rack release as a dotted string.
|
|
31
14
|
def self.release
|
|
32
|
-
|
|
15
|
+
VERSION
|
|
33
16
|
end
|
|
34
17
|
end
|