actionpack 3.1.12 → 3.2.0.rc1

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 (128) hide show
  1. data/CHANGELOG.md +5503 -108
  2. data/README.rdoc +3 -3
  3. data/lib/abstract_controller/asset_paths.rb +1 -1
  4. data/lib/abstract_controller/base.rb +1 -1
  5. data/lib/abstract_controller/callbacks.rb +102 -18
  6. data/lib/abstract_controller/helpers.rb +1 -1
  7. data/lib/abstract_controller/layouts.rb +116 -50
  8. data/lib/abstract_controller/logger.rb +1 -1
  9. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  10. data/lib/abstract_controller/rendering.rb +1 -6
  11. data/lib/abstract_controller/view_paths.rb +6 -5
  12. data/lib/action_controller.rb +0 -15
  13. data/lib/action_controller/caching.rb +0 -1
  14. data/lib/action_controller/caching/actions.rb +5 -6
  15. data/lib/action_controller/caching/fragments.rb +18 -18
  16. data/lib/action_controller/caching/pages.rb +7 -6
  17. data/lib/action_controller/caching/sweeping.rb +1 -1
  18. data/lib/action_controller/log_subscriber.rb +8 -4
  19. data/lib/action_controller/metal.rb +7 -1
  20. data/lib/action_controller/metal/conditional_get.rb +49 -4
  21. data/lib/action_controller/metal/data_streaming.rb +17 -5
  22. data/lib/action_controller/metal/force_ssl.rb +8 -5
  23. data/lib/action_controller/metal/helpers.rb +7 -4
  24. data/lib/action_controller/metal/http_authentication.rb +9 -12
  25. data/lib/action_controller/metal/instrumentation.rb +9 -4
  26. data/lib/action_controller/metal/mime_responds.rb +4 -4
  27. data/lib/action_controller/metal/params_wrapper.rb +12 -8
  28. data/lib/action_controller/metal/redirecting.rb +7 -6
  29. data/lib/action_controller/metal/renderers.rb +9 -11
  30. data/lib/action_controller/metal/request_forgery_protection.rb +2 -1
  31. data/lib/action_controller/metal/rescue.rb +13 -0
  32. data/lib/action_controller/metal/responder.rb +11 -23
  33. data/lib/action_controller/metal/streaming.rb +0 -25
  34. data/lib/action_controller/railtie.rb +1 -0
  35. data/lib/action_controller/railties/paths.rb +4 -3
  36. data/lib/action_controller/record_identifier.rb +4 -4
  37. data/lib/action_controller/test_case.rb +60 -56
  38. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +6 -6
  39. data/lib/action_dispatch.rb +5 -1
  40. data/lib/action_dispatch/http/cache.rb +27 -15
  41. data/lib/action_dispatch/http/filter_parameters.rb +3 -1
  42. data/lib/action_dispatch/http/headers.rb +3 -5
  43. data/lib/action_dispatch/http/mime_negotiation.rb +2 -1
  44. data/lib/action_dispatch/http/mime_type.rb +7 -3
  45. data/lib/action_dispatch/http/mime_types.rb +12 -0
  46. data/lib/action_dispatch/http/parameter_filter.rb +3 -1
  47. data/lib/action_dispatch/http/parameters.rb +0 -4
  48. data/lib/action_dispatch/http/request.rb +18 -68
  49. data/lib/action_dispatch/http/response.rb +11 -32
  50. data/lib/action_dispatch/http/upload.rb +3 -14
  51. data/lib/action_dispatch/http/url.rb +1 -1
  52. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  53. data/lib/action_dispatch/middleware/cookies.rb +20 -16
  54. data/lib/action_dispatch/middleware/debug_exceptions.rb +82 -0
  55. data/lib/action_dispatch/middleware/exception_wrapper.rb +78 -0
  56. data/lib/action_dispatch/middleware/flash.rb +6 -9
  57. data/lib/action_dispatch/middleware/params_parser.rb +6 -11
  58. data/lib/action_dispatch/middleware/public_exceptions.rb +30 -0
  59. data/lib/action_dispatch/middleware/reloader.rb +38 -14
  60. data/lib/action_dispatch/middleware/remote_ip.rb +66 -36
  61. data/lib/action_dispatch/middleware/request_id.rb +39 -0
  62. data/lib/action_dispatch/middleware/session/abstract_store.rb +4 -16
  63. data/lib/action_dispatch/middleware/session/cache_store.rb +50 -0
  64. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  65. data/lib/action_dispatch/middleware/show_exceptions.rb +58 -142
  66. data/lib/action_dispatch/middleware/static.rb +2 -10
  67. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  68. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +13 -8
  69. data/lib/action_dispatch/railtie.rb +15 -1
  70. data/lib/action_dispatch/routing.rb +1 -2
  71. data/lib/action_dispatch/routing/mapper.rb +108 -107
  72. data/lib/action_dispatch/routing/redirection.rb +63 -69
  73. data/lib/action_dispatch/routing/route_set.rb +75 -43
  74. data/lib/action_dispatch/routing/routes_proxy.rb +0 -4
  75. data/lib/action_dispatch/routing/url_for.rb +3 -3
  76. data/lib/action_dispatch/testing/assertions/response.rb +5 -7
  77. data/lib/action_dispatch/testing/assertions/routing.rb +10 -9
  78. data/lib/action_dispatch/testing/integration.rb +8 -25
  79. data/lib/action_dispatch/testing/test_process.rb +3 -2
  80. data/lib/action_dispatch/testing/test_request.rb +4 -23
  81. data/lib/action_pack/version.rb +3 -3
  82. data/lib/action_view.rb +1 -5
  83. data/lib/action_view/asset_paths.rb +7 -8
  84. data/lib/action_view/base.rb +7 -5
  85. data/lib/action_view/helpers/asset_paths.rb +1 -1
  86. data/lib/action_view/helpers/asset_tag_helper.rb +4 -8
  87. data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +3 -0
  88. data/lib/action_view/helpers/atom_feed_helper.rb +2 -2
  89. data/lib/action_view/helpers/capture_helper.rb +3 -3
  90. data/lib/action_view/helpers/controller_helper.rb +1 -1
  91. data/lib/action_view/helpers/date_helper.rb +26 -18
  92. data/lib/action_view/helpers/debug_helper.rb +1 -1
  93. data/lib/action_view/helpers/form_helper.rb +71 -13
  94. data/lib/action_view/helpers/form_options_helper.rb +65 -34
  95. data/lib/action_view/helpers/form_tag_helper.rb +24 -18
  96. data/lib/action_view/helpers/javascript_helper.rb +12 -3
  97. data/lib/action_view/helpers/number_helper.rb +3 -2
  98. data/lib/action_view/helpers/record_tag_helper.rb +51 -5
  99. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  100. data/lib/action_view/helpers/sanitize_helper.rb +6 -7
  101. data/lib/action_view/helpers/tag_helper.rb +1 -1
  102. data/lib/action_view/helpers/text_helper.rb +5 -4
  103. data/lib/action_view/helpers/url_helper.rb +19 -11
  104. data/lib/action_view/locale/en.yml +6 -0
  105. data/lib/action_view/log_subscriber.rb +1 -1
  106. data/lib/action_view/lookup_context.rb +123 -125
  107. data/lib/action_view/path_set.rb +60 -13
  108. data/lib/action_view/renderer/abstract_renderer.rb +16 -11
  109. data/lib/action_view/renderer/partial_renderer.rb +59 -40
  110. data/lib/action_view/renderer/template_renderer.rb +29 -17
  111. data/lib/action_view/template.rb +0 -1
  112. data/lib/action_view/template/error.rb +6 -5
  113. data/lib/action_view/template/handlers.rb +0 -6
  114. data/lib/action_view/template/handlers/builder.rb +10 -1
  115. data/lib/action_view/template/handlers/erb.rb +2 -2
  116. data/lib/action_view/template/resolver.rb +20 -31
  117. data/lib/action_view/test_case.rb +7 -10
  118. data/lib/sprockets/assets.rake +1 -1
  119. data/lib/sprockets/bootstrap.rb +3 -31
  120. data/lib/sprockets/compressors.rb +69 -7
  121. data/lib/sprockets/helpers/rails_helper.rb +6 -11
  122. data/lib/sprockets/railtie.rb +1 -0
  123. data/lib/sprockets/static_compiler.rb +0 -3
  124. metadata +57 -86
  125. checksums.yaml +0 -7
  126. data/lib/action_dispatch/middleware/closed_error.rb +0 -7
  127. data/lib/action_dispatch/routing/route.rb +0 -67
  128. data/lib/action_view/template/handler.rb +0 -49
