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.

Files changed (125) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +379 -462
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller.rb +0 -2
  6. data/lib/abstract_controller/base.rb +17 -32
  7. data/lib/abstract_controller/callbacks.rb +52 -19
  8. data/lib/abstract_controller/collector.rb +4 -9
  9. data/lib/abstract_controller/helpers.rb +2 -2
  10. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  11. data/lib/abstract_controller/rendering.rb +27 -22
  12. data/lib/abstract_controller/translation.rb +8 -7
  13. data/lib/action_controller.rb +4 -3
  14. data/lib/action_controller/api.rb +146 -0
  15. data/lib/action_controller/base.rb +6 -10
  16. data/lib/action_controller/caching.rb +1 -3
  17. data/lib/action_controller/caching/fragments.rb +48 -3
  18. data/lib/action_controller/form_builder.rb +48 -0
  19. data/lib/action_controller/log_subscriber.rb +1 -10
  20. data/lib/action_controller/metal.rb +89 -62
  21. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  22. data/lib/action_controller/metal/conditional_get.rb +65 -24
  23. data/lib/action_controller/metal/cookies.rb +0 -2
  24. data/lib/action_controller/metal/data_streaming.rb +2 -22
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  26. data/lib/action_controller/metal/exceptions.rb +11 -6
  27. data/lib/action_controller/metal/force_ssl.rb +6 -6
  28. data/lib/action_controller/metal/head.rb +14 -7
  29. data/lib/action_controller/metal/helpers.rb +9 -5
  30. data/lib/action_controller/metal/http_authentication.rb +37 -38
  31. data/lib/action_controller/metal/implicit_render.rb +23 -6
  32. data/lib/action_controller/metal/instrumentation.rb +0 -1
  33. data/lib/action_controller/metal/live.rb +17 -55
  34. data/lib/action_controller/metal/mime_responds.rb +17 -37
  35. data/lib/action_controller/metal/params_wrapper.rb +8 -8
  36. data/lib/action_controller/metal/redirecting.rb +32 -9
  37. data/lib/action_controller/metal/renderers.rb +10 -8
  38. data/lib/action_controller/metal/rendering.rb +38 -6
  39. data/lib/action_controller/metal/request_forgery_protection.rb +67 -35
  40. data/lib/action_controller/metal/rescue.rb +2 -4
  41. data/lib/action_controller/metal/streaming.rb +4 -4
  42. data/lib/action_controller/metal/strong_parameters.rb +231 -78
  43. data/lib/action_controller/metal/testing.rb +1 -12
  44. data/lib/action_controller/metal/url_for.rb +12 -5
  45. data/lib/action_controller/renderer.rb +111 -0
  46. data/lib/action_controller/template_assertions.rb +9 -0
  47. data/lib/action_controller/test_case.rb +267 -363
  48. data/lib/action_dispatch.rb +2 -1
  49. data/lib/action_dispatch/http/cache.rb +23 -26
  50. data/lib/action_dispatch/http/filter_parameters.rb +6 -8
  51. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  52. data/lib/action_dispatch/http/headers.rb +28 -11
  53. data/lib/action_dispatch/http/mime_negotiation.rb +40 -26
  54. data/lib/action_dispatch/http/mime_type.rb +92 -61
  55. data/lib/action_dispatch/http/mime_types.rb +1 -4
  56. data/lib/action_dispatch/http/parameter_filter.rb +18 -8
  57. data/lib/action_dispatch/http/parameters.rb +45 -41
  58. data/lib/action_dispatch/http/request.rb +146 -82
  59. data/lib/action_dispatch/http/response.rb +180 -99
  60. data/lib/action_dispatch/http/url.rb +117 -8
  61. data/lib/action_dispatch/journey/formatter.rb +34 -28
  62. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  63. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  64. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  65. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  66. data/lib/action_dispatch/journey/parser_extras.rb +4 -0
  67. data/lib/action_dispatch/journey/path/pattern.rb +37 -41
  68. data/lib/action_dispatch/journey/route.rb +71 -17
  69. data/lib/action_dispatch/journey/router.rb +5 -6
  70. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  71. data/lib/action_dispatch/journey/routes.rb +14 -15
  72. data/lib/action_dispatch/journey/visitors.rb +86 -43
  73. data/lib/action_dispatch/middleware/cookies.rb +184 -135
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +115 -45
  75. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -20
  76. data/lib/action_dispatch/middleware/flash.rb +61 -45
  77. data/lib/action_dispatch/middleware/load_interlock.rb +21 -0
  78. data/lib/action_dispatch/middleware/params_parser.rb +30 -46
  79. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  80. data/lib/action_dispatch/middleware/reloader.rb +2 -4
  81. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  82. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  83. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  84. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  85. data/lib/action_dispatch/middleware/session/cookie_store.rb +29 -23
  86. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  87. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  88. data/lib/action_dispatch/middleware/ssl.rb +93 -36
  89. data/lib/action_dispatch/middleware/stack.rb +43 -48
  90. data/lib/action_dispatch/middleware/static.rb +52 -40
  91. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  92. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  93. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  94. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  95. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  96. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  97. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  98. data/lib/action_dispatch/railtie.rb +0 -2
  99. data/lib/action_dispatch/request/session.rb +66 -34
  100. data/lib/action_dispatch/request/utils.rb +51 -19
  101. data/lib/action_dispatch/routing.rb +3 -8
  102. data/lib/action_dispatch/routing/inspector.rb +6 -30
  103. data/lib/action_dispatch/routing/mapper.rb +447 -322
  104. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  105. data/lib/action_dispatch/routing/redirection.rb +3 -3
  106. data/lib/action_dispatch/routing/route_set.rb +124 -227
  107. data/lib/action_dispatch/routing/url_for.rb +27 -10
  108. data/lib/action_dispatch/testing/assertions.rb +1 -1
  109. data/lib/action_dispatch/testing/assertions/response.rb +27 -9
  110. data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
  111. data/lib/action_dispatch/testing/integration.rb +237 -76
  112. data/lib/action_dispatch/testing/test_process.rb +5 -5
  113. data/lib/action_dispatch/testing/test_request.rb +12 -21
  114. data/lib/action_dispatch/testing/test_response.rb +1 -4
  115. data/lib/action_pack.rb +1 -1
  116. data/lib/action_pack/gem_version.rb +4 -4
  117. metadata +26 -25
  118. data/lib/action_controller/metal/hide_actions.rb +0 -40
  119. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  120. data/lib/action_controller/middleware.rb +0 -39
  121. data/lib/action_controller/model_naming.rb +0 -12
  122. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  123. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  124. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  125. 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 env['action_dispatch.show_exceptions'] == false
