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/request.rb
CHANGED
|
@@ -61,9 +61,14 @@ module Rack
|
|
|
61
61
|
|
|
62
62
|
def initialize(env)
|
|
63
63
|
@env = env
|
|
64
|
+
@ip = nil
|
|
64
65
|
@params = nil
|
|
65
66
|
end
|
|
66
67
|
|
|
68
|
+
def ip
|
|
69
|
+
@ip ||= super
|
|
70
|
+
end
|
|
71
|
+
|
|
67
72
|
def params
|
|
68
73
|
@params ||= super
|
|
69
74
|
end
|
|
@@ -398,8 +403,8 @@ module Rack
|
|
|
398
403
|
return forwarded.last
|
|
399
404
|
end
|
|
400
405
|
when :x_forwarded
|
|
401
|
-
if value = get_header(HTTP_X_FORWARDED_HOST)
|
|
402
|
-
return wrap_ipv6(
|
|
406
|
+
if (value = get_header(HTTP_X_FORWARDED_HOST)) && (x_forwarded_host = split_header(value).last)
|
|
407
|
+
return wrap_ipv6(x_forwarded_host)
|
|
403
408
|
end
|
|
404
409
|
end
|
|
405
410
|
end
|
|
@@ -413,10 +418,9 @@ module Rack
|
|
|
413
418
|
|
|
414
419
|
def ip
|
|
415
420
|
remote_addresses = split_header(get_header('REMOTE_ADDR'))
|
|
416
|
-
external_addresses = reject_trusted_ip_addresses(remote_addresses)
|
|
417
421
|
|
|
418
|
-
|
|
419
|
-
return
|
|
422
|
+
remote_addresses.reverse_each do |ip|
|
|
423
|
+
return ip unless trusted_proxy?(ip)
|
|
420
424
|
end
|
|
421
425
|
|
|
422
426
|
if (forwarded_for = self.forwarded_for) && !forwarded_for.empty?
|
|
@@ -424,7 +428,10 @@ module Rack
|
|
|
424
428
|
# So we reject all the trusted addresses (proxy*) and return the
|
|
425
429
|
# last client. Or if we trust everyone, we just return the first
|
|
426
430
|
# address.
|
|
427
|
-
|
|
431
|
+
forwarded_for.reverse_each do |ip|
|
|
432
|
+
return ip unless trusted_proxy?(ip)
|
|
433
|
+
end
|
|
434
|
+
return forwarded_for.first
|
|
428
435
|
end
|
|
429
436
|
|
|
430
437
|
# If all the addresses are trusted, and we aren't forwarded, just return
|
|
@@ -482,70 +489,42 @@ module Rack
|
|
|
482
489
|
|
|
483
490
|
# Returns the data received in the query string.
|
|
484
491
|
def GET
|
|
485
|
-
|
|
486
|
-
query_string = self.query_string
|
|
487
|
-
if rr_query_string == query_string
|
|
488
|
-
get_header(RACK_REQUEST_QUERY_HASH)
|
|
489
|
-
else
|
|
490
|
-
if rr_query_string
|
|
491
|
-
warn "query string used for GET parsing different from current query string. Starting in Rack 3.2, Rack will used the cached GET value instead of parsing the current query string.", uplevel: 1
|
|
492
|
-
end
|
|
493
|
-
query_hash = parse_query(query_string, '&')
|
|
494
|
-
set_header(RACK_REQUEST_QUERY_STRING, query_string)
|
|
495
|
-
set_header(RACK_REQUEST_QUERY_HASH, query_hash)
|
|
496
|
-
end
|
|
492
|
+
get_header(RACK_REQUEST_QUERY_HASH) || set_header(RACK_REQUEST_QUERY_HASH, parse_query(query_string, '&'))
|
|
497
493
|
end
|
|
498
494
|
|
|
499
|
-
# Returns the data received in the request body.
|
|
495
|
+
# Returns the form data pairs received in the request body.
|
|
500
496
|
#
|
|
501
497
|
# This method support both application/x-www-form-urlencoded and
|
|
502
498
|
# multipart/form-data.
|
|
503
|
-
def
|
|
504
|
-
if
|
|
499
|
+
def form_pairs
|
|
500
|
+
if pairs = get_header(RACK_REQUEST_FORM_PAIRS)
|
|
501
|
+
return pairs
|
|
502
|
+
elsif error = get_header(RACK_REQUEST_FORM_ERROR)
|
|
505
503
|
raise error.class, error.message, cause: error.cause
|
|
506
504
|
end
|
|
507
505
|
|
|
508
506
|
begin
|
|
509
507
|
rack_input = get_header(RACK_INPUT)
|
|
510
508
|
|
|
511
|
-
# If the form hash was already memoized:
|
|
512
|
-
if form_hash = get_header(RACK_REQUEST_FORM_HASH)
|
|
513
|
-
form_input = get_header(RACK_REQUEST_FORM_INPUT)
|
|
514
|
-
# And it was memoized from the same input:
|
|
515
|
-
if form_input.equal?(rack_input)
|
|
516
|
-
return form_hash
|
|
517
|
-
elsif form_input
|
|
518
|
-
warn "input stream used for POST parsing different from current input stream. Starting in Rack 3.2, Rack will used the cached POST value instead of parsing the current input stream.", uplevel: 1
|
|
519
|
-
end
|
|
520
|
-
end
|
|
521
|
-
|
|
522
509
|
# Otherwise, figure out how to parse the input:
|
|
523
510
|
if rack_input.nil?
|
|
524
|
-
set_header
|
|
525
|
-
set_header(RACK_REQUEST_FORM_HASH, {})
|
|
511
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
|
526
512
|
elsif form_data? || parseable_data?
|
|
527
513
|
if pairs = Rack::Multipart.parse_multipart(env, Rack::Multipart::ParamList)
|
|
528
514
|
set_header RACK_REQUEST_FORM_PAIRS, pairs
|
|
529
|
-
set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
|
|
530
515
|
else
|
|
531
|
-
|
|
532
|
-
# in case the slice! call below removes the last byte
|
|
533
|
-
# If read returns nil, use the empty string
|
|
534
|
-
form_vars = get_header(RACK_INPUT).read(query_parser.bytesize_limit + 2) || ''
|
|
516
|
+
form_vars = get_header(RACK_INPUT).read
|
|
535
517
|
|
|
536
518
|
# Fix for Safari Ajax postings that always append \0
|
|
537
519
|
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
|
538
520
|
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
|
539
521
|
|
|
540
522
|
set_header RACK_REQUEST_FORM_VARS, form_vars
|
|
541
|
-
|
|
523
|
+
pairs = query_parser.parse_query_pairs(form_vars, '&')
|
|
524
|
+
set_header(RACK_REQUEST_FORM_PAIRS, pairs)
|
|
542
525
|
end
|
|
543
|
-
|
|
544
|
-
set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
|
|
545
|
-
get_header RACK_REQUEST_FORM_HASH
|
|
546
526
|
else
|
|
547
|
-
set_header
|
|
548
|
-
set_header(RACK_REQUEST_FORM_HASH, {})
|
|
527
|
+
set_header(RACK_REQUEST_FORM_PAIRS, [])
|
|
549
528
|
end
|
|
550
529
|
rescue => error
|
|
551
530
|
set_header(RACK_REQUEST_FORM_ERROR, error)
|
|
@@ -553,6 +532,21 @@ module Rack
|
|
|
553
532
|
end
|
|
554
533
|
end
|
|
555
534
|
|
|
535
|
+
# Returns the data received in the request body.
|
|
536
|
+
#
|
|
537
|
+
# This method support both application/x-www-form-urlencoded and
|
|
538
|
+
# multipart/form-data.
|
|
539
|
+
def POST
|
|
540
|
+
if form_hash = get_header(RACK_REQUEST_FORM_HASH)
|
|
541
|
+
return form_hash
|
|
542
|
+
elsif error = get_header(RACK_REQUEST_FORM_ERROR)
|
|
543
|
+
raise error.class, error.message, cause: error.cause
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
pairs = form_pairs
|
|
547
|
+
set_header RACK_REQUEST_FORM_HASH, expand_param_pairs(pairs)
|
|
548
|
+
end
|
|
549
|
+
|
|
556
550
|
# The union of GET and POST data.
|
|
557
551
|
#
|
|
558
552
|
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
|
@@ -560,6 +554,10 @@ module Rack
|
|
|
560
554
|
self.GET.merge(self.POST)
|
|
561
555
|
end
|
|
562
556
|
|
|
557
|
+
# Allow overriding the query parser that the receiver will use.
|
|
558
|
+
# By default Rack::Utils.default_query_parser is used.
|
|
559
|
+
attr_writer :query_parser
|
|
560
|
+
|
|
563
561
|
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
|
564
562
|
#
|
|
565
563
|
# The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
|
|
@@ -619,13 +617,6 @@ module Rack
|
|
|
619
617
|
Rack::Request.ip_filter.call(ip)
|
|
620
618
|
end
|
|
621
619
|
|
|
622
|
-
# like Hash#values_at
|
|
623
|
-
def values_at(*keys)
|
|
624
|
-
warn("Request#values_at is deprecated and will be removed in a future version of Rack. Please use request.params.values_at instead", uplevel: 1)
|
|
625
|
-
|
|
626
|
-
keys.map { |key| params[key] }
|
|
627
|
-
end
|
|
628
|
-
|
|
629
620
|
private
|
|
630
621
|
|
|
631
622
|
def default_session; {}; end
|
|
@@ -673,7 +664,7 @@ module Rack
|
|
|
673
664
|
end
|
|
674
665
|
|
|
675
666
|
def query_parser
|
|
676
|
-
Utils.default_query_parser
|
|
667
|
+
@query_parser || Utils.default_query_parser
|
|
677
668
|
end
|
|
678
669
|
|
|
679
670
|
def parse_query(qs, d = '&')
|
|
@@ -681,6 +672,7 @@ module Rack
|
|
|
681
672
|
end
|
|
682
673
|
|
|
683
674
|
def parse_multipart
|
|
675
|
+
warn "Rack::Request#parse_multipart is deprecated and will be removed in a future version of Rack.", uplevel: 1
|
|
684
676
|
Rack::Multipart.extract_multipart(self, query_parser)
|
|
685
677
|
end
|
|
686
678
|
|
|
@@ -695,7 +687,7 @@ module Rack
|
|
|
695
687
|
end
|
|
696
688
|
|
|
697
689
|
def split_header(value)
|
|
698
|
-
value ? value.strip.split(/[
|
|
690
|
+
value ? value.strip.split(/[, \t]+/) : []
|
|
699
691
|
end
|
|
700
692
|
|
|
701
693
|
# ipv6 extracted from resolv stdlib, simplified
|
|
@@ -728,8 +720,8 @@ module Rack
|
|
|
728
720
|
# Match IPv6 as a string of hex digits and colons in square brackets
|
|
729
721
|
\[(?<address>#{ipv6})\]
|
|
730
722
|
|
|
|
731
|
-
# Match
|
|
732
|
-
(?<address>[
|
|
723
|
+
# Match any other printable string (except square brackets) as a hostname
|
|
724
|
+
(?<address>[[[:graph:]&&[^\[\]]]]*?)
|
|
733
725
|
)
|
|
734
726
|
(:(?<port>\d+))?
|
|
735
727
|
\z
|
|
@@ -743,10 +735,6 @@ module Rack
|
|
|
743
735
|
return match[:host], match[:address], match[:port]&.to_i
|
|
744
736
|
end
|
|
745
737
|
|
|
746
|
-
def reject_trusted_ip_addresses(ip_addresses)
|
|
747
|
-
ip_addresses.reject { |ip| trusted_proxy?(ip) }
|
|
748
|
-
end
|
|
749
|
-
|
|
750
738
|
FORWARDED_SCHEME_HEADERS = {
|
|
751
739
|
proto: HTTP_X_FORWARDED_PROTO,
|
|
752
740
|
scheme: HTTP_X_FORWARDED_SCHEME
|
data/lib/rack/sendfile.rb
CHANGED
|
@@ -16,21 +16,21 @@ module Rack
|
|
|
16
16
|
# delivery code.
|
|
17
17
|
#
|
|
18
18
|
# In order to take advantage of this middleware, the response body must
|
|
19
|
-
# respond to +to_path+ and the request must include an
|
|
19
|
+
# respond to +to_path+ and the request must include an x-sendfile-type
|
|
20
20
|
# header. Rack::Files and other components implement +to_path+ so there's
|
|
21
|
-
# rarely anything you need to do in your application. The
|
|
21
|
+
# rarely anything you need to do in your application. The x-sendfile-type
|
|
22
22
|
# header is typically set in your web servers configuration. The following
|
|
23
23
|
# sections attempt to document
|
|
24
24
|
#
|
|
25
25
|
# === Nginx
|
|
26
26
|
#
|
|
27
|
-
# Nginx supports the
|
|
27
|
+
# Nginx supports the x-accel-redirect header. This is similar to x-sendfile
|
|
28
28
|
# but requires parts of the filesystem to be mapped into a private URL
|
|
29
29
|
# hierarchy.
|
|
30
30
|
#
|
|
31
31
|
# The following example shows the Nginx configuration required to create
|
|
32
|
-
# a private "/files/" area, enable
|
|
33
|
-
#
|
|
32
|
+
# a private "/files/" area, enable x-accel-redirect, and pass the special
|
|
33
|
+
# x-sendfile-type and x-accel-mapping headers to the backend:
|
|
34
34
|
#
|
|
35
35
|
# location ~ /files/(.*) {
|
|
36
36
|
# internal;
|
|
@@ -44,29 +44,24 @@ module Rack
|
|
|
44
44
|
# proxy_set_header X-Real-IP $remote_addr;
|
|
45
45
|
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
46
46
|
#
|
|
47
|
+
# proxy_set_header x-sendfile-type x-accel-redirect;
|
|
47
48
|
# proxy_set_header x-accel-mapping /var/www/=/files/;
|
|
48
49
|
#
|
|
49
50
|
# proxy_pass http://127.0.0.1:8080/;
|
|
50
51
|
# }
|
|
51
52
|
#
|
|
52
|
-
#
|
|
53
|
+
# Note that the x-sendfile-type header must be set exactly as shown above.
|
|
54
|
+
# The x-accel-mapping header should specify the location on the file system,
|
|
53
55
|
# followed by an equals sign (=), followed name of the private URL pattern
|
|
54
|
-
# that it maps to. The middleware performs a
|
|
56
|
+
# that it maps to. The middleware performs a simple substitution on the
|
|
55
57
|
# resulting path.
|
|
56
58
|
#
|
|
57
|
-
# To enable `x-accel-redirect`, you must configure the middleware explicitly:
|
|
58
|
-
#
|
|
59
|
-
# use Rack::Sendfile, "x-accel-redirect"
|
|
60
|
-
#
|
|
61
|
-
# For security reasons, the `x-sendfile-type` header from requests is ignored.
|
|
62
|
-
# The sendfile variation must be set via the middleware constructor.
|
|
63
|
-
#
|
|
64
59
|
# See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
|
|
65
60
|
#
|
|
66
61
|
# === lighttpd
|
|
67
62
|
#
|
|
68
|
-
# Lighttpd has supported some variation of the
|
|
69
|
-
# time, although only recent version support
|
|
63
|
+
# Lighttpd has supported some variation of the x-sendfile header for some
|
|
64
|
+
# time, although only recent version support x-sendfile in a reverse proxy
|
|
70
65
|
# configuration.
|
|
71
66
|
#
|
|
72
67
|
# $HTTP["host"] == "example.com" {
|
|
@@ -88,7 +83,7 @@ module Rack
|
|
|
88
83
|
#
|
|
89
84
|
# === Apache
|
|
90
85
|
#
|
|
91
|
-
#
|
|
86
|
+
# x-sendfile is supported under Apache 2.x using a separate module:
|
|
92
87
|
#
|
|
93
88
|
# https://tn123.org/mod_xsendfile/
|
|
94
89
|
#
|
|
@@ -102,28 +97,16 @@ module Rack
|
|
|
102
97
|
# === Mapping parameter
|
|
103
98
|
#
|
|
104
99
|
# The third parameter allows for an overriding extension of the
|
|
105
|
-
#
|
|
100
|
+
# x-accel-mapping header. Mappings should be provided in tuples of internal to
|
|
106
101
|
# external. The internal values may contain regular expression syntax, they
|
|
107
102
|
# will be matched with case indifference.
|
|
108
|
-
#
|
|
109
|
-
# When `x-accel-redirect` is explicitly enabled via the variation parameter,
|
|
110
|
-
# and no application-level mappings are provided, the middleware will read
|
|
111
|
-
# the `x-accel-mapping` header from the proxy. This allows nginx to control
|
|
112
|
-
# the path mapping without requiring application-level configuration.
|
|
113
|
-
#
|
|
114
|
-
# === Security
|
|
115
|
-
#
|
|
116
|
-
# For security reasons, the `x-sendfile-type` header from HTTP requests is
|
|
117
|
-
# ignored. The sendfile variation must be explicitly configured via the
|
|
118
|
-
# middleware constructor to prevent information disclosure vulnerabilities
|
|
119
|
-
# where attackers could bypass proxy restrictions.
|
|
120
103
|
|
|
121
104
|
class Sendfile
|
|
122
105
|
def initialize(app, variation = nil, mappings = [])
|
|
123
106
|
@app = app
|
|
124
107
|
@variation = variation
|
|
125
108
|
@mappings = mappings.map do |internal, external|
|
|
126
|
-
[
|
|
109
|
+
[/^#{internal}/i, external]
|
|
127
110
|
end
|
|
128
111
|
end
|
|
129
112
|
|
|
@@ -162,35 +145,22 @@ module Rack
|
|
|
162
145
|
end
|
|
163
146
|
|
|
164
147
|
private
|
|
165
|
-
|
|
166
148
|
def variation(env)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def x_accel_mapping(env)
|
|
173
|
-
# Only allow header when:
|
|
174
|
-
# 1. `x-accel-redirect` is explicitly enabled via constructor.
|
|
175
|
-
# 2. No application-level mappings are configured.
|
|
176
|
-
return nil unless @variation =~ /x-accel-redirect/i
|
|
177
|
-
return nil if @mappings.any?
|
|
178
|
-
|
|
179
|
-
env['HTTP_X_ACCEL_MAPPING']
|
|
149
|
+
@variation ||
|
|
150
|
+
env['sendfile.type'] ||
|
|
151
|
+
env['HTTP_X_SENDFILE_TYPE']
|
|
180
152
|
end
|
|
181
153
|
|
|
182
154
|
def map_accel_path(env, path)
|
|
183
155
|
if mapping = @mappings.find { |internal, _| internal =~ path }
|
|
184
|
-
|
|
185
|
-
elsif mapping =
|
|
186
|
-
# Safe to use header: explicit config + no app mappings:
|
|
156
|
+
path.sub(*mapping)
|
|
157
|
+
elsif mapping = env['HTTP_X_ACCEL_MAPPING']
|
|
187
158
|
mapping.split(',').map(&:strip).each do |m|
|
|
188
159
|
internal, external = m.split('=', 2).map(&:strip)
|
|
189
|
-
new_path = path.sub(
|
|
160
|
+
new_path = path.sub(/^#{internal}/i, external)
|
|
190
161
|
return new_path unless path == new_path
|
|
191
162
|
end
|
|
192
|
-
|
|
193
|
-
return path
|
|
163
|
+
path
|
|
194
164
|
end
|
|
195
165
|
end
|
|
196
166
|
end
|
data/lib/rack/show_exceptions.rb
CHANGED
|
@@ -65,8 +65,12 @@ module Rack
|
|
|
65
65
|
def dump_exception(exception)
|
|
66
66
|
if exception.respond_to?(:detailed_message)
|
|
67
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.
|
|
68
71
|
else
|
|
69
72
|
message = exception.message
|
|
73
|
+
# :nocov:
|
|
70
74
|
end
|
|
71
75
|
string = "#{exception.class}: #{message}\n".dup
|
|
72
76
|
string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
|
|
@@ -401,7 +405,5 @@ module Rack
|
|
|
401
405
|
</body>
|
|
402
406
|
</html>
|
|
403
407
|
HTML
|
|
404
|
-
|
|
405
|
-
# :startdoc:
|
|
406
408
|
end
|
|
407
409
|
end
|
data/lib/rack/show_status.rb
CHANGED
data/lib/rack/static.rb
CHANGED
|
@@ -93,9 +93,6 @@ module Rack
|
|
|
93
93
|
def initialize(app, options = {})
|
|
94
94
|
@app = app
|
|
95
95
|
@urls = options[:urls] || ["/favicon.ico"]
|
|
96
|
-
if @urls.kind_of?(Array)
|
|
97
|
-
@urls = @urls.map { |url| [url, url.end_with?('/') ? url : "#{url}/".freeze].freeze }.freeze
|
|
98
|
-
end
|
|
99
96
|
@index = options[:index]
|
|
100
97
|
@gzip = options[:gzip]
|
|
101
98
|
@cascade = options[:cascade]
|
|
@@ -118,7 +115,7 @@ module Rack
|
|
|
118
115
|
end
|
|
119
116
|
|
|
120
117
|
def route_file(path)
|
|
121
|
-
@urls.kind_of?(Array) && @urls.any? { |url
|
|
118
|
+
@urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
|
|
122
119
|
end
|
|
123
120
|
|
|
124
121
|
def can_serve(path)
|
|
@@ -168,8 +165,6 @@ module Rack
|
|
|
168
165
|
|
|
169
166
|
# Convert HTTP header rules to HTTP headers
|
|
170
167
|
def applicable_rules(path)
|
|
171
|
-
path = ::Rack::Utils.unescape_path(path)
|
|
172
|
-
|
|
173
168
|
@header_rules.find_all do |rule, new_headers|
|
|
174
169
|
case rule
|
|
175
170
|
when :all
|
|
@@ -177,9 +172,10 @@ module Rack
|
|
|
177
172
|
when :fonts
|
|
178
173
|
/\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
|
|
179
174
|
when String
|
|
175
|
+
path = ::Rack::Utils.unescape(path)
|
|
180
176
|
path.start_with?(rule) || path.start_with?('/' + rule)
|
|
181
177
|
when Array
|
|
182
|
-
|
|
178
|
+
/\.(#{rule.join('|')})\z/.match?(path)
|
|
183
179
|
when Regexp
|
|
184
180
|
rule.match?(path)
|
|
185
181
|
else
|