actionpack 4.2.11.3 → 5.0.7.2

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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +890 -384
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/base.rb +28 -38
  6. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
  7. data/lib/abstract_controller/caching.rb +62 -0
  8. data/lib/abstract_controller/callbacks.rb +54 -19
  9. data/lib/abstract_controller/collector.rb +4 -9
  10. data/lib/abstract_controller/error.rb +4 -0
  11. data/lib/abstract_controller/helpers.rb +4 -3
  12. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  13. data/lib/abstract_controller/rendering.rb +28 -18
  14. data/lib/abstract_controller/translation.rb +8 -7
  15. data/lib/abstract_controller.rb +6 -2
  16. data/lib/action_controller/api/api_rendering.rb +14 -0
  17. data/lib/action_controller/api.rb +147 -0
  18. data/lib/action_controller/base.rb +14 -11
  19. data/lib/action_controller/caching.rb +13 -58
  20. data/lib/action_controller/form_builder.rb +48 -0
  21. data/lib/action_controller/log_subscriber.rb +3 -10
  22. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  23. data/lib/action_controller/metal/conditional_get.rb +106 -34
  24. data/lib/action_controller/metal/cookies.rb +1 -3
  25. data/lib/action_controller/metal/data_streaming.rb +14 -34
  26. data/lib/action_controller/metal/etag_with_template_digest.rb +8 -2
  27. data/lib/action_controller/metal/exceptions.rb +11 -6
  28. data/lib/action_controller/metal/force_ssl.rb +11 -11
  29. data/lib/action_controller/metal/head.rb +14 -8
  30. data/lib/action_controller/metal/helpers.rb +15 -6
  31. data/lib/action_controller/metal/http_authentication.rb +44 -35
  32. data/lib/action_controller/metal/implicit_render.rb +61 -6
  33. data/lib/action_controller/metal/instrumentation.rb +5 -5
  34. data/lib/action_controller/metal/live.rb +71 -88
  35. data/lib/action_controller/metal/mime_responds.rb +27 -42
  36. data/lib/action_controller/metal/params_wrapper.rb +9 -9
  37. data/lib/action_controller/metal/redirecting.rb +32 -9
  38. data/lib/action_controller/metal/renderers.rb +83 -40
  39. data/lib/action_controller/metal/rendering.rb +38 -6
  40. data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
  41. data/lib/action_controller/metal/rescue.rb +3 -12
  42. data/lib/action_controller/metal/streaming.rb +4 -4
  43. data/lib/action_controller/metal/strong_parameters.rb +527 -134
  44. data/lib/action_controller/metal/testing.rb +1 -12
  45. data/lib/action_controller/metal/url_for.rb +12 -5
  46. data/lib/action_controller/metal.rb +88 -63
  47. data/lib/action_controller/railtie.rb +11 -7
  48. data/lib/action_controller/renderer.rb +113 -0
  49. data/lib/action_controller/template_assertions.rb +9 -0
  50. data/lib/action_controller/test_case.rb +311 -374
  51. data/lib/action_controller.rb +12 -9
  52. data/lib/action_dispatch/http/cache.rb +73 -34
  53. data/lib/action_dispatch/http/filter_parameters.rb +16 -12
  54. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  55. data/lib/action_dispatch/http/headers.rb +45 -14
  56. data/lib/action_dispatch/http/mime_negotiation.rb +42 -23
  57. data/lib/action_dispatch/http/mime_type.rb +126 -90
  58. data/lib/action_dispatch/http/mime_types.rb +3 -4
  59. data/lib/action_dispatch/http/parameter_filter.rb +19 -9
  60. data/lib/action_dispatch/http/parameters.rb +70 -40
  61. data/lib/action_dispatch/http/request.rb +144 -89
  62. data/lib/action_dispatch/http/response.rb +215 -102
  63. data/lib/action_dispatch/http/upload.rb +6 -2
  64. data/lib/action_dispatch/http/url.rb +117 -8
  65. data/lib/action_dispatch/journey/formatter.rb +47 -30
  66. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  67. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  68. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  69. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  70. data/lib/action_dispatch/journey/parser.rb +2 -0
  71. data/lib/action_dispatch/journey/parser_extras.rb +8 -2
  72. data/lib/action_dispatch/journey/path/pattern.rb +38 -42
  73. data/lib/action_dispatch/journey/route.rb +88 -26
  74. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  75. data/lib/action_dispatch/journey/router.rb +8 -10
  76. data/lib/action_dispatch/journey/routes.rb +14 -15
  77. data/lib/action_dispatch/journey/visitors.rb +89 -44
  78. data/lib/action_dispatch/middleware/callbacks.rb +10 -1
  79. data/lib/action_dispatch/middleware/cookies.rb +188 -134
  80. data/lib/action_dispatch/middleware/debug_exceptions.rb +128 -49
  81. data/lib/action_dispatch/middleware/debug_locks.rb +122 -0
  82. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
  83. data/lib/action_dispatch/middleware/executor.rb +19 -0
  84. data/lib/action_dispatch/middleware/flash.rb +66 -45
  85. data/lib/action_dispatch/middleware/params_parser.rb +32 -46
  86. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  87. data/lib/action_dispatch/middleware/reloader.rb +14 -58
  88. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  89. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  90. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  91. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  92. data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
  93. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  94. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  95. data/lib/action_dispatch/middleware/ssl.rb +124 -36
  96. data/lib/action_dispatch/middleware/stack.rb +44 -40
  97. data/lib/action_dispatch/middleware/static.rb +51 -35
  98. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  99. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  101. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  103. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  104. data/lib/action_dispatch/railtie.rb +2 -2
  105. data/lib/action_dispatch/request/session.rb +69 -33
  106. data/lib/action_dispatch/request/utils.rb +51 -19
  107. data/lib/action_dispatch/routing/inspector.rb +32 -43
  108. data/lib/action_dispatch/routing/mapper.rb +515 -348
  109. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  110. data/lib/action_dispatch/routing/redirection.rb +5 -4
  111. data/lib/action_dispatch/routing/route_set.rb +148 -240
  112. data/lib/action_dispatch/routing/url_for.rb +27 -10
  113. data/lib/action_dispatch/routing.rb +17 -13
  114. data/lib/action_dispatch/testing/assertion_response.rb +45 -0
  115. data/lib/action_dispatch/testing/assertions/response.rb +38 -20
  116. data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
  117. data/lib/action_dispatch/testing/assertions.rb +1 -1
  118. data/lib/action_dispatch/testing/integration.rb +377 -149
  119. data/lib/action_dispatch/testing/request_encoder.rb +53 -0
  120. data/lib/action_dispatch/testing/test_process.rb +24 -20
  121. data/lib/action_dispatch/testing/test_request.rb +22 -31
  122. data/lib/action_dispatch/testing/test_response.rb +12 -4
  123. data/lib/action_dispatch.rb +4 -1
  124. data/lib/action_pack/gem_version.rb +4 -4
  125. data/lib/action_pack.rb +1 -1
  126. metadata +32 -34
  127. data/lib/action_controller/metal/hide_actions.rb +0 -40
  128. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  129. data/lib/action_controller/middleware.rb +0 -39
  130. data/lib/action_controller/model_naming.rb +0 -12
  131. data/lib/action_dispatch/journey/backwards.rb +0 -5
  132. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  133. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  134. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  135. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
  136. /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -1,72 +1,160 @@