@@ -2,50 +2,80 @@ module ActionDispatch
2
2
  class RemoteIp
3
3
  class IpSpoofAttackError < StandardError ; end
4
4
 
5
- class RemoteIpGetter
6
- def initialize(env, check_ip_spoofing, trusted_proxies)
7
- @env = env
8
- @check_ip_spoofing = check_ip_spoofing
9
- @trusted_proxies = trusted_proxies
5
+ # IP addresses that are "trusted proxies" that can be stripped from
6
+ # the comma-delimited list in the X-Forwarded-For header. See also:
7
+ # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
8
+ TRUSTED_PROXIES = %r{
9
+ ^127\.0\.0\.1$ | # localhost
10
+ ^(10 | # private IP 10.x.x.x
11
+ 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
12
+ 192\.168 # private IP 192.168.x.x
13
+ )\.
14
+ }x
15
+
16
+ attr_reader :check_ip, :proxies
17
+
18
+ def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
19
+ @app = app
20
+ @check_ip = check_ip_spoofing
21
+ if custom_proxies
22
+ custom_regexp = Regexp.new(custom_proxies)
23
+ @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp)
24
+ else
25
+ @proxies = TRUSTED_PROXIES
10
26
  end
27
+ end
11
28
 
12
- def remote_addrs
13
- @remote_addrs ||= begin
14
- list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
15
- list.reject { |addr| addr =~ @trusted_proxies }
16
- end
29
+ def call(env)
30
+ env["action_dispatch.remote_ip"] = GetIp.new(env, self)
31
+ @app.call(env)
32
+ end
33
+
34
+ class GetIp
35
+ def initialize(env, middleware)
36
+ @env = env
37
+ @middleware = middleware
38
+ @calculated_ip = false
17
39
  end
