actionpack 4.2.11.3 → 5.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +379 -462
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller.rb +0 -2
- data/lib/abstract_controller/base.rb +17 -32
- data/lib/abstract_controller/callbacks.rb +52 -19
- data/lib/abstract_controller/collector.rb +4 -9
- data/lib/abstract_controller/helpers.rb +2 -2
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
- data/lib/abstract_controller/rendering.rb +27 -22
- data/lib/abstract_controller/translation.rb +8 -7
- data/lib/action_controller.rb +4 -3
- data/lib/action_controller/api.rb +146 -0
- data/lib/action_controller/base.rb +6 -10
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/caching/fragments.rb +48 -3
- data/lib/action_controller/form_builder.rb +48 -0
- data/lib/action_controller/log_subscriber.rb +1 -10
- data/lib/action_controller/metal.rb +89 -62
- data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
- data/lib/action_controller/metal/conditional_get.rb +65 -24
- data/lib/action_controller/metal/cookies.rb +0 -2
- data/lib/action_controller/metal/data_streaming.rb +2 -22
- data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
- data/lib/action_controller/metal/exceptions.rb +11 -6
- data/lib/action_controller/metal/force_ssl.rb +6 -6
- data/lib/action_controller/metal/head.rb +14 -7
- data/lib/action_controller/metal/helpers.rb +9 -5
- data/lib/action_controller/metal/http_authentication.rb +37 -38
- data/lib/action_controller/metal/implicit_render.rb +23 -6
- data/lib/action_controller/metal/instrumentation.rb +0 -1
- data/lib/action_controller/metal/live.rb +17 -55
- data/lib/action_controller/metal/mime_responds.rb +17 -37
- data/lib/action_controller/metal/params_wrapper.rb +8 -8
- data/lib/action_controller/metal/redirecting.rb +32 -9
- data/lib/action_controller/metal/renderers.rb +10 -8
- data/lib/action_controller/metal/rendering.rb +38 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +67 -35
- data/lib/action_controller/metal/rescue.rb +2 -4
- data/lib/action_controller/metal/streaming.rb +4 -4
- data/lib/action_controller/metal/strong_parameters.rb +231 -78
- data/lib/action_controller/metal/testing.rb +1 -12
- data/lib/action_controller/metal/url_for.rb +12 -5
- data/lib/action_controller/renderer.rb +111 -0
- data/lib/action_controller/template_assertions.rb +9 -0
- data/lib/action_controller/test_case.rb +267 -363
- data/lib/action_dispatch.rb +2 -1
- data/lib/action_dispatch/http/cache.rb +23 -26
- data/lib/action_dispatch/http/filter_parameters.rb +6 -8
- data/lib/action_dispatch/http/filter_redirect.rb +7 -8
- data/lib/action_dispatch/http/headers.rb +28 -11
- data/lib/action_dispatch/http/mime_negotiation.rb +40 -26
- data/lib/action_dispatch/http/mime_type.rb +92 -61
- data/lib/action_dispatch/http/mime_types.rb +1 -4
- data/lib/action_dispatch/http/parameter_filter.rb +18 -8
- data/lib/action_dispatch/http/parameters.rb +45 -41
- data/lib/action_dispatch/http/request.rb +146 -82
- data/lib/action_dispatch/http/response.rb +180 -99
- data/lib/action_dispatch/http/url.rb +117 -8
- data/lib/action_dispatch/journey/formatter.rb +34 -28
- data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
- data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
- data/lib/action_dispatch/journey/nodes/node.rb +14 -4
- data/lib/action_dispatch/journey/parser_extras.rb +4 -0
- data/lib/action_dispatch/journey/path/pattern.rb +37 -41
- data/lib/action_dispatch/journey/route.rb +71 -17
- data/lib/action_dispatch/journey/router.rb +5 -6
- data/lib/action_dispatch/journey/router/utils.rb +5 -5
- data/lib/action_dispatch/journey/routes.rb +14 -15
- data/lib/action_dispatch/journey/visitors.rb +86 -43
- data/lib/action_dispatch/middleware/cookies.rb +184 -135
- data/lib/action_dispatch/middleware/debug_exceptions.rb +115 -45
- data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -20
- data/lib/action_dispatch/middleware/flash.rb +61 -45
- data/lib/action_dispatch/middleware/load_interlock.rb +21 -0
- data/lib/action_dispatch/middleware/params_parser.rb +30 -46
- data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
- data/lib/action_dispatch/middleware/reloader.rb +2 -4
- data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
- data/lib/action_dispatch/middleware/request_id.rb +11 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
- data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +29 -23
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
- data/lib/action_dispatch/middleware/ssl.rb +93 -36
- data/lib/action_dispatch/middleware/stack.rb +43 -48
- data/lib/action_dispatch/middleware/static.rb +52 -40
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
- data/lib/action_dispatch/railtie.rb +0 -2
- data/lib/action_dispatch/request/session.rb +66 -34
- data/lib/action_dispatch/request/utils.rb +51 -19
- data/lib/action_dispatch/routing.rb +3 -8
- data/lib/action_dispatch/routing/inspector.rb +6 -30
- data/lib/action_dispatch/routing/mapper.rb +447 -322
- data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +124 -227
- data/lib/action_dispatch/routing/url_for.rb +27 -10
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +27 -9
- data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
- data/lib/action_dispatch/testing/integration.rb +237 -76
- data/lib/action_dispatch/testing/test_process.rb +5 -5
- data/lib/action_dispatch/testing/test_request.rb +12 -21
- data/lib/action_dispatch/testing/test_response.rb +1 -4
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +26 -25
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -8,6 +8,10 @@ end
|
|
8
8
|
|
9
9
|
module ActionDispatch
|
10
10
|
module Session
|
11
|
+
# A session store that uses MemCache to implement storage.
|
12
|
+
#
|
13
|
+
# ==== Options
|
14
|
+
# * <tt>expire_after</tt> - The length of time a session will be stored before automatically expiring.
|
11
15
|
class MemCacheStore < Rack::Session::Dalli
|
12
16
|
include Compatibility
|
13
17
|
include StaleSessionCheck
|
@@ -27,24 +27,26 @@ module ActionDispatch
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def call(env)
|
30
|
+
request = ActionDispatch::Request.new env
|
30
31
|
@app.call(env)
|
31
32
|
rescue Exception => exception
|
32
|
-
if
|
33
|
-
|
33
|
+
if request.show_exceptions?
|
34
|
+
render_exception(request, exception)
|
34
35
|
else
|
35
|
-
|
36
|
+
raise exception
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
40
|
private
|
40
41
|
|
41
|
-
def render_exception(
|
42
|
-
|
42
|
+
def render_exception(request, exception)
|
43
|
+
backtrace_cleaner = request.get_header 'action_dispatch.backtrace_cleaner'
|
44
|
+
wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
|
43
45
|
status = wrapper.status_code
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
response = @exceptions_app.call(env)
|
46
|
+
request.set_header "action_dispatch.exception", wrapper.exception
|
47
|
+
request.set_header "action_dispatch.original_path", request.path_info
|
48
|
+
request.path_info = "/#{status}"
|
49
|
+
response = @exceptions_app.call(request.env)
|
48
50
|
response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
|
49
51
|
rescue Exception => failsafe_error
|
50
52
|
$stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
|
@@ -1,72 +1,129 @@
|
|
1
1
|
module ActionDispatch
|
2
|
+
# This middleware is added to the stack when `config.force_ssl = true`.
|
3
|
+
# It does three jobs to enforce secure HTTP requests:
|
4
|
+
#
|
5
|
+
# 1. TLS redirect. http:// requests are permanently redirected to https://
|
6
|
+
# with the same URL host, path, etc. Pass `:host` and/or `:port` to
|
7
|
+
# modify the destination URL. This is always enabled.
|
8
|
+
#
|
9
|
+
# 2. Secure cookies. Sets the `secure` flag on cookies to tell browsers they
|
10
|
+
# mustn't be sent along with http:// requests. This is always enabled.
|
11
|
+
#
|
12
|
+
# 3. HTTP Strict Transport Security (HSTS). Tells the browser to remember
|
13
|
+
# this site as TLS-only and automatically redirect non-TLS requests.
|
14
|
+
# Enabled by default. Pass `hsts: false` to disable.
|
15
|
+
#
|
16
|
+
# Configure HSTS with `hsts: { … }`:
|
17
|
+
# * `expires`: How long, in seconds, these settings will stick. Defaults to
|
18
|
+
# `180.days` (recommended). The minimum required to qualify for browser
|
19
|
+
# preload lists is `18.weeks`.
|
20
|
+
# * `subdomains`: Set to `true` to tell the browser to apply these settings
|
21
|
+
# to all subdomains. This protects your cookies from interception by a
|
22
|
+
# vulnerable site on a subdomain. Defaults to `false`.
|
23
|
+
# * `preload`: Advertise that this site may be included in browsers'
|
24
|
+
# preloaded HSTS lists. HSTS protects your site on every visit *except the
|
25
|
+
# first visit* since it hasn't seen your HSTS header yet. To close this
|
26
|
+
# gap, browser vendors include a baked-in list of HSTS-enabled sites.
|
27
|
+
# Go to https://hstspreload.appspot.com to submit your site for inclusion.
|
28
|
+
#
|
29
|
+
# Disabling HSTS: To turn off HSTS, omitting the header is not enough.
|
30
|
+
# Browsers will remember the original HSTS directive until it expires.
|
31
|
+
# Instead, use the header to tell browsers to expire HSTS immediately.
|
32
|
+
# Setting `hsts: false` is a shortcut for `hsts: { expires: 0 }`.
|
2
33
|
class SSL
|
3
|
-
|
34
|
+
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
|
35
|
+
# and greater than the 18-week requirement for browser preload lists.
|
36
|
+
HSTS_EXPIRES_IN = 15552000
|
4
37
|
|
5
38
|
def self.default_hsts_options
|
6
|
-
{ :
|
39
|
+
{ expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
|
7
40
|
end
|
8
41
|
|
9
|
-
def initialize(app,
|
42
|
+
def initialize(app, redirect: {}, hsts: {}, **options)
|
10
43
|
@app = app
|
11
44
|
|
12
|
-
|
13
|
-
|
14
|
-
|
45
|
+
if options[:host] || options[:port]
|
46
|
+
ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
|
47
|
+
The `:host` and `:port` options are moving within `:redirect`:
|
48
|
+
`config.ssl_options = { redirect: { host: …, port: … }}`.
|
49
|
+
end_warning
|
50
|
+
@redirect = options.slice(:host, :port)
|
51
|
+
else
|
52
|
+
@redirect = redirect
|
53
|
+
end
|
15
54
|
|
16
|
-
@
|
17
|
-
@port = options[:port]
|
55
|
+
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
|
18
56
|
end
|
19
57
|
|
20
58
|
def call(env)
|
21
|
-
request = Request.new
|
59
|
+
request = Request.new env
|
22
60
|
|
23
61
|
if request.ssl?
|
24
|
-
status, headers, body
|
25
|
-
|
26
|
-
|
27
|
-
|
62
|
+
@app.call(env).tap do |status, headers, body|
|
63
|
+
set_hsts_header! headers
|
64
|
+
flag_cookies_as_secure! headers
|
65
|
+
end
|
28
66
|
else
|
29
|
-
redirect_to_https
|
67
|
+
redirect_to_https request
|
30
68
|
end
|
31
69
|
end
|
32
70
|
|
33
71
|
private
|
34
|
-
def
|
35
|
-
|
36
|
-
port = @port || request.port
|
37
|
-
|
38
|
-
location = "https://#{host}"
|
39
|
-
location << ":#{port}" if port != 80
|
40
|
-
location << request.fullpath
|
41
|
-
|
42
|
-
headers = { 'Content-Type' => 'text/html', 'Location' => location }
|
43
|
-
|
44
|
-
[301, headers, []]
|
72
|
+
def set_hsts_header!(headers)
|
73
|
+
headers['Strict-Transport-Security'.freeze] ||= @hsts_header
|
45
74
|
end
|
46
75
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
76
|
+
def normalize_hsts_options(options)
|
77
|
+
case options
|
78
|
+
# Explicitly disabling HSTS clears the existing setting from browsers
|
79
|
+
# by setting expiry to 0.
|
80
|
+
when false
|
81
|
+
self.class.default_hsts_options.merge(expires: 0)
|
82
|
+
# Default to enabled, with default options.
|
83
|
+
when nil, true
|
84
|
+
self.class.default_hsts_options
|
53
85
|
else
|
54
|
-
|
86
|
+
self.class.default_hsts_options.merge(options)
|
55
87
|
end
|
56
88
|
end
|
57
89
|
|
90
|
+
# http://tools.ietf.org/html/rfc6797#section-6.1
|
91
|
+
def build_hsts_header(hsts)
|
92
|
+
value = "max-age=#{hsts[:expires].to_i}"
|
93
|
+
value << "; includeSubDomains" if hsts[:subdomains]
|
94
|
+
value << "; preload" if hsts[:preload]
|
95
|
+
value
|
96
|
+
end
|
97
|
+
|
58
98
|
def flag_cookies_as_secure!(headers)
|
59
|
-
if cookies = headers['Set-Cookie']
|
60
|
-
cookies = cookies.split("\n")
|
99
|
+
if cookies = headers['Set-Cookie'.freeze]
|
100
|
+
cookies = cookies.split("\n".freeze)
|
61
101
|
|
62
|
-
headers['Set-Cookie'] = cookies.map { |cookie|
|
102
|
+
headers['Set-Cookie'.freeze] = cookies.map { |cookie|
|
63
103
|
if cookie !~ /;\s*secure\s*(;|$)/i
|
64
104
|
"#{cookie}; secure"
|
65
105
|
else
|
66
106
|
cookie
|
67
107
|
end
|
68
|
-
}.join("\n")
|
108
|
+
}.join("\n".freeze)
|
69
109
|
end
|
70
110
|
end
|
111
|
+
|
112
|
+
def redirect_to_https(request)
|
113
|
+
[ @redirect.fetch(:status, 301),
|
114
|
+
{ 'Content-Type' => 'text/html',
|
115
|
+
'Location' => https_location_for(request) },
|
116
|
+
@redirect.fetch(:body, []) ]
|
117
|
+
end
|
118
|
+
|
119
|
+
def https_location_for(request)
|
120
|
+
host = @redirect[:host] || request.host
|
121
|
+
port = @redirect[:port] || request.port
|
122
|
+
|
123
|
+
location = "https://#{host}"
|
124
|
+
location << ":#{port}" if port != 80 && port != 443
|
125
|
+
location << request.fullpath
|
126
|
+
location
|
127
|
+
end
|
71
128
|
end
|
72
129
|
end
|
@@ -4,50 +4,27 @@ require "active_support/dependencies"
|
|
4
4
|
module ActionDispatch
|
5
5
|
class MiddlewareStack
|
6
6
|
class Middleware
|
7
|
-
attr_reader :args, :block, :
|
7
|
+
attr_reader :args, :block, :klass
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@klass =
|
11
|
-
|
12
|
-
|
13
|
-
@klass = klass_or_name
|
14
|
-
@name = @klass.name
|
15
|
-
else
|
16
|
-
@name = klass_or_name.to_s
|
17
|
-
end
|
18
|
-
|
19
|
-
@classcache = ActiveSupport::Dependencies::Reference
|
20
|
-
@args, @block = args, block
|
9
|
+
def initialize(klass, args, block)
|
10
|
+
@klass = klass
|
11
|
+
@args = args
|
12
|
+
@block = block
|
21
13
|
end
|
22
14
|
|
23
|
-
def klass
|
24
|
-
@klass || classcache[@name]
|
25
|
-
end
|
15
|
+
def name; klass.name; end
|
26
16
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
klass == middleware.klass
|
31
|
-
when Class
|
32
|
-
klass == middleware
|
17
|
+
def inspect
|
18
|
+
if klass.is_a?(Class)
|
19
|
+
klass.to_s
|
33
20
|
else
|
34
|
-
|
21
|
+
klass.class.to_s
|
35
22
|
end
|
36
23
|
end
|
37
24
|
|
38
|
-
def inspect
|
39
|
-
klass.to_s
|
40
|
-
end
|
41
|
-
|
42
25
|
def build(app)
|
43
26
|
klass.new(app, *args, &block)
|
44
27
|
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def normalize(object)
|
49
|
-
object.to_s.strip.sub(/^::/, '')
|
50
|
-
end
|
51
28
|
end
|
52
29
|
|
53
30
|
include Enumerable
|
@@ -75,19 +52,17 @@ module ActionDispatch
|
|
75
52
|
middlewares[i]
|
76
53
|
end
|
77
54
|
|
78
|
-
def unshift(*args, &block)
|
79
|
-
|
80
|
-
middlewares.unshift(middleware)
|
55
|
+
def unshift(klass, *args, &block)
|
56
|
+
middlewares.unshift(build_middleware(klass, args, block))
|
81
57
|
end
|
82
58
|
|
83
59
|
def initialize_copy(other)
|
84
60
|
self.middlewares = other.middlewares.dup
|
85
61
|
end
|
86
62
|
|
87
|
-
def insert(index, *args, &block)
|
63
|
+
def insert(index, klass, *args, &block)
|
88
64
|
index = assert_index(index, :before)
|
89
|
-
|
90
|
-
middlewares.insert(index, middleware)
|
65
|
+
middlewares.insert(index, build_middleware(klass, args, block))
|
91
66
|
end
|
92
67
|
|
93
68
|
alias_method :insert_before, :insert
|
@@ -104,26 +79,46 @@ module ActionDispatch
|
|
104
79
|
end
|
105
80
|
|
106
81
|
def delete(target)
|
107
|
-
|
82
|
+
target = get_class target
|
83
|
+
middlewares.delete_if { |m| m.klass == target }
|
108
84
|
end
|
109
85
|
|
110
|
-
def use(*args, &block)
|
111
|
-
|
112
|
-
middlewares.push(middleware)
|
86
|
+
def use(klass, *args, &block)
|
87
|
+
middlewares.push(build_middleware(klass, args, block))
|
113
88
|
end
|
114
89
|
|
115
|
-
def build(app =
|
116
|
-
app ||= block
|
117
|
-
raise "MiddlewareStack#build requires an app" unless app
|
90
|
+
def build(app = Proc.new)
|
118
91
|
middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
|
119
92
|
end
|
120
93
|
|
121
|
-
|
94
|
+
private
|
122
95
|
|
123
96
|
def assert_index(index, where)
|
124
|
-
|
97
|
+
index = get_class index
|
98
|
+
i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index }
|
125
99
|
raise "No such middleware to insert #{where}: #{index.inspect}" unless i
|
126
100
|
i
|
127
101
|
end
|
102
|
+
|
103
|
+
def get_class(klass)
|
104
|
+
if klass.is_a?(String) || klass.is_a?(Symbol)
|
105
|
+
classcache = ActiveSupport::Dependencies::Reference
|
106
|
+
converted_klass = classcache[klass.to_s]
|
107
|
+
ActiveSupport::Deprecation.warn <<-eowarn
|
108
|
+
Passing strings or symbols to the middleware builder is deprecated, please change
|
109
|
+
them to actual class references. For example:
|
110
|
+
|
111
|
+
"#{klass}" => #{converted_klass}
|
112
|
+
|
113
|
+
eowarn
|
114
|
+
converted_klass
|
115
|
+
else
|
116
|
+
klass
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def build_middleware(klass, args, block)
|
121
|
+
Middleware.new(get_class(klass), args, block)
|
122
|
+
end
|
128
123
|
end
|
129
124
|
end
|
@@ -3,33 +3,37 @@ require 'active_support/core_ext/uri'
|
|
3
3
|
|
4
4
|
module ActionDispatch
|
5
5
|
# This middleware returns a file's contents from disk in the body response.
|
6
|
-
# When initialized it can accept
|
7
|
-
#
|
6
|
+
# When initialized, it can accept optional HTTP headers, which will be set
|
7
|
+
# when a response containing a file's contents is delivered.
|
8
8
|
#
|
9
9
|
# This middleware will render the file specified in `env["PATH_INFO"]`
|
10
|
-
# where the base path is in the +root+ directory. For example if the +root+
|
11
|
-
# is set to `public
|
12
|
-
# `assets/application.js` will return a response with contents of a file
|
10
|
+
# where the base path is in the +root+ directory. For example, if the +root+
|
11
|
+
# is set to `public/`, then a request with `env["PATH_INFO"]` of
|
12
|
+
# `assets/application.js` will return a response with the contents of a file
|
13
13
|
# located at `public/assets/application.js` if the file exists. If the file
|
14
|
-
# does not exist a 404 "File not Found" response will be returned.
|
14
|
+
# does not exist, a 404 "File not Found" response will be returned.
|
15
15
|
class FileHandler
|
16
|
-
def initialize(root,
|
16
|
+
def initialize(root, index: 'index', headers: {})
|
17
17
|
@root = root.chomp('/')
|
18
|
-
@
|
19
|
-
|
20
|
-
@file_server = ::Rack::File.new(@root, headers)
|
18
|
+
@file_server = ::Rack::File.new(@root, headers)
|
19
|
+
@index = index
|
21
20
|
end
|
22
21
|
|
22
|
+
# Takes a path to a file. If the file is found, has valid encoding, and has
|
23
|
+
# correct read permissions, the return value is a URI-escaped string
|
24
|
+
# representing the filename. Otherwise, false is returned.
|
25
|
+
#
|
26
|
+
# Used by the `Static` class to check the existence of a valid file
|
27
|
+
# in the server's `public/` directory (see Static#call).
|
23
28
|
def match?(path)
|
24
|
-
path =
|
25
|
-
return false unless
|
29
|
+
path = ::Rack::Utils.unescape_path path
|
30
|
+
return false unless path.valid_encoding?
|
31
|
+
path = Rack::Utils.clean_path_info path
|
26
32
|
|
27
|
-
paths = [path, "#{path}#{ext}", "#{path}
|
28
|
-
Rack::Utils.clean_path_info v
|
29
|
-
}
|
33
|
+
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
|
30
34
|
|
31
35
|
if match = paths.detect { |p|
|
32
|
-
path = File.join(@root, p.force_encoding('UTF-8'))
|
36
|
+
path = File.join(@root, p.force_encoding('UTF-8'.freeze))
|
33
37
|
begin
|
34
38
|
File.file?(path) && File.readable?(path)
|
35
39
|
rescue SystemCallError
|
@@ -37,31 +41,35 @@ module ActionDispatch
|
|
37
41
|
end
|
38
42
|
|
39
43
|
}
|
40
|
-
return ::Rack::Utils.
|
44
|
+
return ::Rack::Utils.escape_path(match)
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
48
|
def call(env)
|
45
|
-
|
49
|
+
serve ActionDispatch::Request.new env
|
50
|
+
end
|
51
|
+
|
52
|
+
def serve(request)
|
53
|
+
path = request.path_info
|
46
54
|
gzip_path = gzip_file_path(path)
|
47
55
|
|
48
|
-
if gzip_path && gzip_encoding_accepted?(
|
49
|
-
|
50
|
-
status, headers, body = @file_server.call(env)
|
56
|
+
if gzip_path && gzip_encoding_accepted?(request)
|
57
|
+
request.path_info = gzip_path
|
58
|
+
status, headers, body = @file_server.call(request.env)
|
51
59
|
if status == 304
|
52
60
|
return [status, headers, body]
|
53
61
|
end
|
54
62
|
headers['Content-Encoding'] = 'gzip'
|
55
63
|
headers['Content-Type'] = content_type(path)
|
56
64
|
else
|
57
|
-
status, headers, body = @file_server.call(env)
|
65
|
+
status, headers, body = @file_server.call(request.env)
|
58
66
|
end
|
59
67
|
|
60
68
|
headers['Vary'] = 'Accept-Encoding' if gzip_path
|
61
69
|
|
62
70
|
return [status, headers, body]
|
63
71
|
ensure
|
64
|
-
|
72
|
+
request.path_info = path
|
65
73
|
end
|
66
74
|
|
67
75
|
private
|
@@ -70,30 +78,26 @@ module ActionDispatch
|
|
70
78
|
end
|
71
79
|
|
72
80
|
def content_type(path)
|
73
|
-
::Rack::Mime.mime_type(::File.extname(path), 'text/plain')
|
81
|
+
::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze)
|
74
82
|
end
|
75
83
|
|
76
|
-
def gzip_encoding_accepted?(
|
77
|
-
|
84
|
+
def gzip_encoding_accepted?(request)
|
85
|
+
request.accept_encoding =~ /\bgzip\b/i
|
78
86
|
end
|
79
87
|
|
80
88
|
def gzip_file_path(path)
|
81
89
|
can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/
|
82
90
|
gzip_path = "#{path}.gz"
|
83
|
-
if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.
|
91
|
+
if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape_path(gzip_path)))
|
84
92
|
gzip_path
|
85
93
|
else
|
86
94
|
false
|
87
95
|
end
|
88
96
|
end
|
89
|
-
|
90
|
-
def valid_path?(path)
|
91
|
-
path.valid_encoding? && !path.include?("\0")
|
92
|
-
end
|
93
97
|
end
|
94
98
|
|
95
99
|
# This middleware will attempt to return the contents of a file's body from
|
96
|
-
# disk in the response.
|
100
|
+
# disk in the response. If a file is not found on disk, the request will be
|
97
101
|
# delegated to the application stack. This middleware is commonly initialized
|
98
102
|
# to serve assets from a server's `public/` directory.
|
99
103
|
#
|
@@ -102,22 +106,30 @@ module ActionDispatch
|
|
102
106
|
# produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
|
103
107
|
# requests will result in a file being returned.
|
104
108
|
class Static
|
105
|
-
def initialize(app, path,
|
109
|
+
def initialize(app, path, deprecated_cache_control = :not_set, index: 'index', headers: {})
|
110
|
+
if deprecated_cache_control != :not_set
|
111
|
+
ActiveSupport::Deprecation.warn("The `cache_control` argument is deprecated," \
|
112
|
+
"replaced by `headers: { 'Cache-Control' => #{deprecated_cache_control} }`, " \
|
113
|
+
" and will be removed in Rails 5.1.")
|
114
|
+
headers['Cache-Control'.freeze] = deprecated_cache_control
|
115
|
+
end
|
116
|
+
|
106
117
|
@app = app
|
107
|
-
@file_handler = FileHandler.new(path,
|
118
|
+
@file_handler = FileHandler.new(path, index: index, headers: headers)
|
108
119
|
end
|
109
120
|
|
110
121
|
def call(env)
|
111
|
-
|
112
|
-
|
113
|
-
|
122
|
+
req = ActionDispatch::Request.new env
|
123
|
+
|
124
|
+
if req.get? || req.head?
|
125
|
+
path = req.path_info.chomp('/'.freeze)
|
114
126
|
if match = @file_handler.match?(path)
|
115
|
-
|
116
|
-
return @file_handler.
|
127
|
+
req.path_info = match
|
128
|
+
return @file_handler.serve(req)
|
117
129
|
end
|
118
130
|
end
|
119
131
|
|
120
|
-
@app.call(env)
|
132
|
+
@app.call(req.env)
|
121
133
|
end
|
122
134
|
end
|
123
135
|
end
|