rack 3.0.11 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -4
- data/CONTRIBUTING.md +11 -9
- data/README.md +34 -15
- 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/constants.rb +3 -1
- data/lib/rack/content_length.rb +0 -1
- 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 +12 -14
- data/lib/rack/multipart/parser.rb +123 -62
- data/lib/rack/multipart.rb +34 -1
- data/lib/rack/query_parser.rb +15 -68
- data/lib/rack/request.rb +28 -21
- data/lib/rack/response.rb +21 -23
- data/lib/rack/show_exceptions.rb +6 -2
- data/lib/rack/utils.rb +57 -96
- data/lib/rack/version.rb +1 -14
- data/lib/rack.rb +10 -16
- metadata +4 -10
- 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/utils.rb
CHANGED
@@ -6,6 +6,7 @@ require 'fileutils'
|
|
6
6
|
require 'set'
|
7
7
|
require 'tempfile'
|
8
8
|
require 'time'
|
9
|
+
require 'cgi/escape'
|
9
10
|
|
10
11
|
require_relative 'query_parser'
|
11
12
|
require_relative 'mime'
|
@@ -85,15 +86,6 @@ module Rack
|
|
85
86
|
self.default_query_parser = self.default_query_parser.new_depth_limit(v)
|
86
87
|
end
|
87
88
|
|
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
89
|
if defined?(Process::CLOCK_MONOTONIC)
|
98
90
|
def clock_time
|
99
91
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
@@ -184,21 +176,8 @@ module Rack
|
|
184
176
|
matches&.first
|
185
177
|
end
|
186
178
|
|
187
|
-
ESCAPE_HTML = {
|
188
|
-
"&" => "&",
|
189
|
-
"<" => "<",
|
190
|
-
">" => ">",
|
191
|
-
"'" => "'",
|
192
|
-
'"' => """,
|
193
|
-
"/" => "/"
|
194
|
-
}
|
195
|
-
|
196
|
-
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
197
|
-
|
198
179
|
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
199
|
-
|
200
|
-
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
201
|
-
end
|
180
|
+
define_method(:escape_html, CGI.method(:escapeHTML))
|
202
181
|
|
203
182
|
def select_best_encoding(available_encodings, accept_encoding)
|
204
183
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
@@ -252,21 +231,6 @@ module Rack
|
|
252
231
|
end
|
253
232
|
end
|
254
233
|
|
255
|
-
def add_cookie_to_header(header, key, value)
|
256
|
-
warn("add_cookie_to_header is deprecated and will be removed in Rack 3.1", uplevel: 1)
|
257
|
-
|
258
|
-
case header
|
259
|
-
when nil, ''
|
260
|
-
return set_cookie_header(key, value)
|
261
|
-
when String
|
262
|
-
[header, set_cookie_header(key, value)]
|
263
|
-
when Array
|
264
|
-
header + [set_cookie_header(key, value)]
|
265
|
-
else
|
266
|
-
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
234
|
# :call-seq:
|
271
235
|
# parse_cookies(env) -> hash
|
272
236
|
#
|
@@ -280,6 +244,20 @@ module Rack
|
|
280
244
|
parse_cookies_header env[HTTP_COOKIE]
|
281
245
|
end
|
282
246
|
|
247
|
+
# A valid cookie key according to RFC2616.
|
248
|
+
# 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: ( ) < > @ , ; : \ " / [ ] ? = { }.
|
249
|
+
VALID_COOKIE_KEY = /\A[!#$%&'*+\-\.\^_`|~0-9a-zA-Z]+\z/.freeze
|
250
|
+
private_constant :VALID_COOKIE_KEY
|
251
|
+
|
252
|
+
private def escape_cookie_key(key)
|
253
|
+
if key =~ VALID_COOKIE_KEY
|
254
|
+
key
|
255
|
+
else
|
256
|
+
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
|
257
|
+
escape(key)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
283
261
|
# :call-seq:
|
284
262
|
# set_cookie_header(key, value) -> encoded string
|
285
263
|
#
|
@@ -306,7 +284,7 @@ module Rack
|
|
306
284
|
def set_cookie_header(key, value)
|
307
285
|
case value
|
308
286
|
when Hash
|
309
|
-
key =
|
287
|
+
key = escape_cookie_key(key) unless value[:escape_key] == false
|
310
288
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
311
289
|
path = "; path=#{value[:path]}" if value[:path]
|
312
290
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
@@ -318,23 +296,24 @@ module Rack
|
|
318
296
|
when false, nil
|
319
297
|
nil
|
320
298
|
when :none, 'None', :None
|
321
|
-
';
|
299
|
+
'; samesite=none'
|
322
300
|
when :lax, 'Lax', :Lax
|
323
|
-
';
|
301
|
+
'; samesite=lax'
|
324
302
|
when true, :strict, 'Strict', :Strict
|
325
|
-
';
|
303
|
+
'; samesite=strict'
|
326
304
|
else
|
327
|
-
raise ArgumentError, "Invalid
|
305
|
+
raise ArgumentError, "Invalid :same_site value: #{value[:same_site].inspect}"
|
328
306
|
end
|
307
|
+
partitioned = "; partitioned" if value[:partitioned]
|
329
308
|
value = value[:value]
|
330
309
|
else
|
331
|
-
key =
|
310
|
+
key = escape_cookie_key(key)
|
332
311
|
end
|
333
312
|
|
334
313
|
value = [value] unless Array === value
|
335
314
|
|
336
315
|
return "#{key}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
337
|
-
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
316
|
+
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}#{partitioned}"
|
338
317
|
end
|
339
318
|
|
340
319
|
# :call-seq:
|
@@ -375,24 +354,12 @@ module Rack
|
|
375
354
|
set_cookie_header(key, value.merge(max_age: '0', expires: Time.at(0), value: ''))
|
376
355
|
end
|
377
356
|
|
378
|
-
def make_delete_cookie_header(header, key, value)
|
379
|
-
warn("make_delete_cookie_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
380
|
-
|
381
|
-
delete_set_cookie_header!(header, key, value)
|
382
|
-
end
|
383
|
-
|
384
357
|
def delete_cookie_header!(headers, key, value = {})
|
385
358
|
headers[SET_COOKIE] = delete_set_cookie_header!(headers[SET_COOKIE], key, value)
|
386
359
|
|
387
360
|
return nil
|
388
361
|
end
|
389
362
|
|
390
|
-
def add_remove_cookie_to_header(header, key, value = {})
|
391
|
-
warn("add_remove_cookie_to_header is deprecated and will be removed in Rack 3.1, use delete_set_cookie_header! instead", uplevel: 1)
|
392
|
-
|
393
|
-
delete_set_cookie_header!(header, key, value)
|
394
|
-
end
|
395
|
-
|
396
363
|
# :call-seq:
|
397
364
|
# delete_set_cookie_header!(header, key, value = {}) -> header value
|
398
365
|
#
|
@@ -435,6 +402,8 @@ module Rack
|
|
435
402
|
|
436
403
|
def get_byte_ranges(http_range, size)
|
437
404
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
405
|
+
# Ignore Range when file size is 0 to avoid a 416 error.
|
406
|
+
return nil if size.zero?
|
438
407
|
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
439
408
|
ranges = []
|
440
409
|
$1.split(/,\s*/).each do |range_spec|
|
@@ -517,39 +486,12 @@ module Rack
|
|
517
486
|
end
|
518
487
|
end
|
519
488
|
|
520
|
-
# A wrapper around Headers
|
521
|
-
# header when set.
|
522
|
-
#
|
523
|
-
# @api private
|
524
|
-
class HeaderHash < Hash # :nodoc:
|
525
|
-
def self.[](headers)
|
526
|
-
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
527
|
-
if headers.is_a?(Headers) && !headers.frozen?
|
528
|
-
return headers
|
529
|
-
end
|
530
|
-
|
531
|
-
new_headers = Headers.new
|
532
|
-
headers.each{|k,v| new_headers[k] = v}
|
533
|
-
new_headers
|
534
|
-
end
|
535
|
-
|
536
|
-
def self.new(hash = {})
|
537
|
-
warn "Rack::Utils::HeaderHash is deprecated and will be removed in Rack 3.1, switch to Rack::Headers", uplevel: 1
|
538
|
-
headers = Headers.new
|
539
|
-
hash.each{|k,v| headers[k] = v}
|
540
|
-
headers
|
541
|
-
end
|
542
|
-
|
543
|
-
def self.allocate
|
544
|
-
raise TypeError, "cannot allocate HeaderHash"
|
545
|
-
end
|
546
|
-
end
|
547
|
-
|
548
489
|
# Every standard HTTP code mapped to the appropriate message.
|
549
490
|
# Generated with:
|
550
|
-
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv
|
551
|
-
# ruby -
|
552
|
-
#
|
491
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv \
|
492
|
+
# | ruby -rcsv -e "puts CSV.parse(STDIN, headers: true) \
|
493
|
+
# .reject {|v| v['Description'] == 'Unassigned' or v['Description'].include? '(' } \
|
494
|
+
# .map {|v| %Q/#{v['Value']} => '#{v['Description']}'/ }.join(','+?\n)"
|
553
495
|
HTTP_STATUS_CODES = {
|
554
496
|
100 => 'Continue',
|
555
497
|
101 => 'Switching Protocols',
|
@@ -571,7 +513,6 @@ module Rack
|
|
571
513
|
303 => 'See Other',
|
572
514
|
304 => 'Not Modified',
|
573
515
|
305 => 'Use Proxy',
|
574
|
-
306 => '(Unused)',
|
575
516
|
307 => 'Temporary Redirect',
|
576
517
|
308 => 'Permanent Redirect',
|
577
518
|
400 => 'Bad Request',
|
@@ -587,13 +528,13 @@ module Rack
|
|
587
528
|
410 => 'Gone',
|
588
529
|
411 => 'Length Required',
|
589
530
|
412 => 'Precondition Failed',
|
590
|
-
413 => '
|
531
|
+
413 => 'Content Too Large',
|
591
532
|
414 => 'URI Too Long',
|
592
533
|
415 => 'Unsupported Media Type',
|
593
534
|
416 => 'Range Not Satisfiable',
|
594
535
|
417 => 'Expectation Failed',
|
595
536
|
421 => 'Misdirected Request',
|
596
|
-
422 => 'Unprocessable
|
537
|
+
422 => 'Unprocessable Content',
|
597
538
|
423 => 'Locked',
|
598
539
|
424 => 'Failed Dependency',
|
599
540
|
425 => 'Too Early',
|
@@ -601,7 +542,7 @@ module Rack
|
|
601
542
|
428 => 'Precondition Required',
|
602
543
|
429 => 'Too Many Requests',
|
603
544
|
431 => 'Request Header Fields Too Large',
|
604
|
-
451 => 'Unavailable
|
545
|
+
451 => 'Unavailable For Legal Reasons',
|
605
546
|
500 => 'Internal Server Error',
|
606
547
|
501 => 'Not Implemented',
|
607
548
|
502 => 'Bad Gateway',
|
@@ -611,8 +552,6 @@ module Rack
|
|
611
552
|
506 => 'Variant Also Negotiates',
|
612
553
|
507 => 'Insufficient Storage',
|
613
554
|
508 => 'Loop Detected',
|
614
|
-
509 => 'Bandwidth Limit Exceeded',
|
615
|
-
510 => 'Not Extended',
|
616
555
|
511 => 'Network Authentication Required'
|
617
556
|
}
|
618
557
|
|
@@ -620,12 +559,34 @@ module Rack
|
|
620
559
|
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
621
560
|
|
622
561
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
623
|
-
[message.downcase.gsub(/\s
|
562
|
+
[message.downcase.gsub(/\s|-/, '_').to_sym, code]
|
624
563
|
}.flatten]
|
625
564
|
|
565
|
+
OBSOLETE_SYMBOLS_TO_STATUS_CODES = {
|
566
|
+
payload_too_large: 413,
|
567
|
+
unprocessable_entity: 422,
|
568
|
+
bandwidth_limit_exceeded: 509,
|
569
|
+
not_extended: 510
|
570
|
+
}.freeze
|
571
|
+
private_constant :OBSOLETE_SYMBOLS_TO_STATUS_CODES
|
572
|
+
|
573
|
+
OBSOLETE_SYMBOL_MAPPINGS = {
|
574
|
+
payload_too_large: :content_too_large,
|
575
|
+
unprocessable_entity: :unprocessable_content
|
576
|
+
}.freeze
|
577
|
+
private_constant :OBSOLETE_SYMBOL_MAPPINGS
|
578
|
+
|
626
579
|
def status_code(status)
|
627
580
|
if status.is_a?(Symbol)
|
628
|
-
SYMBOL_TO_STATUS_CODE.fetch(status)
|
581
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) do
|
582
|
+
fallback_code = OBSOLETE_SYMBOLS_TO_STATUS_CODES.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
583
|
+
message = "Status code #{status.inspect} is deprecated and will be removed in a future version of Rack."
|
584
|
+
if canonical_symbol = OBSOLETE_SYMBOL_MAPPINGS[status]
|
585
|
+
message = "#{message} Please use #{canonical_symbol.inspect} instead."
|
586
|
+
end
|
587
|
+
warn message, uplevel: 1
|
588
|
+
fallback_code
|
589
|
+
end
|
629
590
|
else
|
630
591
|
status.to_i
|
631
592
|
end
|
data/lib/rack/version.rb
CHANGED
@@ -12,20 +12,7 @@
|
|
12
12
|
# so it should be enough just to <tt>require 'rack'</tt> in your code.
|
13
13
|
|
14
14
|
module Rack
|
15
|
-
|
16
|
-
VERSION = [1, 3].freeze
|
17
|
-
deprecate_constant :VERSION
|
18
|
-
|
19
|
-
VERSION_STRING = "1.3".freeze
|
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.11"
|
15
|
+
RELEASE = "3.1.0"
|
29
16
|
|
30
17
|
# Return the Rack release as a dotted string.
|
31
18
|
def self.release
|
data/lib/rack.rb
CHANGED
@@ -15,23 +15,21 @@ require_relative 'rack/version'
|
|
15
15
|
require_relative 'rack/constants'
|
16
16
|
|
17
17
|
module Rack
|
18
|
-
autoload :
|
18
|
+
autoload :BadRequest, "rack/bad_request"
|
19
19
|
autoload :BodyProxy, "rack/body_proxy"
|
20
|
+
autoload :Builder, "rack/builder"
|
20
21
|
autoload :Cascade, "rack/cascade"
|
21
|
-
autoload :Chunked, "rack/chunked"
|
22
22
|
autoload :CommonLogger, "rack/common_logger"
|
23
23
|
autoload :ConditionalGet, "rack/conditional_get"
|
24
24
|
autoload :Config, "rack/config"
|
25
25
|
autoload :ContentLength, "rack/content_length"
|
26
26
|
autoload :ContentType, "rack/content_type"
|
27
|
+
autoload :Deflater, "rack/deflater"
|
28
|
+
autoload :Directory, "rack/directory"
|
27
29
|
autoload :ETag, "rack/etag"
|
28
30
|
autoload :Events, "rack/events"
|
29
|
-
autoload :File, "rack/file"
|
30
31
|
autoload :Files, "rack/files"
|
31
|
-
autoload :Deflater, "rack/deflater"
|
32
|
-
autoload :Directory, "rack/directory"
|
33
32
|
autoload :ForwardRequest, "rack/recursive"
|
34
|
-
autoload :Handler, "rack/handler"
|
35
33
|
autoload :Head, "rack/head"
|
36
34
|
autoload :Headers, "rack/headers"
|
37
35
|
autoload :Lint, "rack/lint"
|
@@ -40,32 +38,28 @@ module Rack
|
|
40
38
|
autoload :MediaType, "rack/media_type"
|
41
39
|
autoload :MethodOverride, "rack/method_override"
|
42
40
|
autoload :Mime, "rack/mime"
|
41
|
+
autoload :MockRequest, "rack/mock_request"
|
42
|
+
autoload :MockResponse, "rack/mock_response"
|
43
|
+
autoload :Multipart, "rack/multipart"
|
43
44
|
autoload :NullLogger, "rack/null_logger"
|
44
45
|
autoload :QueryParser, "rack/query_parser"
|
45
46
|
autoload :Recursive, "rack/recursive"
|
46
47
|
autoload :Reloader, "rack/reloader"
|
48
|
+
autoload :Request, "rack/request"
|
49
|
+
autoload :Response, "rack/response"
|
47
50
|
autoload :RewindableInput, "rack/rewindable_input"
|
48
51
|
autoload :Runtime, "rack/runtime"
|
49
52
|
autoload :Sendfile, "rack/sendfile"
|
50
|
-
autoload :Server, "rack/server"
|
51
53
|
autoload :ShowExceptions, "rack/show_exceptions"
|
52
54
|
autoload :ShowStatus, "rack/show_status"
|
53
55
|
autoload :Static, "rack/static"
|
54
56
|
autoload :TempfileReaper, "rack/tempfile_reaper"
|
55
57
|
autoload :URLMap, "rack/urlmap"
|
56
58
|
autoload :Utils, "rack/utils"
|
57
|
-
autoload :Multipart, "rack/multipart"
|
58
|
-
|
59
|
-
autoload :MockRequest, "rack/mock_request"
|
60
|
-
autoload :MockResponse, "rack/mock_response"
|
61
|
-
|
62
|
-
autoload :Request, "rack/request"
|
63
|
-
autoload :Response, "rack/response"
|
64
59
|
|
65
60
|
module Auth
|
66
61
|
autoload :Basic, "rack/auth/basic"
|
67
|
-
autoload :AbstractRequest, "rack/auth/abstract/request"
|
68
62
|
autoload :AbstractHandler, "rack/auth/abstract/handler"
|
69
|
-
autoload :
|
63
|
+
autoload :AbstractRequest, "rack/auth/abstract/request"
|
70
64
|
end
|
71
65
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leah Neukirchen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-06-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -89,15 +89,10 @@ files:
|
|
89
89
|
- lib/rack/auth/abstract/handler.rb
|
90
90
|
- lib/rack/auth/abstract/request.rb
|
91
91
|
- lib/rack/auth/basic.rb
|
92
|
-
- lib/rack/
|
93
|
-
- lib/rack/auth/digest/md5.rb
|
94
|
-
- lib/rack/auth/digest/nonce.rb
|
95
|
-
- lib/rack/auth/digest/params.rb
|
96
|
-
- lib/rack/auth/digest/request.rb
|
92
|
+
- lib/rack/bad_request.rb
|
97
93
|
- lib/rack/body_proxy.rb
|
98
94
|
- lib/rack/builder.rb
|
99
95
|
- lib/rack/cascade.rb
|
100
|
-
- lib/rack/chunked.rb
|
101
96
|
- lib/rack/common_logger.rb
|
102
97
|
- lib/rack/conditional_get.rb
|
103
98
|
- lib/rack/config.rb
|
@@ -108,7 +103,6 @@ files:
|
|
108
103
|
- lib/rack/directory.rb
|
109
104
|
- lib/rack/etag.rb
|
110
105
|
- lib/rack/events.rb
|
111
|
-
- lib/rack/file.rb
|
112
106
|
- lib/rack/files.rb
|
113
107
|
- lib/rack/head.rb
|
114
108
|
- lib/rack/headers.rb
|
@@ -164,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
164
158
|
- !ruby/object:Gem::Version
|
165
159
|
version: '0'
|
166
160
|
requirements: []
|
167
|
-
rubygems_version: 3.5.
|
161
|
+
rubygems_version: 3.5.9
|
168
162
|
signing_key:
|
169
163
|
specification_version: 4
|
170
164
|
summary: A modular Ruby webserver interface.
|
data/lib/rack/auth/digest/md5.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require_relative '../digest'
|
@@ -1 +0,0 @@
|
|
1
|
-
require_relative '../digest'
|
@@ -1 +0,0 @@
|
|
1
|
-
require_relative '../digest'
|
@@ -1 +0,0 @@
|
|
1
|
-
require_relative '../digest'
|
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
|
-
|