1
1
  module ActionDispatch
2
+ # This middleware is added to the stack when `config.force_ssl = true`, and is passed
3
+ # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP
4
+ # requests:
5
+ #
6
+ # 1. TLS redirect: Permanently redirects http:// requests to https://
7
+ # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
8
+ # to modify the destination URL
9
+ # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
10
+ # `redirect: false` to disable this feature.
11
+ #
12
+ # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
13
+ # mustn't be sent along with http:// requests. Enabled by default. Set
14
+ # `config.ssl_options` with `secure_cookies: false` to disable this feature.
15
+ #
16
+ # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
17
+ # this site as TLS-only and automatically redirect non-TLS requests.
18
+ # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
19
+ #
20
+ # Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
21
+ # * `expires`: How long, in seconds, these settings will stick. The minimum
22
+ # required to qualify for browser preload lists is `18.weeks`. Defaults to
23
+ # `180.days` (recommended).
24
+ # * `subdomains`: Set to `true` to tell the browser to apply these settings
25
+ # to all subdomains. This protects your cookies from interception by a
26
+ # vulnerable site on a subdomain. Defaults to `false`.
27
+ # * `preload`: Advertise that this site may be included in browsers'
28
+ # preloaded HSTS lists. HSTS protects your site on every visit *except the
29
+ # first visit* since it hasn't seen your HSTS header yet. To close this
30
+ # gap, browser vendors include a baked-in list of HSTS-enabled sites.
31
+ # Go to https://hstspreload.appspot.com to submit your site for inclusion.
32
+ # Defaults to `false`.
33
+ #
34
+ # To turn off HSTS, omitting the header is not enough. Browsers will remember the
35
+ # original HSTS directive until it expires. Instead, use the header to tell browsers to
36
+ # expire HSTS immediately. Setting `hsts: false` is a shortcut for
37
+ # `hsts: { expires: 0 }`.
38
+ #
39
+ # Requests can opt-out of redirection with `exclude`:
40
+ #
41
+ # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
2
42
  class SSL