18
40
 
19
- def to_s
20
- return remote_addrs.first if remote_addrs.any?
21
-
22
- forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
23
-
24
- if client_ip = @env['HTTP_CLIENT_IP']
25
- if @check_ip_spoofing && !forwarded_ips.include?(client_ip)
26
- # We don't know which came from the proxy, and which from the user
27
- raise IpSpoofAttackError, "IP spoofing attack?!" \
28
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
29
- "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
30
- end
31
- return client_ip
41
+ # Determines originating IP address. REMOTE_ADDR is the standard
42
+ # but will be wrong if the user is behind a proxy. Proxies will set
43
+ # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
44
+ # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
45
+ # multiple chained proxies. The last address which is not a known proxy
46
+ # will be the originating IP.
47
+ def calculate_ip
48
+ client_ip = @env['HTTP_CLIENT_IP']
49
+ forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR')
50
+ remote_addrs = ips_from('REMOTE_ADDR')
51
+
52
+ check_ip = client_ip && @middleware.check_ip
53
+ if check_ip && !forwarded_ips.include?(client_ip)
54
+ # We don't know which came from the proxy, and which from the user
55
+ raise IpSpoofAttackError, "IP spoofing attack?!" \
56
+ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
57
+ "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
32
58
  end
33
59
 
34
- return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"]
60
+ not_proxy = client_ip || forwarded_ips.last || remote_addrs.first
61
+
62
+ # Return first REMOTE_ADDR if there are no other options
63
+ not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first
35
64
  end
36
- end
37
65
 
38
- def initialize(app, check_ip_spoofing = true, trusted_proxies = nil)
39
- @app = app
40
- @check_ip_spoofing = check_ip_spoofing
41
- regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)'
42
- regex << "|(#{trusted_proxies})" if trusted_proxies
43
- @trusted_proxies = Regexp.new(regex, "i")
44
- end
66
+ def to_s
67
+ return @ip if @calculated_ip
68
+ @calculated_ip = true
69
+ @ip = calculate_ip
70
+ end
45
71
 
46
- def call(env)
47
- env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies)
48
- @app.call(env)
72
+ protected
73
+
74
+ def ips_from(header, allow_proxies = false)
75
+ ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : []
76
+ allow_proxies ? ips : ips.reject{|ip| ip =~ @middleware.proxies }
77
+ end
49
78
  end
79
+
50
80
  end