33
- raise exception
33
+ if request.show_exceptions?
34
+ render_exception(request, exception)
34
35
  else
35
- render_exception(env, exception)
36
+ raise exception
36
37
  end
37
38
  end
38
39
 
39
40
  private
40
41
 
41
- def render_exception(env, exception)
42
- wrapper = ExceptionWrapper.new(env, exception)
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
- env["action_dispatch.exception"] = wrapper.exception
45
- env["action_dispatch.original_path"] = env["PATH_INFO"]
46
- env["PATH_INFO"] = "/#{status}"
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
- YEAR = 31536000
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
- { :expires => YEAR, :subdomains => false }
39
+ { expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
7
40
  end
8
41
 
9
- def initialize(app, options = {})
42
+ def initialize(app, redirect: {}, hsts: {}, **options)
10
43
  @app = app
11
44
 
12
- @hsts = options.fetch(:hsts, {})
13
- @hsts = {} if @hsts == true
14
- @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
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
- @host = options[:host]
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(env)
59
+ request = Request.new env
22
60
 
23
61
  if request.ssl?
24
- status, headers, body = @app.call(env)
25
- headers.reverse_merge!(hsts_headers)
26
- flag_cookies_as_secure!(headers)
27
- [status, headers, body]
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(request)
67
+ redirect_to_https request
30
68
  end
31
69
  end
32
70
 
33
71
  private
34
- def redirect_to_https(request)
35
- host = @host || request.host
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
- # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
48
- def hsts_headers
49
- if @hsts
50
- value = "max-age=#{@hsts[:expires].to_i}"
51
- value += "; includeSubDomains" if @hsts[:subdomains]
52
- { 'Strict-Transport-Security' => value }
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, :name, :classcache
7
+ attr_reader :args, :block, :klass
8
8
 
9
- def initialize(klass_or_name, *args, &block)
10
- @klass = nil
11
-
12
- if klass_or_name.respond_to?(:name)
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 ==(middleware)
28
- case middleware
29
- when Middleware
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
- normalize(@name) == normalize(middleware)
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
- middleware = self.class::Middleware.new(*args, &block)
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
- middleware = self.class::Middleware.new(*args, &block)
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
- middlewares.delete target
82
+ target = get_class target
83
+ middlewares.delete_if { |m| m.klass == target }
108
84
  end
109
85
 
110
- def use(*args, &block)
111
- middleware = self.class::Middleware.new(*args, &block)
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 = nil, &block)
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
- protected
94
+ private
122
95
 
123
96
  def assert_index(index, where)
124
- i = index.is_a?(Integer) ? index : middlewares.index(index)
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 an optional 'Cache-Control' header which
7
- # will be set when a response containing a file's contents is delivered.
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/` then a request with `env["PATH_INFO"]` of
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, cache_control)
16
+ def initialize(root, index: 'index', headers: {})
17
17
  @root = root.chomp('/')
18
- @compiled_root = /^#{Regexp.escape(root)}/
19
- headers = cache_control && { 'Cache-Control' => cache_control }
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 = URI.parser.unescape(path)
25
- return false unless valid_path?(path)
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}/index#{ext}"].map { |v|
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.escape(match)
44
+ return ::Rack::Utils.escape_path(match)
41
45
  end
42
46
  end
43
47
 
44
48
  def call(env)
45
- path = env['PATH_INFO']
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?(env)
49
- env['PATH_INFO'] = gzip_path
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
- env['PATH_INFO'] = path
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?(env)
77
- env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/i
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.unescape(gzip_path)))
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. If a file is not found on disk, the request will be
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, cache_control=nil)
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, cache_control)
118
+ @file_handler = FileHandler.new(path, index: index, headers: headers)
108
119
  end
109
120
 
110
121
  def call(env)
111
- case env['REQUEST_METHOD']
112
- when 'GET', 'HEAD'
113
- path = env['PATH_INFO'].chomp('/')
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
- env["PATH_INFO"] = match
116
- return @file_handler.call(env)
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