3
- YEAR = 31536000
43
+ # Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
44
+ # and greater than the 18-week requirement for browser preload lists.
45
+ HSTS_EXPIRES_IN = 15552000
4
46
 
5
47
  def self.default_hsts_options
6
- { :expires => YEAR, :subdomains => false }
48
+ { expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
7
49
  end
8
50
 
9
- def initialize(app, options = {})
51
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options)
10
52
  @app = app
11
53
 
12
- @hsts = options.fetch(:hsts, {})
13
- @hsts = {} if @hsts == true
14
- @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
54
+ if options[:host] || options[:port]
55
+ ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
56
+ The `:host` and `:port` options are moving within `:redirect`:
57
+ `config.ssl_options = { redirect: { host: …, port: … } }`.
58
+ end_warning
59
+ @redirect = options.slice(:host, :port)
60
+ else
61
+ @redirect = redirect
62
+ end
63
+
64
+ @exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
65
+ @secure_cookies = secure_cookies
66
+
67
+ if hsts != true && hsts != false && hsts[:subdomains].nil?
68
+ hsts[:subdomains] = false
15
69
 
16
- @host = options[:host]
17
- @port = options[:port]
70
+ ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
71
+ In Rails 5.1, The `:subdomains` option of HSTS config will be treated as true if
72
+ unspecified. Set `config.ssl_options = { hsts: { subdomains: false } }` to opt out
73
+ of this behavior.
74
+ end_warning
75
+ end
76
+
77
+ @hsts_header = build_hsts_header(normalize_hsts_options(hsts))
18
78
  end
19
79
 
20
80
  def call(env)
21
- request = Request.new(env)
81
+ request = Request.new env
22
82
 
23
83
  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]
84
+ @app.call(env).tap do |status, headers, body|
85
+ set_hsts_header! headers
86
+ flag_cookies_as_secure! headers if @secure_cookies
87
+ end
28
88
  else
29
- redirect_to_https(request)
89
+ return redirect_to_https request unless @exclude.call(request)
90
+ @app.call(env)
30
91
  end
31
92
  end
32
93
 
33
94
  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, []]
95
+ def set_hsts_header!(headers)
96
+ headers['Strict-Transport-Security'.freeze] ||= @hsts_header
45
97
  end
46
98
 
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 }
99
+ def normalize_hsts_options(options)
100
+ case options
101
+ # Explicitly disabling HSTS clears the existing setting from browsers
102
+ # by setting expiry to 0.
103
+ when false
104
+ self.class.default_hsts_options.merge(expires: 0)
105
+ # Default to enabled, with default options.
106
+ when nil, true
107
+ self.class.default_hsts_options
53
108
  else
54
- {}
109
+ self.class.default_hsts_options.merge(options)
55
110
  end
56
111
  end
57
112
 
113
+ # http://tools.ietf.org/html/rfc6797#section-6.1
114
+ def build_hsts_header(hsts)
115
+ value = "max-age=#{hsts[:expires].to_i}"
116
+ value << "; includeSubDomains" if hsts[:subdomains]
117
+ value << "; preload" if hsts[:preload]
118
+ value
119
+ end
120
+
58
121
  def flag_cookies_as_secure!(headers)
59
- if cookies = headers['Set-Cookie']
60
- cookies = cookies.split("\n")
122
+ if cookies = headers['Set-Cookie'.freeze]
123
+ cookies = cookies.split("\n".freeze)
61
124
 
62
- headers['Set-Cookie'] = cookies.map { |cookie|
125
+ headers['Set-Cookie'.freeze] = cookies.map { |cookie|
63
126
  if cookie !~ /;\s*secure\s*(;|$)/i
64
127
  "#{cookie}; secure"
65
128
  else
66
129
  cookie
67
130
  end
68
- }.join("\n")
131
+ }.join("\n".freeze)
69
132
  end
70
133
  end
