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.

@@ -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
data/lib/rack/file.rb DELETED
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'files'
4
-
5
- module Rack
6
- warn "Rack::File is deprecated and will be removed in Rack 3.1", uplevel: 1
7
-
8
- File = Files
9
- end