51
- end
81
+ end
@@ -0,0 +1,39 @@
1
+ require 'securerandom'
2
+ require 'active_support/core_ext/string/access'
3
+ require 'active_support/core_ext/object/blank'
4
+
5
+ module ActionDispatch
6
+ # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
7
+ # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
8
+ #
9
+ # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
10
+ # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
11
+ # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
12
+ #
13
+ # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
14
+ # from multiple pieces of the stack.
15
+ class RequestId
16
+ def initialize(app)
17
+ @app = app
18
+ end
19
+
20
+ def call(env)
21
+ env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
22
+ status, headers, body = @app.call(env)
23
+
24
+ headers["X-Request-Id"] = env["action_dispatch.request_id"]
25
+ [ status, headers, body ]
26
+ end
27
+
28
+ private
29
+ def external_request_id(env)
30
+ if request_id = env["HTTP_X_REQUEST_ID"].presence
31
+ request_id.gsub(/[^\w\-]/, "").first(255)
32
+ end
33
+ end
34
+
35
+ def internal_request_id
36
+ SecureRandom.hex(16)
37
+ end
38
+ end
39
+ end
@@ -25,8 +25,6 @@ module ActionDispatch
25
25
  module Compatibility
26
26
  def initialize(app, options = {})
27
27
  options[:key] ||= '_session_id'
28
- # FIXME Rack's secret is not being used
29
- options[:secret] ||= SecureRandom.hex(30)
30
28
  super
31
29
  end
32
30
 
@@ -61,12 +59,12 @@ module ActionDispatch
61
59
  # Note that the regexp does not allow $1 to end with a ':'
62
60
  $1.constantize
63
61
  rescue LoadError, NameError => const_error
64
- raise ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
62
+ raise ActionDispatch::Session::SessionRestoreError,
63
+ "Session contains objects whose class definition isn't available.\n" +
64
+ "Remember to require the classes for all objects kept in the session.\n" +
65
+ "(Original exception: #{const_error.message} [#{const_error.class}])\n"
65
66
  end
66
67
  retry
67
- elsif argument_error.message =~ %r{dump format error \(user class\)}
68
- # Error unmarshalling object from session.
69
- {}
70
68
  else
71
69
  raise
72
70
  end
@@ -76,16 +74,6 @@ module ActionDispatch
76
74
  class AbstractStore < Rack::Session::Abstract::ID
77
75
  include Compatibility
78
76
  include StaleSessionCheck
79
-
80
- def destroy_session(env, sid, options)
81
- ActiveSupport::Deprecation.warn "Implementing #destroy in session stores is deprecated. " <<
82
- "Please implement destroy_session(env, session_id, options) instead."
83
- destroy(env)
84
- end
85
-
86
- def destroy(env)
87
- raise '#destroy needs to be implemented.'
88
- end
89
77
  end
90
78
  end
91
79
  end
@@ -0,0 +1,50 @@
1
+ require 'action_dispatch/middleware/session/abstract_store'
2
+ require 'rack/session/memcache'
3
+
4
+ module ActionDispatch
5
+ module Session
6
+ # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
7
+ # if you don't store critical data in your sessions and you don't need them to live for extended periods
8
+ # of time.
9
+ class CacheStore < AbstractStore
10
+ # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
11
+ # not specified, <tt>Rails.cache</tt> will be used.
12
+ def initialize(app, options = {})
13
+ @cache = options[:cache] || Rails.cache
14
+ options[:expire_after] ||= @cache.options[:expires_in]
15
+ super
16
+ end
17
+
18
+ # Get a session from the cache.
19
+ def get_session(env, sid)
20
+ sid ||= generate_sid
21
+ session = @cache.read(cache_key(sid))
22
+ session ||= {}
23
+ [sid, session]
24
+ end
25
+
26
+ # Set a session in the cache.
27
+ def set_session(env, sid, session, options)
28
+ key = cache_key(sid)
29
+ if session
30
+ @cache.write(key, session, :expires_in => options[:expire_after])
31
+ else
32
+ @cache.delete(key)
33
+ end
34
+ sid
35
+ end
36
+
37
+ # Remove a session from the cache.
38
+ def destroy_session(env, sid, options)
39
+ @cache.delete(cache_key(sid))
40
+ generate_sid
41
+ end
42
+
43
+ private
44
+ # Turn the session id into a cache key.
45
+ def cache_key(sid)
46
+ "_session_id:#{sid}"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -59,7 +59,7 @@ module ActionDispatch
59
59
  end
60
60
 
61
61
  def set_session(env, sid, session_data, options)
62
- session_data.merge!("session_id" => sid)
62
+ session_data.merge("session_id" => sid)
63
63
  end
64
64
 
65
65
  def set_cookie(env, session_id, cookie)