134
+
135
+ def redirect_to_https(request)
136
+ [ @redirect.fetch(:status, redirection_status(request)),
137
+ { 'Content-Type' => 'text/html',
138
+ 'Location' => https_location_for(request) },
139
+ @redirect.fetch(:body, []) ]
140
+ end
141
+
142
+ def redirection_status(request)
143
+ if request.get? || request.head?
144
+ 301 # Issue a permanent redirect via a GET request.
145
+ else
146
+ 307 # Issue a fresh request redirect to preserve the HTTP method.
147
+ end
148
+ end
149
+
150
+ def https_location_for(request)
151
+ host = @redirect[:host] || request.host
152
+ port = @redirect[:port] || request.port
153
+
154
+ location = "https://#{host}"
155
+ location << ":#{port}" if port != 80 && port != 443
156
+ location << request.fullpath
157
+ location
158
+ end
71
159
  end
72
160
  end
@@ -4,25 +4,15 @@ 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
17
  def ==(middleware)
28
18
  case middleware
@@ -30,24 +20,20 @@ module ActionDispatch
30
20
  klass == middleware.klass
31
21
  when Class
32
22
  klass == middleware
33
- else
34
- normalize(@name) == normalize(middleware)
35
23
  end
36
24
  end
37
25
 
38
26
  def inspect
39
- klass.to_s
27
+ if klass.is_a?(Class)
28
+ klass.to_s
29
+ else
30
+ klass.class.to_s
31
+ end
40
32
  end
41
33
 
42
34
  def build(app)
43
35
  klass.new(app, *args, &block)
44
36
  end
45
-
46
- private
47
-
48
- def normalize(object)
49
- object.to_s.strip.sub(/^::/, '')
50
- end
51
37
  end
52
38
 
53
39
  include Enumerable
@@ -75,19 +61,17 @@ module ActionDispatch
75
61
  middlewares[i]
76
62
  end
77
63
 
78
- def unshift(*args, &block)
79
- middleware = self.class::Middleware.new(*args, &block)
80
- middlewares.unshift(middleware)
64
+ def unshift(klass, *args, &block)
65
+ middlewares.unshift(build_middleware(klass, args, block))
81
66
  end
82
67
 
83
68
  def initialize_copy(other)
84
69
  self.middlewares = other.middlewares.dup
85
70
  end
86
71
 
87
- def insert(index, *args, &block)
72
+ def insert(index, klass, *args, &block)
88
73
  index = assert_index(index, :before)
89
- middleware = self.class::Middleware.new(*args, &block)
90
- middlewares.insert(index, middleware)
74
+ middlewares.insert(index, build_middleware(klass, args, block))
91
75
  end
92
76
 
93
77
  alias_method :insert_before, :insert
@@ -104,26 +88,46 @@ module ActionDispatch
104
88
  end
105
89
 
106
90
  def delete(target)
107
- middlewares.delete target
91
+ target = get_class target
92
+ middlewares.delete_if { |m| m.klass == target }
108
93
  end
109
94
 
110
- def use(*args, &block)
111
- middleware = self.class::Middleware.new(*args, &block)
112
- middlewares.push(middleware)
95
+ def use(klass, *args, &block)
96
+ middlewares.push(build_middleware(klass, args, block))
113
97
  end
114
98
 
115
- def build(app = nil, &block)
116
- app ||= block
117
- raise "MiddlewareStack#build requires an app" unless app
99
+ def build(app = Proc.new)
118
100
  middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
119
101
  end
120
102
 
121
- protected
103
+ private
122
104
 
123
105
  def assert_index(index, where)
124
- i = index.is_a?(Integer) ? index : middlewares.index(index)
106
+ index = get_class index
107
+ i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index }
125
108
  raise "No such middleware to insert #{where}: #{index.inspect}" unless i
126
109
  i
127
110
  end
111
+
112
+ def get_class(klass)
113
+ if klass.is_a?(String) || klass.is_a?(Symbol)
114
+ classcache = ActiveSupport::Dependencies::Reference
115
+ converted_klass = classcache[klass.to_s]
116
+ ActiveSupport::Deprecation.warn <<-eowarn
117
+ Passing strings or symbols to the middleware builder is deprecated, please change
118
+ them to actual class references. For example:
119
+
120
+ "#{klass}" => #{converted_klass}
121
+
122
+ eowarn
123
+ converted_klass
124
+ else
125
+ klass
126
+ end
127
+ end
128
+
129
+ def build_middleware(klass, args, block)
130
+ Middleware.new(get_class(klass), args, block)
131
+ end
128
132
  end
129
133
  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)
