rack 3.0.17 → 3.1.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 +27 -101
- data/CONTRIBUTING.md +11 -9
- data/README.md +34 -42
- data/SPEC.rdoc +38 -13
- data/lib/rack/auth/basic.rb +1 -2
- data/lib/rack/bad_request.rb +8 -0
- data/lib/rack/builder.rb +23 -10
- data/lib/rack/cascade.rb +0 -3
- data/lib/rack/common_logger.rb +2 -3
- data/lib/rack/constants.rb +3 -1
- data/lib/rack/content_length.rb +0 -1
- data/lib/rack/etag.rb +0 -3
- data/lib/rack/headers.rb +86 -2
- data/lib/rack/lint.rb +116 -34
- data/lib/rack/logger.rb +2 -1
- data/lib/rack/mime.rb +6 -5
- data/lib/rack/mock_request.rb +19 -14
- data/lib/rack/mock_response.rb +13 -44
- data/lib/rack/multipart/parser.rb +123 -62
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +19 -115
- data/lib/rack/request.rb +28 -21
- data/lib/rack/response.rb +21 -23
- data/lib/rack/sendfile.rb +1 -1
- data/lib/rack/show_exceptions.rb +6 -2
- data/lib/rack/static.rb +1 -2
- data/lib/rack/utils.rb +57 -96
- data/lib/rack/version.rb +1 -14
- data/lib/rack.rb +1 -3
- metadata +8 -11
- 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/auth/digest.rb
DELETED
@@ -1,256 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'abstract/handler'
|
4
|
-
require_relative 'abstract/request'
|
5
|
-
require 'digest/md5'
|
6
|
-
require 'base64'
|
7
|
-
|
8
|
-
module Rack
|
9
|
-
warn "Rack::Auth::Digest is deprecated and will be removed in Rack 3.1", uplevel: 1
|
10
|
-
|
11
|
-
module Auth
|
12
|
-
module Digest
|
13
|
-
# Rack::Auth::Digest::Nonce is the default nonce generator for the
|
14
|
-
# Rack::Auth::Digest::MD5 authentication handler.
|
15
|
-
#
|
16
|
-
# +private_key+ needs to set to a constant string.
|
17
|
-
#
|
18
|
-
# +time_limit+ can be optionally set to an integer (number of seconds),
|
19
|
-
# to limit the validity of the generated nonces.
|
20
|
-
|
21
|
-
class Nonce
|
22
|
-
|
23
|
-
class << self
|
24
|
-
attr_accessor :private_key, :time_limit
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.parse(string)
|
28
|
-
new(*Base64.decode64(string).split(' ', 2))
|
29
|
-
end
|
30
|
-
|
31
|
-
def initialize(timestamp = Time.now, given_digest = nil)
|
32
|
-
@timestamp, @given_digest = timestamp.to_i, given_digest
|
33
|
-
end
|
34
|
-
|
35
|
-
def to_s
|
36
|
-
Base64.encode64("#{@timestamp} #{digest}").strip
|
37
|
-
end
|
38
|
-
|
39
|
-
def digest
|
40
|
-
::Digest::MD5.hexdigest("#{@timestamp}:#{self.class.private_key}")
|
41
|
-
end
|
42
|
-
|
43
|
-
def valid?
|
44
|
-
digest == @given_digest
|
45
|
-
end
|
46
|
-
|
47
|
-
def stale?
|
48
|
-
!self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit
|
49
|
-
end
|
50
|
-
|
51
|
-
def fresh?
|
52
|
-
!stale?
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
class Params < Hash
|
58
|
-
|
59
|
-
def self.parse(str)
|
60
|
-
Params[*split_header_value(str).map do |param|
|
61
|
-
k, v = param.split('=', 2)
|
62
|
-
[k, dequote(v)]
|
63
|
-
end.flatten]
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.dequote(str) # From WEBrick::HTTPUtils
|
67
|
-
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
68
|
-
ret.gsub!(/\\(.)/, "\\1")
|
69
|
-
ret
|
70
|
-
end
|
71
|
-
|
72
|
-
def self.split_header_value(str)
|
73
|
-
str.scan(/\w+\=(?:"[^\"]+"|[^,]+)/n)
|
74
|
-
end
|
75
|
-
|
76
|
-
def initialize
|
77
|
-
super()
|
78
|
-
|
79
|
-
yield self if block_given?
|
80
|
-
end
|
81
|
-
|
82
|
-
def [](k)
|
83
|
-
super k.to_s
|
84
|
-
end
|
85
|
-
|
86
|
-
def []=(k, v)
|
87
|
-
super k.to_s, v.to_s
|
88
|
-
end
|
89
|
-
|
90
|
-
UNQUOTED = ['nc', 'stale']
|
91
|
-
|
92
|
-
def to_s
|
93
|
-
map do |k, v|
|
94
|
-
"#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}"
|
95
|
-
end.join(', ')
|
96
|
-
end
|
97
|
-
|
98
|
-
def quote(str) # From WEBrick::HTTPUtils
|
99
|
-
'"' + str.gsub(/[\\\"]/o, "\\\1") + '"'
|
100
|
-
end
|
101
|
-
|
102
|
-
end
|
103
|
-
|
104
|
-
class Request < Auth::AbstractRequest
|
105
|
-
def method
|
106
|
-
@env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] || @env[REQUEST_METHOD]
|
107
|
-
end
|
108
|
-
|
109
|
-
def digest?
|
110
|
-
"digest" == scheme
|
111
|
-
end
|
112
|
-
|
113
|
-
def correct_uri?
|
114
|
-
request.fullpath == uri
|
115
|
-
end
|
116
|
-
|
117
|
-
def nonce
|
118
|
-
@nonce ||= Nonce.parse(params['nonce'])
|
119
|
-
end
|
120
|
-
|
121
|
-
def params
|
122
|
-
@params ||= Params.parse(parts.last)
|
123
|
-
end
|
124
|
-
|
125
|
-
def respond_to?(sym, *)
|
126
|
-
super or params.has_key? sym.to_s
|
127
|
-
end
|
128
|
-
|
129
|
-
def method_missing(sym, *args)
|
130
|
-
return super unless params.has_key?(key = sym.to_s)
|
131
|
-
return params[key] if args.size == 0
|
132
|
-
raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
|
137
|
-
# HTTP Digest Authentication, as per RFC 2617.
|
138
|
-
#
|
139
|
-
# Initialize with the [Rack] application that you want protecting,
|
140
|
-
# and a block that looks up a plaintext password for a given username.
|
141
|
-
#
|
142
|
-
# +opaque+ needs to be set to a constant base64/hexadecimal string.
|
143
|
-
#
|
144
|
-
class MD5 < AbstractHandler
|
145
|
-
|
146
|
-
attr_accessor :opaque
|
147
|
-
|
148
|
-
attr_writer :passwords_hashed
|
149
|
-
|
150
|
-
def initialize(app, realm = nil, opaque = nil, &authenticator)
|
151
|
-
@passwords_hashed = nil
|
152
|
-
if opaque.nil? and realm.respond_to? :values_at
|
153
|
-
realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed
|
154
|
-
end
|
155
|
-
super(app, realm, &authenticator)
|
156
|
-
@opaque = opaque
|
157
|
-
end
|
158
|
-
|
159
|
-
def passwords_hashed?
|
160
|
-
!!@passwords_hashed
|
161
|
-
end
|
162
|
-
|
163
|
-
def call(env)
|
164
|
-
auth = Request.new(env)
|
165
|
-
|
166
|
-
unless auth.provided?
|
167
|
-
return unauthorized
|
168
|
-
end
|
169
|
-
|
170
|
-
if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
|
171
|
-
return bad_request
|
172
|
-
end
|
173
|
-
|
174
|
-
if valid?(auth)
|
175
|
-
if auth.nonce.stale?
|
176
|
-
return unauthorized(challenge(stale: true))
|
177
|
-
else
|
178
|
-
env['REMOTE_USER'] = auth.username
|
179
|
-
|
180
|
-
return @app.call(env)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
unauthorized
|
185
|
-
end
|
186
|
-
|
187
|
-
|
188
|
-
private
|
189
|
-
|
190
|
-
QOP = 'auth'
|
191
|
-
|
192
|
-
def params(hash = {})
|
193
|
-
Params.new do |params|
|
194
|
-
params['realm'] = realm
|
195
|
-
params['nonce'] = Nonce.new.to_s
|
196
|
-
params['opaque'] = H(opaque)
|
197
|
-
params['qop'] = QOP
|
198
|
-
|
199
|
-
hash.each { |k, v| params[k] = v }
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
def challenge(hash = {})
|
204
|
-
"Digest #{params(hash)}"
|
205
|
-
end
|
206
|
-
|
207
|
-
def valid?(auth)
|
208
|
-
valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
|
209
|
-
end
|
210
|
-
|
211
|
-
def valid_qop?(auth)
|
212
|
-
QOP == auth.qop
|
213
|
-
end
|
214
|
-
|
215
|
-
def valid_opaque?(auth)
|
216
|
-
H(opaque) == auth.opaque
|
217
|
-
end
|
218
|
-
|
219
|
-
def valid_nonce?(auth)
|
220
|
-
auth.nonce.valid?
|
221
|
-
end
|
222
|
-
|
223
|
-
def valid_digest?(auth)
|
224
|
-
pw = @authenticator.call(auth.username)
|
225
|
-
pw && Rack::Utils.secure_compare(digest(auth, pw), auth.response)
|
226
|
-
end
|
227
|
-
|
228
|
-
def md5(data)
|
229
|
-
::Digest::MD5.hexdigest(data)
|
230
|
-
end
|
231
|
-
|
232
|
-
alias :H :md5
|
233
|
-
|
234
|
-
def KD(secret, data)
|
235
|
-
H "#{secret}:#{data}"
|
236
|
-
end
|
237
|
-
|
238
|
-
def A1(auth, password)
|
239
|
-
"#{auth.username}:#{auth.realm}:#{password}"
|
240
|
-
end
|
241
|
-
|
242
|
-
def A2(auth)
|
243
|
-
"#{auth.method}:#{auth.uri}"
|
244
|
-
end
|
245
|
-
|
246
|
-
def digest(auth, password)
|
247
|
-
password_hash = passwords_hashed? ? password : H(A1(auth, password))
|
248
|
-
|
249
|
-
KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}"
|
250
|
-
end
|
251
|
-
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
data/lib/rack/chunked.rb
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'constants'
|
4
|
-
require_relative 'utils'
|
5
|
-
|
6
|
-
module Rack
|
7
|
-
warn "Rack::Chunked is deprecated and will be removed in Rack 3.1", uplevel: 1
|
8
|
-
|
9
|
-
# Middleware that applies chunked transfer encoding to response bodies
|
10
|
-
# when the response does not include a content-length header.
|
11
|
-
#
|
12
|
-
# This supports the trailer response header to allow the use of trailing
|
13
|
-
# headers in the chunked encoding. However, using this requires you manually
|
14
|
-
# specify a response body that supports a +trailers+ method. Example:
|
15
|
-
#
|
16
|
-
# [200, { 'trailer' => 'expires'}, ["Hello", "World"]]
|
17
|
-
# # error raised
|
18
|
-
#
|
19
|
-
# body = ["Hello", "World"]
|
20
|
-
# def body.trailers
|
21
|
-
# { 'expires' => Time.now.to_s }
|
22
|
-
# end
|
23
|
-
# [200, { 'trailer' => 'expires'}, body]
|
24
|
-
# # No exception raised
|
25
|
-
class Chunked
|
26
|
-
include Rack::Utils
|
27
|
-
|
28
|
-
# A body wrapper that emits chunked responses.
|
29
|
-
class Body
|
30
|
-
TERM = "\r\n"
|
31
|
-
TAIL = "0#{TERM}"
|
32
|
-
|
33
|
-
# Store the response body to be chunked.
|
34
|
-
def initialize(body)
|
35
|
-
@body = body
|
36
|
-
end
|
37
|
-
|
38
|
-
# For each element yielded by the response body, yield
|
39
|
-
# the element in chunked encoding.
|
40
|
-
def each(&block)
|
41
|
-
term = TERM
|
42
|
-
@body.each do |chunk|
|
43
|
-
size = chunk.bytesize
|
44
|
-
next if size == 0
|
45
|
-
|
46
|
-
yield [size.to_s(16), term, chunk.b, term].join
|
47
|
-
end
|
48
|
-
yield TAIL
|
49
|
-
yield_trailers(&block)
|
50
|
-
yield term
|
51
|
-
end
|
52
|
-
|
53
|
-
# Close the response body if the response body supports it.
|
54
|
-
def close
|
55
|
-
@body.close if @body.respond_to?(:close)
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
# Do nothing as this class does not support trailer headers.
|
61
|
-
def yield_trailers
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# A body wrapper that emits chunked responses and also supports
|
66
|
-
# sending Trailer headers. Note that the response body provided to
|
67
|
-
# initialize must have a +trailers+ method that returns a hash
|
68
|
-
# of trailer headers, and the rack response itself should have a
|
69
|
-
# Trailer header listing the headers that the +trailers+ method
|
70
|
-
# will return.
|
71
|
-
class TrailerBody < Body
|
72
|
-
private
|
73
|
-
|
74
|
-
# Yield strings for each trailer header.
|
75
|
-
def yield_trailers
|
76
|
-
@body.trailers.each_pair do |k, v|
|
77
|
-
yield "#{k}: #{v}\r\n"
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def initialize(app)
|
83
|
-
@app = app
|
84
|
-
end
|
85
|
-
|
86
|
-
# Whether the HTTP version supports chunked encoding (HTTP 1.1 does).
|
87
|
-
def chunkable_version?(ver)
|
88
|
-
case ver
|
89
|
-
# pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have
|
90
|
-
# a version (nor response headers)
|
91
|
-
when 'HTTP/1.0', nil, 'HTTP/0.9'
|
92
|
-
false
|
93
|
-
else
|
94
|
-
true
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# If the rack app returns a response that should have a body,
|
99
|
-
# but does not have content-length or transfer-encoding headers,
|
100
|
-
# modify the response to use chunked transfer-encoding.
|
101
|
-
def call(env)
|
102
|
-
status, headers, body = response = @app.call(env)
|
103
|
-
|
104
|
-
if chunkable_version?(env[SERVER_PROTOCOL]) &&
|
105
|
-
!STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
|
106
|
-
!headers[CONTENT_LENGTH] &&
|
107
|
-
!headers[TRANSFER_ENCODING]
|
108
|
-
|
109
|
-
headers[TRANSFER_ENCODING] = 'chunked'
|
110
|
-
if headers['trailer']
|
111
|
-
response[2] = TrailerBody.new(body)
|
112
|
-
else
|
113
|
-
response[2] = Body.new(body)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
response
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|