@@ -1,171 +1,87 @@
1
- require 'active_support/core_ext/exception'
2
- require 'active_support/notifications'
3
1
  require 'action_dispatch/http/request'
2
+ require 'action_dispatch/middleware/exception_wrapper'
3
+ require 'active_support/deprecation'
4
4
 
5
5
  module ActionDispatch
6
- # This middleware rescues any exception returned by the application and renders
7
- # nice exception pages if it's being rescued locally.
6
+ # This middleware rescues any exception returned by the application
7
+ # and calls an exceptions app that will wrap it in a format for the end user.
8
+ #
9
+ # The exceptions app should be passed as parameter on initialization
10
+ # of ShowExceptions. Everytime there is an exception, ShowExceptions will
11
+ # store the exception in env["action_dispatch.exception"], rewrite the
12
+ # PATH_INFO to the exception status code and call the rack app.
13
+ #
14
+ # If the application returns a "X-Cascade" pass response, this middleware
15
+ # will send an empty response as result with the correct status code.
16
+ # If any exception happens inside the exceptions app, this middleware
17
+ # catches the exceptions and returns a FAILSAFE_RESPONSE.
8
18
  class ShowExceptions
9
- RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
10
-
11
- cattr_accessor :rescue_responses
12
- @@rescue_responses = Hash.new(:internal_server_error)
13
- @@rescue_responses.update({
14
- 'ActionController::RoutingError' => :not_found,
15
- 'AbstractController::ActionNotFound' => :not_found,
16
- 'ActiveRecord::RecordNotFound' => :not_found,
17
- 'ActiveRecord::StaleObjectError' => :conflict,
18
- 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
19
- 'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
20
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
21
- 'ActionController::NotImplemented' => :not_implemented,
22
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
23
- })
24
-
25
- cattr_accessor :rescue_templates
26
- @@rescue_templates = Hash.new('diagnostics')
27
- @@rescue_templates.update({
28
- 'ActionView::MissingTemplate' => 'missing_template',
29
- 'ActionController::RoutingError' => 'routing_error',
30
- 'AbstractController::ActionNotFound' => 'unknown_action',
31
- 'ActionView::Template::Error' => 'template_error'
32
- })
33
-
34
19
  FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'},
35
20
  ["<html><body><h1>500 Internal Server Error</h1>" <<
36
21
  "If you are the administrator of this website, then please read this web " <<
37
22
  "application's log file and/or the web server's log file to find out what " <<
38
23
  "went wrong.</body></html>"]]
39
24
 
40
- def initialize(app, consider_all_requests_local = false)
41
- @app = app
42
- @consider_all_requests_local = consider_all_requests_local
43
- end
44
-
45
- def call(env)
46
- begin
47
- status, headers, body = @app.call(env)
48
- exception = nil
49
-
50
- # Only this middleware cares about RoutingError. So, let's just raise
51
- # it here.
52
- if headers['X-Cascade'] == 'pass'
53
- raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
54
- end
55
- rescue Exception => exception
56
- raise exception if env['action_dispatch.show_exceptions'] == false
57
- end
58
-
59
- exception ? render_exception(env, exception) : [status, headers, body]
60
- end
61
-
62
- private
63
- def render_exception(env, exception)
64
- log_error(exception)
65
- exception = original_exception(exception)
66
-
67
- request = Request.new(env)
68
- if @consider_all_requests_local || request.local?
69
- rescue_action_locally(request, exception)
70
- else
71
- rescue_action_in_public(exception)
72
- end
73
- rescue Exception => failsafe_error
74
- $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
75
- FAILSAFE_RESPONSE
76
- end
77
-
78
- # Render detailed diagnostics for unhandled exceptions rescued from
79
- # a controller action.
80
- def rescue_action_locally(request, exception)
81
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
82
- :request => request,
83
- :exception => exception,
84
- :application_trace => application_trace(exception),
85
- :framework_trace => framework_trace(exception),
86
- :full_trace => full_trace(exception)
87
- )
88
- file = "rescues/#{@@rescue_templates[exception.class.name]}.erb"
89
- body = template.render(:file => file, :layout => 'rescues/layout.erb')
90
- render(status_code(exception), body)
91
- end
92
-
93
- # Attempts to render a static error page based on the
94
- # <tt>status_code</tt> thrown, or just return headers if no such file
95
- # exists. At first, it will try to render a localized static page.
96
- # For example, if a 500 error is being handled Rails and locale is :da,
97
- # it will first attempt to render the file at <tt>public/500.da.html</tt>
98
- # then attempt to render <tt>public/500.html</tt>. If none of them exist,
99
- # the body of the response will be left empty.
100
- def rescue_action_in_public(exception)
101
- status = status_code(exception)
102
- locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
103
- path = "#{public_path}/#{status}.html"
104
-
105
- if locale_path && File.exist?(locale_path)
106
- render(status, File.read(locale_path))
107
- elsif File.exist?(path)
108
- render(status, File.read(path))
109
- else
110
- render(status, '')
111
- end
25
+ class << self
26
+ def rescue_responses
27
+ ActiveSupport::Deprecation.warn "ActionDispatch::ShowExceptions.rescue_responses is deprecated. " \
28
+ "Please configure your exceptions using a railtie or in your application config instead."
29
+ ExceptionWrapper.rescue_responses
112
30
  end