29
+ path = ::Rack::Utils.unescape_path path
25
30
  return false unless valid_path?(path)
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(Rack::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,17 +78,17 @@ 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.any? { |enc, quality| enc =~ /\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
@@ -93,7 +101,7 @@ module ActionDispatch
93
101
  end
94
102
 
95
103
  # 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
104
+ # disk in the response. If a file is not found on disk, the request will be
97
105
  # delegated to the application stack. This middleware is commonly initialized
98
106
  # to serve assets from a server's `public/` directory.
99
107
  #
@@ -102,22 +110,30 @@ module ActionDispatch
102
110
  # produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
103
111
  # requests will result in a file being returned.
104
112
  class Static
105
- def initialize(app, path, cache_control=nil)
113
+ def initialize(app, path, deprecated_cache_control = :not_set, index: 'index', headers: {})
114
+ if deprecated_cache_control != :not_set
115
+ ActiveSupport::Deprecation.warn("The `cache_control` argument is deprecated," \
116
+ "replaced by `headers: { 'Cache-Control' => #{deprecated_cache_control} }`, " \
117
+ " and will be removed in Rails 5.1.")
118
+ headers['Cache-Control'.freeze] = deprecated_cache_control
119
+ end
120
+
106
121
  @app = app
107
- @file_handler = FileHandler.new(path, cache_control)
122
+ @file_handler = FileHandler.new(path, index: index, headers: headers)
108
123
  end
109
124
 
110
125
  def call(env)
111
- case env['REQUEST_METHOD']
112
- when 'GET', 'HEAD'
113
- path = env['PATH_INFO'].chomp('/')
126
+ req = Rack::Request.new env
127
+
128
+ if req.get? || req.head?
129
+ path = req.path_info.chomp('/'.freeze)
114
130
  if match = @file_handler.match?(path)
115
- env["PATH_INFO"] = match
116
- return @file_handler.call(env)
131
+ req.path_info = match
132
+ return @file_handler.serve(req)
117
133
  end
118
134
  end
119
135
 
120
- @app.call(env)
136
+ @app.call(req.env)
121
137
  end
122
138
  end
123
139
  end
@@ -5,20 +5,8 @@
5
5
  <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre>
6
6
  <% end %>
7
7
 
8
- <%
9
- clean_params = @request.filtered_parameters.clone
10
- clean_params.delete("action")
11
- clean_params.delete("controller")
12
-
13
- request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
14
-
15
- def debug_hash(object)
16
- object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
17
- end unless self.class.method_defined?(:debug_hash)
18
- %>
19
-
20
8
  <h2 style="margin-top: 30px">Request</h2>
21
- <p><b>Parameters</b>:</p> <pre><%= request_dump %></pre>
9
+ <p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre>
22
10
 
23
11
  <div class="details">
24
12
  <div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
@@ -31,4 +19,4 @@
31
19
  </div>
32
20
 
33
21
  <h2 style="margin-top: 30px">Response</h2>
34
- <p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre>
22
+ <p><b>Headers</b>:</p> <pre><%= debug_headers(defined?(@response) ? @response.headers : {}) %></pre>
@@ -0,0 +1,8 @@
1
+ <% @source_extracts.first(3).each do |source_extract| %>
2
+ <% if source_extract[:code] %>
3
+ Extracted source (around line #<%= source_extract[:line_number] %>):
4
+
5
+ <% source_extract[:code].each do |line, source| -%>
6
+ <%= line == source_extract[:line_number] ? "*#{line}" : "##{line}" -%> <%= source -%><% end -%>
7
+ <% end %>
8
+ <% end %>
@@ -1,6 +1,6 @@
1
1
  <header>
2
2
  <h1>
3
- <%= @exception.original_exception.class.to_s %> in
3
+ <%= @exception.cause.class.to_s %> in
4
4
  <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
5
5
  </h1>
6
6
  </header>
@@ -1,4 +1,4 @@
1
- <%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
1
+ <%= @exception.cause.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
2
2
 
3
3
  Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised:
4
4
  <%= @exception.message %>
@@ -4,13 +4,13 @@
4
4
  <%= route[:name] %><span class='helper'>_path</span>
5
5
  <% end %>
6
6
  </td>
7
- <td data-route-verb='<%= route[:verb] %>'>
7
+ <td>
8
8
  <%= route[:verb] %>
9
9
  </td>
10
- <td data-route-path='<%= route[:path] %>' data-regexp='<%= route[:regexp] %>'>
10
+ <td data-route-path='<%= route[:path] %>'>
11
11
  <%= route[:path] %>
12
12
  </td>
13
- <td data-route-reqs='<%= route[:reqs] %>'>
14
- <%= route[:reqs] %>
13
+ <td>
14
+ <%=simple_format route[:reqs] %>
15
15
  </td>
16
16
  </tr>