113
31
 
114
- def status_code(exception)
115
- Rack::Utils.status_code(@@rescue_responses[exception.class.name])
116
- end
117
-
118
- def render(status, body)
119
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
32
+ def rescue_templates
33
+ ActiveSupport::Deprecation.warn "ActionDispatch::ShowExceptions.rescue_templates is deprecated. " \
34
+ "Please configure your exceptions using a railtie or in your application config instead."
35
+ ExceptionWrapper.rescue_templates
120
36
  end
37
+ end
121
38
 
122
- def public_path
123
- defined?(Rails.public_path) ? Rails.public_path : 'public_path'
39
+ def initialize(app, exceptions_app = nil)
40
+ if [true, false].include?(exceptions_app)
41
+ ActiveSupport::Deprecation.warn "Passing consider_all_requests_local option to ActionDispatch::ShowExceptions middleware no longer works"
42
+ exceptions_app = nil
124
43
  end
125
44
 
126
- def log_error(exception)
127
- return unless logger
128
-
129
- ActiveSupport::Deprecation.silence do
130
- message = "\n#{exception.class} (#{exception.message}):\n"
131
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
132
- message << " " << application_trace(exception).join("\n ")
133
- logger.fatal("#{message}\n\n")
134
- end
45
+ if exceptions_app.nil?
46
+ raise ArgumentError, "You need to pass an exceptions_app when initializing ActionDispatch::ShowExceptions. " \
47
+ "In case you want to render pages from a public path, you can use ActionDispatch::PublicExceptions.new('path/to/public')"
135
48
  end
136
49
 
137
- def application_trace(exception)
138
- clean_backtrace(exception, :silent)
139
- end
50
+ @app = app
51
+ @exceptions_app = exceptions_app
52
+ end
140
53
 
141
- def framework_trace(exception)
142
- clean_backtrace(exception, :noise)
54
+ def call(env)
55
+ begin
56
+ response = @app.call(env)
57
+ rescue Exception => exception
58
+ raise exception if env['action_dispatch.show_exceptions'] == false
143
59
  end
144
60
 
145
- def full_trace(exception)
146
- clean_backtrace(exception, :all)
147
- end
61
+ response || render_exception(env, exception)
62
+ end
148
63
 
149
- def clean_backtrace(exception, *args)
150
- defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
151
- Rails.backtrace_cleaner.clean(exception.backtrace, *args) :
152
- exception.backtrace
153
- end
64
+ private
154
65
 
155
- def logger
156
- defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
157
- end
66
+ # Define this method because some plugins were monkey patching it.
67
+ # Remove this after 3.2 is out with the other deprecations in this class.
68
+ def status_code(*)
69
+ end
158
70
 
159
- def original_exception(exception)
160
- if registered_original_exception?(exception)
161
- exception.original_exception
162
- else
163
- exception
164
- end
71
+ def render_exception(env, exception)
72
+ wrapper = ExceptionWrapper.new(env, exception)
73
+ status = wrapper.status_code
74
+ env["action_dispatch.exception"] = wrapper.exception
75
+ env["PATH_INFO"] = "/#{status}"
76
+ response = @exceptions_app.call(env)
77
+ response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
78
+ rescue Exception => failsafe_error
79
+ $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
80
+ FAILSAFE_RESPONSE
165
81
  end
166
82
 
167
- def registered_original_exception?(exception)
168
- exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
83
+ def pass_response(status)
84
+ [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []]
169
85
  end
170
86
  end
171
87
  end