aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/middleware
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware')
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb310
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb240
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_locks.rb124
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb119
-rw-r--r--actionpack/lib/action_dispatch/middleware/executor.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb37
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb46
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb50
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb52
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb26
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb44
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb108
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb42
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb120
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb35
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb59
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb8
24 files changed, 799 insertions, 699 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index c782779b34..5b2ad36dd5 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
module ActionDispatch
# Provides callbacks to be executed before and after dispatching the request.
@@ -7,17 +8,6 @@ module ActionDispatch
define_callbacks :call
class << self
- def to_prepare(*args, &block)
- ActiveSupport::Reloader.to_prepare(*args, &block)
- end
-
- def to_cleanup(*args, &block)
- ActiveSupport::Reloader.to_complete(*args, &block)
- end
-
- deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead',
- to_cleanup: 'use ActiveSupport::Reloader.to_complete instead'
-
def before(*args, &block)
set_callback(:call, :before, *args, &block)
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index f2f3150b56..ea4156c972 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -1,13 +1,15 @@
-require 'active_support/core_ext/hash/keys'
-require 'active_support/key_generator'
-require 'active_support/message_verifier'
-require 'active_support/json'
-require 'rack/utils'
+# frozen_string_literal: true
+
+require "active_support/core_ext/hash/keys"
+require "active_support/key_generator"
+require "active_support/message_verifier"
+require "active_support/json"
+require "rack/utils"
module ActionDispatch
class Request
def cookie_jar
- fetch_header('action_dispatch.cookies'.freeze) do
+ fetch_header("action_dispatch.cookies".freeze) do
self.cookie_jar = Cookies::CookieJar.build(self, cookies)
end
end
@@ -20,11 +22,11 @@ module ActionDispatch
}
def have_cookie_jar?
- has_header? 'action_dispatch.cookies'.freeze
+ has_header? "action_dispatch.cookies".freeze
end
def cookie_jar=(jar)
- set_header 'action_dispatch.cookies'.freeze, jar
+ set_header "action_dispatch.cookies".freeze, jar
end
def key_generator
@@ -43,6 +45,22 @@ module ActionDispatch
get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT
end
+ def authenticated_encrypted_cookie_salt
+ get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT
+ end
+
+ def use_authenticated_cookie_encryption
+ get_header Cookies::USE_AUTHENTICATED_COOKIE_ENCRYPTION
+ end
+
+ def encrypted_cookie_cipher
+ get_header Cookies::ENCRYPTED_COOKIE_CIPHER
+ end
+
+ def signed_cookie_digest
+ get_header Cookies::SIGNED_COOKIE_DIGEST
+ end
+
def secret_token
get_header Cookies::SECRET_TOKEN
end
@@ -58,6 +76,11 @@ module ActionDispatch
def cookies_digest
get_header Cookies::COOKIES_DIGEST
end
+
+ def cookies_rotations
+ get_header Cookies::COOKIES_ROTATIONS
+ end
+
# :startdoc:
end
@@ -77,16 +100,17 @@ module ActionDispatch
# cookies[:lat_lon] = JSON.generate([47.68, -122.37])
#
# # Sets a cookie that expires in 1 hour.
- # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
+ # cookies[:login] = { value: "XJ-122", expires: 1.hour }
+ #
+ # # Sets a cookie that expires at a specific time.
+ # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's `secrets.secret_key_base` value.
# # It can be read using the signed method `cookies.signed[:name]`
# cookies.signed[:user_id] = current_user.id
#
# # Sets an encrypted cookie value before sending it to the client which
# # prevent users from reading and tampering with its value.
- # # The cookie is signed by your app's `secrets.secret_key_base` value.
# # It can be read using the encrypted method `cookies.encrypted[:name]`
# cookies.encrypted[:discount] = 45
#
@@ -94,7 +118,7 @@ module ActionDispatch
# cookies.permanent[:login] = "XJ-122"
#
# # You can also chain these methods:
- # cookies.permanent.signed[:login] = "XJ-122"
+ # cookies.signed.permanent[:login] = "XJ-122"
#
# Examples of reading:
#
@@ -112,7 +136,7 @@ module ActionDispatch
#
# cookies[:name] = {
# value: 'a yummy cookie',
- # expires: 1.year.from_now,
+ # expires: 1.year,
# domain: 'domain.com'
# }
#
@@ -137,8 +161,8 @@ module ActionDispatch
#
# * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
# set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
- # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 1.
- # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
+ # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 2.
+ # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object.
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
# Default is +false+.
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
@@ -149,10 +173,15 @@ module ActionDispatch
SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
+ AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze
+ USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption".freeze
+ ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher".freeze
+ SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
+ COOKIES_ROTATIONS = "action_dispatch.cookies_rotations".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -160,7 +189,7 @@ module ActionDispatch
# Raised when storing more than 4K of session data.
CookieOverflow = Class.new StandardError
- # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed
+ # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed.
module ChainedCookieJars
# Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
#
@@ -179,12 +208,12 @@ module ActionDispatch
# Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
- # cookie was tampered with by the user (or a 3rd party), nil will be returned.
+ # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
+ # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
+ # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
#
# Example:
#
@@ -193,35 +222,28 @@ module ActionDispatch
#
# cookies.signed[:discount] # => 45
def signed
- @signed ||=
- if upgrade_legacy_signed_cookies?
- UpgradeLegacySignedCookieJar.new(self)
- else
- SignedCookieJar.new(self)
- end
+ @signed ||= SignedKeyRotatingCookieJar.new(self)
end
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
- # If the cookie was tampered with by the user (or a 3rd party), nil will be returned.
+ # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
+ # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
+ # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
+ # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
+ #
+ # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
#
# Example:
#
# cookies.encrypted[:discount] = 45
- # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
#
# cookies.encrypted[:discount] # => 45
def encrypted
- @encrypted ||=
- if upgrade_legacy_signed_cookies?
- UpgradeLegacyEncryptedCookieJar.new(self)
- else
- EncryptedCookieJar.new(self)
- end
+ @encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
end
# Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
@@ -237,32 +259,23 @@ module ActionDispatch
private
- def upgrade_legacy_signed_cookies?
- request.secret_token.present? && request.secret_key_base.present?
- end
- end
+ def upgrade_legacy_signed_cookies?
+ request.secret_token.present? && request.secret_key_base.present?
+ end
- # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
- # to the Message{Encryptor,Verifier} allows us to handle the
- # (de)serialization step within the cookie jar, which gives us the
- # opportunity to detect and migrate legacy cookies.
- module VerifyAndUpgradeLegacySignedMessage # :nodoc:
- def initialize(*args)
- super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
- end
+ def upgrade_legacy_hmac_aes_cbc_cookies?
+ request.secret_key_base.present? &&
+ request.encrypted_signed_cookie_salt.present? &&
+ request.encrypted_cookie_salt.present? &&
+ request.use_authenticated_cookie_encryption
+ end
- def verify_and_upgrade_legacy_signed_message(name, signed_message)
- deserialize(name, @legacy_verifier.verify(signed_message)).tap do |value|
- self[name] = { value: value }
+ def encrypted_cookie_cipher
+ request.encrypted_cookie_cipher || "aes-256-gcm"
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
- end
- private
- def parse(name, signed_message)
- super || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ def signed_cookie_digest
+ request.signed_cookie_digest || "SHA1"
end
end
@@ -332,30 +345,34 @@ module ActionDispatch
def update_cookies_from_jar
request_jar = @request.cookie_jar.instance_variable_get(:@cookies)
- set_cookies = request_jar.reject { |k,_| @delete_cookies.key?(k) }
+ set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) }
@cookies.update set_cookies if set_cookies
end
def to_header
- @cookies.map { |k,v| "#{escape(k)}=#{escape(v)}" }.join '; '
+ @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; "
end
- def handle_options(options) #:nodoc:
+ def handle_options(options) # :nodoc:
+ if options[:expires].respond_to?(:from_now)
+ options[:expires] = options[:expires].from_now
+ end
+
options[:path] ||= "/"
- if options[:domain] == :all || options[:domain] == 'all'
- # if there is a provided tld length then we use it otherwise default domain regexp
+ if options[:domain] == :all || options[:domain] == "all"
+ # If there is a provided tld length then we use it otherwise default domain regexp.
domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
- # if host is not ip and matches domain regexp
+ # If host is not ip and matches domain regexp.
# (ip confirms to domain regexp so we explicitly check for ip)
options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp)
".#{$&}"
end
elsif options[:domain].is_a? Array
- # if host matches one of the supplied domains without a dot in front of it
- options[:domain] = options[:domain].find {|domain| request.host.include? domain.sub(/^\./, '') }
+ # If host matches one of the supplied domains without a dot in front of it.
+ options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") }
end
end
@@ -367,12 +384,12 @@ module ActionDispatch
value = options[:value]
else
value = options
- options = { :value => value }
+ options = { value: value }
end
handle_options(options)
- if @cookies[name.to_s] != value or options[:expires]
+ if @cookies[name.to_s] != value || options[:expires]
@cookies[name.to_s] = value
@set_cookies[name.to_s] = options
@delete_cookies.delete(name.to_s)
@@ -404,9 +421,9 @@ module ActionDispatch
@delete_cookies[name.to_s] == options
end
- # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
+ # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie.
def clear(options = {})
- @cookies.each_key{ |k| delete(k, options) }
+ @cookies.each_key { |k| delete(k, options) }
end
def write(headers)
@@ -415,31 +432,30 @@ module ActionDispatch
end
end
- mattr_accessor :always_write_cookie
- self.always_write_cookie = false
+ mattr_accessor :always_write_cookie, default: false
private
- def escape(string)
- ::Rack::Utils.escape(string)
- end
+ def escape(string)
+ ::Rack::Utils.escape(string)
+ end
- def make_set_cookie_header(header)
- header = @set_cookies.inject(header) { |m, (k, v)|
- if write_cookie?(v)
- ::Rack::Utils.add_cookie_to_header(m, k, v)
- else
- m
- end
- }
- @delete_cookies.inject(header) { |m, (k, v)|
- ::Rack::Utils.add_remove_cookie_to_header(m, k, v)
- }
- end
+ def make_set_cookie_header(header)
+ header = @set_cookies.inject(header) { |m, (k, v)|
+ if write_cookie?(v)
+ ::Rack::Utils.add_cookie_to_header(m, k, v)
+ else
+ m
+ end
+ }
+ @delete_cookies.inject(header) { |m, (k, v)|
+ ::Rack::Utils.add_remove_cookie_to_header(m, k, v)
+ }
+ end
- def write_cookie?(cookie)
- request.ssl? || !cookie[:secure] || always_write_cookie
- end
+ def write_cookie?(cookie)
+ request.ssl? || !cookie[:secure] || always_write_cookie
+ end
end
class AbstractCookieJar # :nodoc:
@@ -470,6 +486,14 @@ module ActionDispatch
def request; @parent_jar.request; end
private
+ def expiry_options(options)
+ if options[:expires].respond_to?(:from_now)
+ { expires_in: options[:expires] }
+ else
+ { expires_at: options[:expires] }
+ end
+ end
+
def parse(name, data); data; end
def commit(options); end
end
@@ -493,6 +517,7 @@ module ActionDispatch
module SerializedCookieJars # :nodoc:
MARSHAL_SIGNATURE = "\x04\x08".freeze
+ SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
protected
def needs_migration?(value)
@@ -503,12 +528,16 @@ module ActionDispatch
serializer.dump(value)
end
- def deserialize(name, value)
+ def deserialize(name)
+ rotate = false
+ value = yield -> { rotate = true }
+
if value
- if needs_migration?(value)
- Marshal.load(value).tap do |v|
- self[name] = { value: v }
- end
+ case
+ when needs_migration?(value)
+ self[name] = Marshal.load(value)
+ when rotate
+ self[name] = serializer.load(value)
else
serializer.load(value)
end
@@ -528,79 +557,100 @@ module ActionDispatch
end
def digest
- request.cookies_digest || 'SHA1'
- end
-
- def key_generator
- request.key_generator
+ request.cookies_digest || "SHA1"
end
end
- class SignedCookieJar < AbstractCookieJar # :nodoc:
+ class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
include SerializedCookieJars
def initialize(parent_jar)
super
- secret = key_generator.generate_key(request.signed_cookie_salt)
- @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+
+ secret = request.key_generator.generate_key(request.signed_cookie_salt)
+ @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
+
+ request.cookies_rotations.signed.each do |*secrets, **options|
+ @verifier.rotate(*secrets, serializer: SERIALIZER, **options)
+ end
+
+ if upgrade_legacy_signed_cookies?
+ @verifier.rotate request.secret_token, serializer: SERIALIZER
+ end
end
private
def parse(name, signed_message)
- deserialize name, @verifier.verified(signed_message)
+ deserialize(name) do |rotate|
+ @verifier.verified(signed_message, on_rotation: rotate)
+ end
end
def commit(options)
- options[:value] = @verifier.generate(serialize(options[:value]))
+ options[:value] = @verifier.generate(serialize(options[:value]), expiry_options(options))
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
end
- # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
- # secrets.secret_token and secrets.secret_key_base are both set. It reads
- # legacy cookies signed with the old dummy key generator and signs and
- # re-saves them using the new key generator to provide a smooth upgrade path.
- class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
- include VerifyAndUpgradeLegacySignedMessage
- end
-
- class EncryptedCookieJar < AbstractCookieJar # :nodoc:
+ class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
include SerializedCookieJars
def initialize(parent_jar)
super
- if ActiveSupport::LegacyKeyGenerator === key_generator
- raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " +
- "Read the upgrade documentation to learn more about this new config option."
+ if request.use_authenticated_cookie_encryption
+ key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER)
+ else
+ key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len)
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
+ end
+
+ request.cookies_rotations.encrypted.each do |*secrets, **options|
+ @encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
+ end
+
+ if upgrade_legacy_hmac_aes_cbc_cookies?
+ legacy_cipher = "aes-256-cbc"
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher))
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
+
+ @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
end
- secret = key_generator.generate_key(request.encrypted_cookie_salt || '')
- sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || '')
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ if upgrade_legacy_signed_cookies?
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
+ end
end
private
def parse(name, encrypted_message)
- deserialize name, @encryptor.decrypt_and_verify(encrypted_message)
- rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
- nil
+ deserialize(name) do |rotate|
+ @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
+ end
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
+ parse_legacy_signed_message(name, encrypted_message)
end
def commit(options)
- options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]))
+ options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), expiry_options(options))
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
- end
- # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
- # instead of EncryptedCookieJar if secrets.secret_token and secrets.secret_key_base
- # are both set. It reads legacy cookies signed with the old dummy key generator and
- # encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
- class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
- include VerifyAndUpgradeLegacySignedMessage
+ def parse_legacy_signed_message(name, legacy_signed_message)
+ if defined?(@legacy_verifier)
+ deserialize(name) do |rotate|
+ rotate.call
+
+ @legacy_verifier.verified(legacy_signed_message)
+ end
+ end
+ end
end
def initialize(app)
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 51a471fb23..511306eb0e 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -1,16 +1,18 @@
-require 'action_dispatch/http/request'
-require 'action_dispatch/middleware/exception_wrapper'
-require 'action_dispatch/routing/inspector'
-require 'action_view'
-require 'action_view/base'
+# frozen_string_literal: true
-require 'pp'
+require "action_dispatch/http/request"
+require "action_dispatch/middleware/exception_wrapper"
+require "action_dispatch/routing/inspector"
+require "action_view"
+require "action_view/base"
+
+require "pp"
module ActionDispatch
# This middleware is responsible for logging exceptions and
# showing a debugging page in case the request is local.
class DebugExceptions
- RESCUES_TEMPLATE_PATH = File.expand_path('../templates', __FILE__)
+ RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__)
class DebugView < ActionView::Base
def debug_params(params)
@@ -19,23 +21,33 @@ module ActionDispatch
clean_params.delete("controller")
if clean_params.empty?
- 'None'
+ "None"
else
- PP.pp(clean_params, "", 200)
+ PP.pp(clean_params, "".dup, 200)
end
end
def debug_headers(headers)
if headers.present?
- headers.inspect.gsub(',', ",\n")
+ headers.inspect.gsub(",", ",\n")
else
- 'None'
+ "None"
end
end
def debug_hash(object)
object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
end
+
+ def render(*)
+ logger = ActionView::Base.logger
+
+ if logger && logger.respond_to?(:silence)
+ logger.silence { super }
+ else
+ super
+ end
+ end
end
def initialize(app, routes_app = nil, response_format = :default)
@@ -48,7 +60,7 @@ module ActionDispatch
request = ActionDispatch::Request.new env
_, headers, body = response = @app.call(env)
- if headers['X-Cascade'] == 'pass'
+ if headers["X-Cascade"] == "pass"
body.close if body.respond_to?(:close)
raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
end
@@ -61,125 +73,133 @@ module ActionDispatch
private
- def render_exception(request, exception)
- backtrace_cleaner = request.get_header('action_dispatch.backtrace_cleaner')
- wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
- log_error(request, wrapper)
-
- if request.get_header('action_dispatch.show_detailed_exceptions')
- case @response_format
- when :api
- render_for_api_application(request, wrapper)
- when :default
- render_for_default_application(request, wrapper)
+ def render_exception(request, exception)
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
+ log_error(request, wrapper)
+
+ if request.get_header("action_dispatch.show_detailed_exceptions")
+ content_type = request.formats.first
+
+ if api_request?(content_type)
+ render_for_api_request(content_type, wrapper)
+ else
+ render_for_browser_request(request, wrapper)
+ end
+ else
+ raise exception
end
- else
- raise exception
end
- end
- def render_for_default_application(request, wrapper)
- template = create_template(request, wrapper)
- file = "rescues/#{wrapper.rescue_template}"
+ def render_for_browser_request(request, wrapper)
+ template = create_template(request, wrapper)
+ file = "rescues/#{wrapper.rescue_template}"
- if request.xhr?
- body = template.render(template: file, layout: false, formats: [:text])
- format = "text/plain"
- else
- body = template.render(template: file, layout: 'rescues/layout')
- format = "text/html"
+ if request.xhr?
+ body = template.render(template: file, layout: false, formats: [:text])
+ format = "text/plain"
+ else
+ body = template.render(template: file, layout: "rescues/layout")
+ format = "text/html"
+ end
+ render(wrapper.status_code, body, format)
end
- render(wrapper.status_code, body, format)
- end
-
- def render_for_api_application(request, wrapper)
- body = {
- status: wrapper.status_code,
- error: Rack::Utils::HTTP_STATUS_CODES.fetch(
- wrapper.status_code,
- Rack::Utils::HTTP_STATUS_CODES[500]
- ),
- exception: wrapper.exception.inspect,
- traces: wrapper.traces
- }
-
- content_type = request.formats.first
- to_format = "to_#{content_type.to_sym}"
-
- if content_type && body.respond_to?(to_format)
- formatted_body = body.public_send(to_format)
- format = content_type
- else
- formatted_body = body.to_json
- format = Mime[:json]
- end
-
- render(wrapper.status_code, formatted_body, format)
- end
- def create_template(request, wrapper)
- traces = wrapper.traces
+ def render_for_api_request(content_type, wrapper)
+ body = {
+ status: wrapper.status_code,
+ error: Rack::Utils::HTTP_STATUS_CODES.fetch(
+ wrapper.status_code,
+ Rack::Utils::HTTP_STATUS_CODES[500]
+ ),
+ exception: wrapper.exception.inspect,
+ traces: wrapper.traces
+ }
+
+ to_format = "to_#{content_type.to_sym}"
+
+ if content_type && body.respond_to?(to_format)
+ formatted_body = body.public_send(to_format)
+ format = content_type
+ else
+ formatted_body = body.to_json
+ format = Mime[:json]
+ end
- trace_to_show = 'Application Trace'
- if traces[trace_to_show].empty? && wrapper.rescue_template != 'routing_error'
- trace_to_show = 'Full Trace'
+ render(wrapper.status_code, formatted_body, format)
end
- if source_to_show = traces[trace_to_show].first
- source_to_show_id = source_to_show[:id]
+ def create_template(request, wrapper)
+ traces = wrapper.traces
+
+ trace_to_show = "Application Trace"
+ if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error"
+ trace_to_show = "Full Trace"
+ end
+
+ if source_to_show = traces[trace_to_show].first
+ source_to_show_id = source_to_show[:id]
+ end
+
+ DebugView.new([RESCUES_TEMPLATE_PATH],
+ request: request,
+ exception: wrapper.exception,
+ traces: traces,
+ show_source_idx: source_to_show_id,
+ trace_to_show: trace_to_show,
+ routes_inspector: routes_inspector(wrapper.exception),
+ source_extracts: wrapper.source_extracts,
+ line_number: wrapper.line_number,
+ file: wrapper.file
+ )
end
- DebugView.new([RESCUES_TEMPLATE_PATH],
- request: request,
- exception: wrapper.exception,
- traces: traces,
- show_source_idx: source_to_show_id,
- trace_to_show: trace_to_show,
- routes_inspector: routes_inspector(wrapper.exception),
- source_extracts: wrapper.source_extracts,
- line_number: wrapper.line_number,
- file: wrapper.file
- )
- end
+ def render(status, body, format)
+ [status, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]]
+ end
- def render(status, body, format)
- [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
- end
+ def log_error(request, wrapper)
+ logger = logger(request)
+ return unless logger
- def log_error(request, wrapper)
- logger = logger(request)
- return unless logger
+ exception = wrapper.exception
- exception = wrapper.exception
+ trace = wrapper.application_trace
+ trace = wrapper.framework_trace if trace.empty?
- trace = wrapper.application_trace
- trace = wrapper.framework_trace if trace.empty?
+ ActiveSupport::Deprecation.silence do
+ logger.fatal " "
+ logger.fatal "#{exception.class} (#{exception.message}):"
+ log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
+ logger.fatal " "
+ log_array logger, trace
+ end
+ end
- ActiveSupport::Deprecation.silence do
- logger.fatal " "
- logger.fatal "#{exception.class} (#{exception.message}):"
- log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code)
- logger.fatal " "
- log_array logger, trace
+ def log_array(logger, array)
+ if logger.formatter && logger.formatter.respond_to?(:tags_text)
+ logger.fatal array.join("\n#{logger.formatter.tags_text}")
+ else
+ logger.fatal array.join("\n")
+ end
end
- end
- def log_array(logger, array)
- array.map { |line| logger.fatal line }
- end
+ def logger(request)
+ request.logger || ActionView::Base.logger || stderr_logger
+ end
- def logger(request)
- request.logger || ActionView::Base.logger || stderr_logger
- end
+ def stderr_logger
+ @stderr_logger ||= ActiveSupport::Logger.new($stderr)
+ end
- def stderr_logger
- @stderr_logger ||= ActiveSupport::Logger.new($stderr)
- end
+ def routes_inspector(exception)
+ if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
+ ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
+ end
+ end
- def routes_inspector(exception)
- if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error))
- ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes)
+ def api_request?(content_type)
+ @response_format == :api && !content_type.html?
end
- end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb
new file mode 100644
index 0000000000..03760438f7
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+module ActionDispatch
+ # This middleware can be used to diagnose deadlocks in the autoload interlock.
+ #
+ # To use it, insert it near the top of the middleware stack, using
+ # <tt>config/application.rb</tt>:
+ #
+ # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
+ #
+ # After restarting the application and re-triggering the deadlock condition,
+ # <tt>/rails/locks</tt> will show a summary of all threads currently known to
+ # the interlock, which lock level they are holding or awaiting, and their
+ # current backtrace.
+ #
+ # Generally a deadlock will be caused by the interlock conflicting with some
+ # other external lock or blocking I/O call. These cannot be automatically
+ # identified, but should be visible in the displayed backtraces.
+ #
+ # NOTE: The formatting and content of this middleware's output is intended for
+ # human consumption, and should be expected to change between releases.
+ #
+ # This middleware exposes operational details of the server, with no access
+ # control. It should only be enabled when in use, and removed thereafter.
+ class DebugLocks
+ def initialize(app, path = "/rails/locks")
+ @app = app
+ @path = path
+ end
+
+ def call(env)
+ req = ActionDispatch::Request.new env
+
+ if req.get?
+ path = req.path_info.chomp("/".freeze)
+ if path == @path
+ return render_details(req)
+ end
+ end
+
+ @app.call(env)
+ end
+
+ private
+ def render_details(req)
+ threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads|
+ # The Interlock itself comes to a complete halt as long as this block
+ # is executing. That gives us a more consistent picture of everything,
+ # but creates a pretty strong Observer Effect.
+ #
+ # Most directly, that means we need to do as little as possible in
+ # this block. More widely, it means this middleware should remain a
+ # strictly diagnostic tool (to be used when something has gone wrong),
+ # and not for any sort of general monitoring.
+
+ raw_threads.each.with_index do |(thread, info), idx|
+ info[:index] = idx
+ info[:backtrace] = thread.backtrace
+ end
+
+ raw_threads
+ end
+
+ str = threads.map do |thread, info|
+ if info[:exclusive]
+ lock_state = "Exclusive".dup
+ elsif info[:sharing] > 0
+ lock_state = "Sharing".dup
+ lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
+ else
+ lock_state = "No lock".dup
+ end
+
+ if info[:waiting]
+ lock_state << " (yielded share)"
+ end
+
+ msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n".dup
+
+ if info[:sleeper]
+ msg << " Waiting in #{info[:sleeper]}"
+ msg << " to #{info[:purpose].to_s.inspect}" unless info[:purpose].nil?
+ msg << "\n"
+
+ if info[:compatible]
+ compat = info[:compatible].map { |c| c == false ? "share" : c.to_s.inspect }
+ msg << " may be pre-empted for: #{compat.join(', ')}\n"
+ end
+
+ blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) }
+ msg << " blocked by: #{blockers.map { |i| i[:index] }.join(', ')}\n" if blockers.any?
+ end
+
+ blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) }
+ msg << " blocking: #{blockees.map { |i| i[:index] }.join(', ')}\n" if blockees.any?
+
+ msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace]
+ end.join("\n\n---\n\n\n")
+
+ [200, { "Content-Type" => "text/plain", "Content-Length" => str.size }, [str]]
+ end
+
+ def blocked_by?(victim, blocker, all_threads)
+ return false if victim.equal?(blocker)
+
+ case victim[:sleeper]
+ when :start_sharing
+ blocker[:exclusive] ||
+ (!victim[:waiting] && blocker[:compatible] && !blocker[:compatible].include?(false))
+ when :start_exclusive
+ blocker[:sharing] > 0 ||
+ blocker[:exclusive] ||
+ (blocker[:compatible] && !blocker[:compatible].include?(victim[:purpose]))
+ when :yield_shares
+ blocker[:exclusive]
+ when :stop_exclusive
+ blocker[:exclusive] ||
+ victim[:compatible] &&
+ victim[:compatible].include?(blocker[:purpose]) &&
+ all_threads.all? { |other| !other[:compatible] || blocker.equal?(other) || other[:compatible].include?(blocker[:purpose]) }
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 59edc66086..d1b4508378 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -1,33 +1,32 @@
-require 'active_support/core_ext/module/attribute_accessors'
-require 'rack/utils'
+# frozen_string_literal: true
+
+require "active_support/core_ext/module/attribute_accessors"
+require "rack/utils"
module ActionDispatch
class ExceptionWrapper
- cattr_accessor :rescue_responses
- @@rescue_responses = Hash.new(:internal_server_error)
- @@rescue_responses.merge!(
- 'ActionController::RoutingError' => :not_found,
- 'AbstractController::ActionNotFound' => :not_found,
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
- 'ActionController::UnknownHttpMethod' => :method_not_allowed,
- 'ActionController::NotImplemented' => :not_implemented,
- 'ActionController::UnknownFormat' => :not_acceptable,
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
- 'ActionController::InvalidCrossOriginRequest' => :unprocessable_entity,
- 'ActionDispatch::ParamsParser::ParseError' => :bad_request,
- 'ActionController::BadRequest' => :bad_request,
- 'ActionController::ParameterMissing' => :bad_request,
- 'Rack::Utils::ParameterTypeError' => :bad_request,
- 'Rack::Utils::InvalidParameterError' => :bad_request
+ cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!(
+ "ActionController::RoutingError" => :not_found,
+ "AbstractController::ActionNotFound" => :not_found,
+ "ActionController::MethodNotAllowed" => :method_not_allowed,
+ "ActionController::UnknownHttpMethod" => :method_not_allowed,
+ "ActionController::NotImplemented" => :not_implemented,
+ "ActionController::UnknownFormat" => :not_acceptable,
+ "ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
+ "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
+ "ActionDispatch::Http::Parameters::ParseError" => :bad_request,
+ "ActionController::BadRequest" => :bad_request,
+ "ActionController::ParameterMissing" => :bad_request,
+ "Rack::QueryParser::ParameterTypeError" => :bad_request,
+ "Rack::QueryParser::InvalidParameterError" => :bad_request
)
- cattr_accessor :rescue_templates
- @@rescue_templates = Hash.new('diagnostics')
- @@rescue_templates.merge!(
- 'ActionView::MissingTemplate' => 'missing_template',
- 'ActionController::RoutingError' => 'routing_error',
- 'AbstractController::ActionNotFound' => 'unknown_action',
- 'ActionView::Template::Error' => 'template_error'
+ cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!(
+ "ActionView::MissingTemplate" => "missing_template",
+ "ActionController::RoutingError" => "routing_error",
+ "AbstractController::ActionNotFound" => "unknown_action",
+ "ActiveRecord::StatementInvalid" => "invalid_statement",
+ "ActionView::Template::Error" => "template_error"
)
attr_reader :backtrace_cleaner, :exception, :line_number, :file
@@ -100,49 +99,49 @@ module ActionDispatch
private
- def backtrace
- Array(@exception.backtrace)
- end
+ def backtrace
+ Array(@exception.backtrace)
+ end
- def original_exception(exception)
- if @@rescue_responses.has_key?(exception.cause.class.name)
- exception.cause
- else
- exception
+ def original_exception(exception)
+ if @@rescue_responses.has_key?(exception.cause.class.name)
+ exception.cause
+ else
+ exception
+ end
end
- end
- def clean_backtrace(*args)
- if backtrace_cleaner
- backtrace_cleaner.clean(backtrace, *args)
- else
- backtrace
+ def clean_backtrace(*args)
+ if backtrace_cleaner
+ backtrace_cleaner.clean(backtrace, *args)
+ else
+ backtrace
+ end
end
- end
- def source_fragment(path, line)
- return unless Rails.respond_to?(:root) && Rails.root
- full_path = Rails.root.join(path)
- if File.exist?(full_path)
- File.open(full_path, "r") do |file|
- start = [line - 3, 0].max
- lines = file.each_line.drop(start).take(6)
- Hash[*(start+1..(lines.count+start)).zip(lines).flatten]
+ def source_fragment(path, line)
+ return unless Rails.respond_to?(:root) && Rails.root
+ full_path = Rails.root.join(path)
+ if File.exist?(full_path)
+ File.open(full_path, "r") do |file|
+ start = [line - 3, 0].max
+ lines = file.each_line.drop(start).take(6)
+ Hash[*(start + 1..(lines.count + start)).zip(lines).flatten]
+ end
end
end
- end
- def extract_file_and_line_number(trace)
- # Split by the first colon followed by some digits, which works for both
- # Windows and Unix path styles.
- file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
- [file, line.to_i]
- end
+ def extract_file_and_line_number(trace)
+ # Split by the first colon followed by some digits, which works for both
+ # Windows and Unix path styles.
+ file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
+ [file, line.to_i]
+ end
- def expand_backtrace
- @exception.backtrace.unshift(
- @exception.to_s.split("\n")
- ).flatten!
- end
+ def expand_backtrace
+ @exception.backtrace.unshift(
+ @exception.to_s.split("\n")
+ ).flatten!
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/executor.rb b/actionpack/lib/action_dispatch/middleware/executor.rb
index 06245b403b..129b18d3d9 100644
--- a/actionpack/lib/action_dispatch/middleware/executor.rb
+++ b/actionpack/lib/action_dispatch/middleware/executor.rb
@@ -1,4 +1,6 @@
-require 'rack/body_proxy'
+# frozen_string_literal: true
+
+require "rack/body_proxy"
module ActionDispatch
class Executor
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 06038af571..3e11846778 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,4 +1,6 @@
-require 'active_support/core_ext/hash/keys'
+# frozen_string_literal: true
+
+require "active_support/core_ext/hash/keys"
module ActionDispatch
# The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
@@ -36,7 +38,7 @@ module ActionDispatch
#
# See docs on the FlashHash class for more details about the flash.
class Flash
- KEY = 'action_dispatch.request.flash_hash'.freeze
+ KEY = "action_dispatch.request.flash_hash".freeze
module RequestMethods
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
@@ -60,14 +62,14 @@ module ActionDispatch
session = self.session || {}
flash_hash = self.flash_hash
- if flash_hash && (flash_hash.present? || session.key?('flash'))
+ if flash_hash && (flash_hash.present? || session.key?("flash"))
session["flash"] = flash_hash.to_session_value
self.flash = flash_hash.dup
end
- if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
- session.key?('flash') && session['flash'].nil?
- session.delete('flash')
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded?
+ session.key?("flash") && session["flash"].nil?
+ session.delete("flash")
end
end
@@ -118,8 +120,8 @@ module ActionDispatch
end
new(flashes, flashes.keys)
when Hash # Rails 4.0
- flashes = value['flashes']
- if discard = value['discard']
+ flashes = value["flashes"]
+ if discard = value["discard"]
flashes.except!(*discard)
end
new(flashes, flashes.keys)
@@ -129,11 +131,11 @@ module ActionDispatch
end
# Builds a hash containing the flashes to keep for the next request.
- # If there are none to keep, returns nil.
+ # If there are none to keep, returns +nil+.
def to_session_value #:nodoc:
flashes_to_keep = @flashes.except(*@discard)
return nil if flashes_to_keep.empty?
- {'flashes' => flashes_to_keep}
+ { "discard" => [], "flashes" => flashes_to_keep }
end
def initialize(flashes = {}, discard = []) #:nodoc:
@@ -277,15 +279,16 @@ module ActionDispatch
end
protected
- def now_is_loaded?
- @now
- end
+ def now_is_loaded?
+ @now
+ end
- def stringify_array(array)
- array.map do |item|
- item.kind_of?(Symbol) ? item.to_s : item
+ private
+ def stringify_array(array) # :doc:
+ array.map do |item|
+ item.kind_of?(Symbol) ? item.to_s : item
+ end
end
- end
end
def self.new(app) app; end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
deleted file mode 100644
index faf3262b8f..0000000000
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'action_dispatch/http/request'
-
-module ActionDispatch
- # ActionDispatch::ParamsParser works for all the requests having any Content-Length
- # (like POST). It takes raw data from the request and puts it through the parser
- # that is picked based on Content-Type header.
- #
- # In case of any error while parsing data ParamsParser::ParseError is raised.
- class ParamsParser
- # Raised when raw data from the request cannot be parsed by the parser
- # defined for request's content mime type.
- class ParseError < StandardError
-
- def initialize(message = nil, original_exception = nil)
- if message
- ActiveSupport::Deprecation.warn("Passing #message is deprecated and has no effect. " \
- "#{self.class} will automatically capture the message " \
- "of the original exception.", caller)
- end
-
- if original_exception
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
- super($!.message)
- end
-
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
- end
-
- # Create a new +ParamsParser+ middleware instance.
- #
- # The +parsers+ argument can take Hash of parsers where key is identifying
- # content mime type, and value is a lambda that is going to process data.
- def self.new(app, parsers = {})
- ActiveSupport::Deprecation.warn('ActionDispatch::ParamsParser is deprecated and will be removed in Rails 5.1. Configure the parameter parsing in ActionDispatch::Request.parameter_parsers.')
- parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key }
- ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers)
- app
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 0f27984550..3feb3a19f3 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -1,11 +1,13 @@
+# frozen_string_literal: true
+
module ActionDispatch
# When called, this middleware renders an error page. By default if an HTML
- # response is expected it will render static error pages from the `/public`
+ # response is expected it will render static error pages from the <tt>/public</tt>
# directory. For example when this middleware receives a 500 response it will
- # render the template found in `/public/500.html`.
+ # render the template found in <tt>/public/500.html</tt>.
# If an internationalized locale is set, this middleware will attempt to render
- # the template in `/public/500.<locale>.html`. If an internationalized template
- # is not found it will fall back on `/public/500.html`.
+ # the template in <tt>/public/500.<locale>.html</tt>. If an internationalized template
+ # is not found it will fall back on <tt>/public/500.html</tt>.
#
# When a request with a content type other than HTML is made, this middleware
# will attempt to convert error information into the appropriate response type.
@@ -20,36 +22,36 @@ module ActionDispatch
request = ActionDispatch::Request.new(env)
status = request.path_info[1..-1].to_i
content_type = request.formats.first
- body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
+ body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
render(status, content_type, body)
end
private
- def render(status, content_type, body)
- format = "to_#{content_type.to_sym}" if content_type
- if format && body.respond_to?(format)
- render_format(status, content_type, body.public_send(format))
- else
- render_html(status)
+ def render(status, content_type, body)
+ format = "to_#{content_type.to_sym}" if content_type
+ if format && body.respond_to?(format)
+ render_format(status, content_type, body.public_send(format))
+ else
+ render_html(status)
+ end
end
- end
- def render_format(status, content_type, body)
- [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
- 'Content-Length' => body.bytesize.to_s}, [body]]
- end
+ def render_format(status, content_type, body)
+ [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
+ "Content-Length" => body.bytesize.to_s }, [body]]
+ end
- def render_html(status)
- path = "#{public_path}/#{status}.#{I18n.locale}.html"
- path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
+ def render_html(status)
+ path = "#{public_path}/#{status}.#{I18n.locale}.html"
+ path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
- if found || File.exist?(path)
- render_format(status, 'text/html', File.read(path))
- else
- [404, { "X-Cascade" => "pass" }, []]
+ if found || File.exist?(path)
+ render_format(status, "text/html", File.read(path))
+ else
+ [404, { "X-Cascade" => "pass" }, []]
+ end
end
- end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 112bde6596..8bb3ba7504 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -1,54 +1,12 @@
+# frozen_string_literal: true
+
module ActionDispatch
- # ActionDispatch::Reloader provides prepare and cleanup callbacks,
- # intended to assist with code reloading during development.
- #
- # Prepare callbacks are run before each request, and cleanup callbacks
- # after each request. In this respect they are analogs of ActionDispatch::Callback's
- # before and after callbacks. However, cleanup callbacks are not called until the
- # request is fully complete -- that is, after #close has been called on
- # the response body. This is important for streaming responses such as the
- # following:
- #
- # self.response_body = -> (response, output) do
- # # code here which refers to application models
- # end
- #
- # Cleanup callbacks will not be called until after the response_body lambda
- # is evaluated, ensuring that it can refer to application models and other
- # classes before they are unloaded.
+ # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
+ # callbacks, intended to assist with code reloading during development.
#
# By default, ActionDispatch::Reloader is included in the middleware stack
# only in the development environment; specifically, when +config.cache_classes+
- # is false. Callbacks may be registered even when it is not included in the
- # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
- # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
- #
+ # is false.
class Reloader < Executor
- def self.to_prepare(*args, &block)
- ActiveSupport::Reloader.to_prepare(*args, &block)
- end
-
- def self.to_cleanup(*args, &block)
- ActiveSupport::Reloader.to_complete(*args, &block)
- end
-
- def self.prepare!
- default_reloader.prepare!
- end
-
- def self.cleanup!
- default_reloader.reload!
- end
-
- class << self
- attr_accessor :default_reloader # :nodoc:
-
- deprecate to_prepare: 'use ActiveSupport::Reloader.to_prepare instead',
- to_cleanup: 'use ActiveSupport::Reloader.to_complete instead',
- prepare!: 'use Rails.application.reloader.prepare! instead',
- cleanup!: 'use Rails.application.reloader.reload! instead of cleanup + prepare'
- end
-
- self.default_reloader = ActiveSupport::Reloader
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 31b75498b6..35158f9062 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -1,4 +1,6 @@
-require 'ipaddr'
+# frozen_string_literal: true
+
+require "ipaddr"
module ActionDispatch
# This middleware calculates the IP address of the remote client that is
@@ -10,7 +12,7 @@ module ActionDispatch
# by @gingerlime. A more detailed explanation of the algorithm is given
# at GetIp#calculate_ip.
#
- # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
+ # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
# requires. Some Rack servers simply drop preceding headers, and only report
# the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
# If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
@@ -29,7 +31,7 @@ module ActionDispatch
# The default trusted IPs list simply includes IP addresses that are
# guaranteed by the IP specification to be private addresses. Those will
# not be the ultimate client IP in production, and so are discarded. See
- # http://en.wikipedia.org/wiki/Private_network for details.
+ # https://en.wikipedia.org/wiki/Private_network for details.
TRUSTED_PROXIES = [
"127.0.0.1", # localhost IPv4
"::1", # localhost IPv6
@@ -131,8 +133,8 @@ module ActionDispatch
should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user
- raise IpSpoofAttackError, "IP spoofing attack?! " +
- "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " +
+ raise IpSpoofAttackError, "IP spoofing attack?! " \
+ "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
@@ -153,17 +155,17 @@ module ActionDispatch
@ip ||= calculate_ip
end
- protected
+ private
- def ips_from(header)
+ def ips_from(header) # :doc:
return [] unless header
- # Split the comma-separated list into an array of strings
+ # Split the comma-separated list into an array of strings.
ips = header.strip.split(/[,\s]+/)
ips.select do |ip|
begin
- # Only return IPs that are valid according to the IPAddr#new method
+ # Only return IPs that are valid according to the IPAddr#new method.
range = IPAddr.new(ip).to_range
- # we want to make sure nobody is sneaking a netmask in
+ # We want to make sure nobody is sneaking a netmask in.
range.begin == range.end
rescue ArgumentError
nil
@@ -171,13 +173,11 @@ module ActionDispatch
end
end
- def filter_proxies(ips)
+ def filter_proxies(ips) # :doc:
ips.reject do |ip|
@proxies.any? { |proxy| proxy === ip }
end
end
-
end
-
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 1555ff72af..805d3f2148 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -1,9 +1,12 @@
-require 'securerandom'
-require 'active_support/core_ext/string/access'
+# frozen_string_literal: true
+
+require "securerandom"
+require "active_support/core_ext/string/access"
module ActionDispatch
- # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
- # ActionDispatch::Request#uuid or the alias ActionDispatch::Request#request_id) and sends the same id to the client via the X-Request-Id header.
+ # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible
+ # through <tt>ActionDispatch::Request#request_id</tt> or the alias <tt>ActionDispatch::Request#uuid</tt>) and sends
+ # the same id to the client via the X-Request-Id header.
#
# The unique request id is either based on the X-Request-Id header in the request, which would typically be generated
# by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
@@ -12,7 +15,7 @@ module ActionDispatch
# The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
# from multiple pieces of the stack.
class RequestId
- X_REQUEST_ID = "X-Request-Id".freeze # :nodoc:
+ X_REQUEST_ID = "X-Request-Id".freeze #:nodoc:
def initialize(app)
@app = app
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 5fb5953811..5b0be96223 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -1,34 +1,25 @@
-require 'rack/utils'
-require 'rack/request'
-require 'rack/session/abstract/id'
-require 'action_dispatch/middleware/cookies'
-require 'action_dispatch/request/session'
+# frozen_string_literal: true
+
+require "rack/utils"
+require "rack/request"
+require "rack/session/abstract/id"
+require "action_dispatch/middleware/cookies"
+require "action_dispatch/request/session"
module ActionDispatch
module Session
class SessionRestoreError < StandardError #:nodoc:
-
- def initialize(const_error = nil)
- if const_error
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
- super("Session contains objects whose class definition isn't available.\n" +
- "Remember to require the classes for all objects kept in the session.\n" +
+ def initialize
+ super("Session contains objects whose class definition isn't available.\n" \
+ "Remember to require the classes for all objects kept in the session.\n" \
"(Original exception: #{$!.message} [#{$!.class}])\n")
set_backtrace $!.backtrace
end
-
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
end
module Compatibility
def initialize(app, options = {})
- options[:key] ||= '_session_id'
+ options[:key] ||= "_session_id"
super
end
@@ -38,14 +29,13 @@ module ActionDispatch
sid
end
- protected
+ private
- def initialize_sid
+ def initialize_sid # :doc:
@default_options.delete(:sidbits)
@default_options.delete(:secure_random)
end
- private
def make_request(env)
ActionDispatch::Request.new env
end
@@ -65,7 +55,7 @@ module ActionDispatch
rescue ArgumentError => argument_error
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
begin
- # Note that the regexp does not allow $1 to end with a ':'
+ # Note that the regexp does not allow $1 to end with a ':'.
$1.constantize
rescue LoadError, NameError
raise ActionDispatch::Session::SessionRestoreError
@@ -94,9 +84,9 @@ module ActionDispatch
private
- def set_cookie(request, session_id, cookie)
- request.cookie_jar[key] = cookie
- end
+ def set_cookie(request, session_id, cookie)
+ request.cookie_jar[key] = cookie
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index 589ae46e38..a6d965a644 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -1,4 +1,6 @@
-require 'action_dispatch/middleware/session/abstract_store'
+# frozen_string_literal: true
+
+require "action_dispatch/middleware/session/abstract_store"
module ActionDispatch
module Session
@@ -19,7 +21,7 @@ module ActionDispatch
# Get a session from the cache.
def find_session(env, sid)
- unless sid and session = @cache.read(cache_key(sid))
+ unless sid && (session = @cache.read(cache_key(sid)))
sid, session = generate_sid, {}
end
[sid, session]
@@ -29,7 +31,7 @@ module ActionDispatch
def write_session(env, sid, session, options)
key = cache_key(sid)
if session
- @cache.write(key, session, :expires_in => options[:expire_after])
+ @cache.write(key, session, expires_in: options[:expire_after])
else
@cache.delete(key)
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index dec9c60ef2..4ea96196d3 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -1,6 +1,8 @@
-require 'active_support/core_ext/hash/keys'
-require 'action_dispatch/middleware/session/abstract_store'
-require 'rack/session/cookie'
+# frozen_string_literal: true
+
+require "active_support/core_ext/hash/keys"
+require "action_dispatch/middleware/session/abstract_store"
+require "rack/session/cookie"
module ActionDispatch
module Session
@@ -19,39 +21,25 @@ module ActionDispatch
# knowing your app's secret key, but can easily read their +user_id+. This
# was the default for Rails 3 apps.
#
- # If you have secret_key_base set, your cookies will be encrypted. This
+ # Your cookies will be encrypted using your apps secret_key_base. This
# goes a step further than signed cookies in that encrypted cookies cannot
# be altered or read by users. This is the default starting in Rails 4.
#
- # If you have both secret_token and secret_key_base set, your cookies will
- # be encrypted, and signed cookies generated by Rails 3 will be
- # transparently read and encrypted to provide a smooth upgrade path.
- #
- # Configure your session store in config/initializers/session_store.rb:
+ # Configure your session store in <tt>config/initializers/session_store.rb</tt>:
#
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
#
- # Configure your secret key in config/secrets.yml:
- #
- # development:
- # secret_key_base: 'secret key'
+ # By default, your secret key base is derived from your application name in
+ # the test and development environments. In all other environments, it is stored
+ # encrypted in the <tt>config/credentials.yml.enc</tt> file.
#
- # To generate a secret key for an existing application, run `rails secret`.
+ # If your application was not updated to Rails 5.2 defaults, the secret_key_base
+ # will be found in the old <tt>config/secrets.yml</tt> file.
#
- # If you are upgrading an existing Rails 3 app, you should leave your
- # existing secret_token in place and simply add the new secret_key_base.
- # Note that you should wait to set secret_key_base until you have 100% of
- # your userbase on Rails 4 and are reasonably sure you will not need to
- # rollback to Rails 3. This is because cookies signed based on the new
- # secret_key_base in Rails 4 are not backwards compatible with Rails 3.
- # You are free to leave your existing secret_token in place, not set the
- # new secret_key_base, and ignore the deprecation warnings until you are
- # reasonably sure that your upgrade is otherwise complete. Additionally,
- # you should take care to make sure you are not relying on the ability to
- # decode signed cookies generated by your app in external applications or
- # JavaScript before upgrading.
- #
- # Note that changing the secret key will invalidate all existing sessions!
+ # Note that changing your secret_key_base will invalidate all existing session.
+ # Additionally, you should take care to make sure you are not relying on the
+ # ability to decode signed cookies generated by your app in external
+ # applications or JavaScript before changing it.
#
# Because CookieStore extends Rack::Session::Abstract::Persisted, many of the
# options described there can be used to customize the session cookie that
@@ -63,8 +51,8 @@ module ActionDispatch
# Other useful options include <tt>:key</tt>, <tt>:secure</tt> and
# <tt>:httponly</tt>.
class CookieStore < AbstractStore
- def initialize(app, options={})
- super(app, options.merge!(:cookie_only => true))
+ def initialize(app, options = {})
+ super(app, options.merge!(cookie_only: true))
end
def delete_session(req, session_id, options)
@@ -84,46 +72,46 @@ module ActionDispatch
private
- def extract_session_id(req)
- stale_session_check! do
- unpacked_cookie_data(req)["session_id"]
+ def extract_session_id(req)
+ stale_session_check! do
+ unpacked_cookie_data(req)["session_id"]
+ end
end
- end
- def unpacked_cookie_data(req)
- req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
- v = stale_session_check! do
- if data = get_cookie(req)
- data.stringify_keys!
+ def unpacked_cookie_data(req)
+ req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
+ v = stale_session_check! do
+ if data = get_cookie(req)
+ data.stringify_keys!
+ end
+ data || {}
end
- data || {}
+ req.set_header k, v
end
- req.set_header k, v
end
- end
- def persistent_session_id!(data, sid=nil)
- data ||= {}
- data["session_id"] ||= sid || generate_sid
- data
- end
+ def persistent_session_id!(data, sid = nil)
+ data ||= {}
+ data["session_id"] ||= sid || generate_sid
+ data
+ end
- def write_session(req, sid, session_data, options)
- session_data["session_id"] = sid
- session_data
- end
+ def write_session(req, sid, session_data, options)
+ session_data["session_id"] = sid
+ session_data
+ end
- def set_cookie(request, session_id, cookie)
- cookie_jar(request)[@key] = cookie
- end
+ def set_cookie(request, session_id, cookie)
+ cookie_jar(request)[@key] = cookie
+ end
- def get_cookie(req)
- cookie_jar(req)[@key]
- end
+ def get_cookie(req)
+ cookie_jar(req)[@key]
+ end
- def cookie_jar(request)
- request.cookie_jar.signed_or_encrypted
- end
+ def cookie_jar(request)
+ request.cookie_jar.signed_or_encrypted
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index cb19786f0b..914df3a2b1 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -1,6 +1,8 @@
-require 'action_dispatch/middleware/session/abstract_store'
+# frozen_string_literal: true
+
+require "action_dispatch/middleware/session/abstract_store"
begin
- require 'rack/session/dalli'
+ require "rack/session/dalli"
rescue LoadError => e
$stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
raise e
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 64695f9738..3c88afd4d3 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,5 +1,7 @@
-require 'action_dispatch/http/request'
-require 'action_dispatch/middleware/exception_wrapper'
+# frozen_string_literal: true
+
+require "action_dispatch/http/request"
+require "action_dispatch/middleware/exception_wrapper"
module ActionDispatch
# This middleware rescues any exception returned by the application
@@ -8,14 +10,14 @@ module ActionDispatch
# The exceptions app should be passed as parameter on initialization
# of ShowExceptions. Every time there is an exception, ShowExceptions will
# store the exception in env["action_dispatch.exception"], rewrite the
- # PATH_INFO to the exception status code and call the rack app.
+ # PATH_INFO to the exception status code and call the Rack app.
#
# If the application returns a "X-Cascade" pass response, this middleware
# will send an empty response as result with the correct status code.
# If any exception happens inside the exceptions app, this middleware
# catches the exceptions and returns a FAILSAFE_RESPONSE.
class ShowExceptions
- FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' },
+ FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" },
["500 Internal Server Error\n" \
"If you are the administrator of this website, then please read this web " \
"application's log file and/or the web server's log file to find out what " \
@@ -39,22 +41,22 @@ module ActionDispatch
private
- def render_exception(request, exception)
- backtrace_cleaner = request.get_header 'action_dispatch.backtrace_cleaner'
- wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
- status = wrapper.status_code
- request.set_header "action_dispatch.exception", wrapper.exception
- request.set_header "action_dispatch.original_path", request.path_info
- request.path_info = "/#{status}"
- response = @exceptions_app.call(request.env)
- response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
- rescue Exception => failsafe_error
- $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
- FAILSAFE_RESPONSE
- end
+ def render_exception(request, exception)
+ backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner"
+ wrapper = ExceptionWrapper.new(backtrace_cleaner, exception)
+ status = wrapper.status_code
+ request.set_header "action_dispatch.exception", wrapper.exception
+ request.set_header "action_dispatch.original_path", request.path_info
+ request.path_info = "/#{status}"
+ response = @exceptions_app.call(request.env)
+ response[1]["X-Cascade"] == "pass" ? pass_response(status) : response
+ rescue Exception => failsafe_error
+ $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
+ FAILSAFE_RESPONSE
+ end
- def pass_response(status)
- [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []]
- end
+ def pass_response(status)
+ [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []]
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index ab3077b308..ef633aadc6 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -1,78 +1,68 @@
+# frozen_string_literal: true
+
module ActionDispatch
- # This middleware is added to the stack when `config.force_ssl = true`, and is passed
- # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP
+ # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
+ # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
# requests:
#
- # 1. TLS redirect: Permanently redirects http:// requests to https://
- # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
- # to modify the destination URL
- # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
- # `redirect: false` to disable this feature.
+ # 1. <b>TLS redirect</b>: Permanently redirects +http://+ requests to +https://+
+ # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+
+ # to modify the destination URL
+ # (e.g. <tt>redirect: { host: "secure.widgets.com", port: 8080 }</tt>), or set
+ # <tt>redirect: false</tt> to disable this feature.
+ #
+ # Requests can opt-out of redirection with +exclude+:
+ #
+ # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
+ #
+ # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they
+ # must not be sent along with +http://+ requests. Enabled by default. Set
+ # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature.
#
- # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
- # mustn't be sent along with http:// requests. Enabled by default. Set
- # `config.ssl_options` with `secure_cookies: false` to disable this feature.
+ # 3. <b>HTTP Strict Transport Security (HSTS)</b>: Tells the browser to remember
+ # this site as TLS-only and automatically redirect non-TLS requests.
+ # Enabled by default. Configure +config.ssl_options+ with <tt>hsts: false</tt> to disable.
#
- # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
- # this site as TLS-only and automatically redirect non-TLS requests.
- # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
+ # Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS:
#
- # Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
- # * `expires`: How long, in seconds, these settings will stick. Defaults to
- # `180.days` (recommended). The minimum required to qualify for browser
- # preload lists is `18.weeks`.
- # * `subdomains`: Set to `true` to tell the browser to apply these settings
- # to all subdomains. This protects your cookies from interception by a
- # vulnerable site on a subdomain. Defaults to `true`.
- # * `preload`: Advertise that this site may be included in browsers'
- # preloaded HSTS lists. HSTS protects your site on every visit *except the
- # first visit* since it hasn't seen your HSTS header yet. To close this
- # gap, browser vendors include a baked-in list of HSTS-enabled sites.
- # Go to https://hstspreload.appspot.com to submit your site for inclusion.
+ # * +expires+: How long, in seconds, these settings will stick. The minimum
+ # required to qualify for browser preload lists is 18 weeks. Defaults to
+ # 180 days (recommended).
#
- # To turn off HSTS, omitting the header is not enough. Browsers will remember the
- # original HSTS directive until it expires. Instead, use the header to tell browsers to
- # expire HSTS immediately. Setting `hsts: false` is a shortcut for
- # `hsts: { expires: 0 }`.
+ # * +subdomains+: Set to +true+ to tell the browser to apply these settings
+ # to all subdomains. This protects your cookies from interception by a
+ # vulnerable site on a subdomain. Defaults to +true+.
#
- # Requests can opt-out of redirection with `exclude`:
+ # * +preload+: Advertise that this site may be included in browsers'
+ # preloaded HSTS lists. HSTS protects your site on every visit <i>except the
+ # first visit</i> since it hasn't seen your HSTS header yet. To close this
+ # gap, browser vendors include a baked-in list of HSTS-enabled sites.
+ # Go to https://hstspreload.org to submit your site for inclusion.
+ # Defaults to +false+.
#
- # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
+ # To turn off HSTS, omitting the header is not enough. Browsers will remember the
+ # original HSTS directive until it expires. Instead, use the header to tell browsers to
+ # expire HSTS immediately. Setting <tt>hsts: false</tt> is a shortcut for
+ # <tt>hsts: { expires: 0 }</tt>.
class SSL
+ # :stopdoc:
+
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
# and greater than the 18-week requirement for browser preload lists.
HSTS_EXPIRES_IN = 15552000
def self.default_hsts_options
- { expires: HSTS_EXPIRES_IN, subdomains: false, preload: false }
+ { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false }
end
- def initialize(app, redirect: {}, hsts: {}, secure_cookies: true, **options)
+ def initialize(app, redirect: {}, hsts: {}, secure_cookies: true)
@app = app
- if options[:host] || options[:port]
- ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
- The `:host` and `:port` options are moving within `:redirect`:
- `config.ssl_options = { redirect: { host: …, port: … } }`.
- end_warning
- @redirect = options.slice(:host, :port)
- else
- @redirect = redirect
- end
+ @redirect = redirect
@exclude = @redirect && @redirect[:exclude] || proc { !@redirect }
@secure_cookies = secure_cookies
- if hsts != true && hsts != false && hsts[:subdomains].nil?
- hsts[:subdomains] = false
-
- ActiveSupport::Deprecation.warn <<-end_warning.strip_heredoc
- In Rails 5.1, The `:subdomains` option of HSTS config will be treated as true if
- unspecified. Set `config.ssl_options = { hsts: { subdomains: false } }` to opt out
- of this behavior.
- end_warning
- end
-
@hsts_header = build_hsts_header(normalize_hsts_options(hsts))
end
@@ -92,7 +82,7 @@ module ActionDispatch
private
def set_hsts_header!(headers)
- headers['Strict-Transport-Security'.freeze] ||= @hsts_header
+ headers["Strict-Transport-Security".freeze] ||= @hsts_header
end
def normalize_hsts_options(options)
@@ -109,19 +99,19 @@ module ActionDispatch
end
end
- # http://tools.ietf.org/html/rfc6797#section-6.1
+ # https://tools.ietf.org/html/rfc6797#section-6.1
def build_hsts_header(hsts)
- value = "max-age=#{hsts[:expires].to_i}"
+ value = "max-age=#{hsts[:expires].to_i}".dup
value << "; includeSubDomains" if hsts[:subdomains]
value << "; preload" if hsts[:preload]
value
end
def flag_cookies_as_secure!(headers)
- if cookies = headers['Set-Cookie'.freeze]
+ if cookies = headers["Set-Cookie".freeze]
cookies = cookies.split("\n".freeze)
- headers['Set-Cookie'.freeze] = cookies.map { |cookie|
+ headers["Set-Cookie".freeze] = cookies.map { |cookie|
if cookie !~ /;\s*secure\s*(;|$)/i
"#{cookie}; secure"
else
@@ -132,17 +122,25 @@ module ActionDispatch
end
def redirect_to_https(request)
- [ @redirect.fetch(:status, 301),
- { 'Content-Type' => 'text/html',
- 'Location' => https_location_for(request) },
+ [ @redirect.fetch(:status, redirection_status(request)),
+ { "Content-Type" => "text/html",
+ "Location" => https_location_for(request) },
@redirect.fetch(:body, []) ]
end
+ def redirection_status(request)
+ if request.get? || request.head?
+ 301 # Issue a permanent redirect via a GET request.
+ else
+ 307 # Issue a fresh request redirect to preserve the HTTP method.
+ end
+ end
+
def https_location_for(request)
host = @redirect[:host] || request.host
port = @redirect[:port] || request.port
- location = "https://#{host}"
+ location = "https://#{host}".dup
location << ":#{port}" if port != 80 && port != 443
location << request.fullpath
location
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 0b4bee5462..b82f8aa3a3 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "active_support/inflector/methods"
require "active_support/dependencies"
@@ -88,7 +90,6 @@ module ActionDispatch
end
def delete(target)
- target = get_class target
middlewares.delete_if { |m| m.klass == target }
end
@@ -102,32 +103,14 @@ module ActionDispatch
private
- def assert_index(index, where)
- index = get_class index
- i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index }
- raise "No such middleware to insert #{where}: #{index.inspect}" unless i
- i
- end
-
- def get_class(klass)
- if klass.is_a?(String) || klass.is_a?(Symbol)
- classcache = ActiveSupport::Dependencies::Reference
- converted_klass = classcache[klass.to_s]
- ActiveSupport::Deprecation.warn <<-eowarn
-Passing strings or symbols to the middleware builder is deprecated, please change
-them to actual class references. For example:
-
- "#{klass}" => #{converted_klass}
-
- eowarn
- converted_klass
- else
- klass
+ def assert_index(index, where)
+ i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index }
+ raise "No such middleware to insert #{where}: #{index.inspect}" unless i
+ i
end
- end
- def build_middleware(klass, args, block)
- Middleware.new(get_class(klass), args, block)
- end
+ def build_middleware(klass, args, block)
+ Middleware.new(klass, args, block)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 41c220236a..23492e14eb 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -1,20 +1,22 @@
-require 'rack/utils'
-require 'active_support/core_ext/uri'
+# frozen_string_literal: true
+
+require "rack/utils"
+require "active_support/core_ext/uri"
module ActionDispatch
# This middleware returns a file's contents from disk in the body response.
# When initialized, it can accept optional HTTP headers, which will be set
# when a response containing a file's contents is delivered.
#
- # This middleware will render the file specified in `env["PATH_INFO"]`
+ # This middleware will render the file specified in <tt>env["PATH_INFO"]</tt>
# where the base path is in the +root+ directory. For example, if the +root+
- # is set to `public/`, then a request with `env["PATH_INFO"]` of
- # `assets/application.js` will return a response with the contents of a file
- # located at `public/assets/application.js` if the file exists. If the file
+ # is set to +public/+, then a request with <tt>env["PATH_INFO"]</tt> of
+ # +assets/application.js+ will return a response with the contents of a file
+ # located at +public/assets/application.js+ if the file exists. If the file
# does not exist, a 404 "File not Found" response will be returned.
class FileHandler
- def initialize(root, index: 'index', headers: {})
- @root = root.chomp('/')
+ def initialize(root, index: "index", headers: {})
+ @root = root.chomp("/")
@file_server = ::Rack::File.new(@root, headers)
@index = index
end
@@ -23,17 +25,17 @@ module ActionDispatch
# correct read permissions, the return value is a URI-escaped string
# representing the filename. Otherwise, false is returned.
#
- # Used by the `Static` class to check the existence of a valid file
- # in the server's `public/` directory (see Static#call).
+ # Used by the +Static+ class to check the existence of a valid file
+ # in the server's +public/+ directory (see Static#call).
def match?(path)
path = ::Rack::Utils.unescape_path path
- return false unless valid_path?(path)
- path = Rack::Utils.clean_path_info path
+ return false unless ::Rack::Utils.valid_path? path
+ path = ::Rack::Utils.clean_path_info path
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p|
- path = File.join(@root, p.force_encoding('UTF-8'.freeze))
+ path = File.join(@root, p.dup.force_encoding(Encoding::UTF_8))
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
@@ -46,7 +48,7 @@ module ActionDispatch
end
def call(env)
- serve ActionDispatch::Request.new env
+ serve(Rack::Request.new(env))
end
def serve(request)
@@ -59,13 +61,13 @@ module ActionDispatch
if status == 304
return [status, headers, body]
end
- headers['Content-Encoding'] = 'gzip'
- headers['Content-Type'] = content_type(path)
+ headers["Content-Encoding"] = "gzip"
+ headers["Content-Type"] = content_type(path)
else
status, headers, body = @file_server.call(request.env)
end
- headers['Vary'] = 'Accept-Encoding' if gzip_path
+ headers["Vary"] = "Accept-Encoding" if gzip_path
return [status, headers, body]
ensure
@@ -78,11 +80,11 @@ module ActionDispatch
end
def content_type(path)
- ::Rack::Mime.mime_type(::File.extname(path), 'text/plain'.freeze)
+ ::Rack::Mime.mime_type(::File.extname(path), "text/plain".freeze)
end
def gzip_encoding_accepted?(request)
- request.accept_encoding =~ /\bgzip\b/i
+ request.accept_encoding.any? { |enc, quality| enc =~ /\bgzip\b/i }
end
def gzip_file_path(path)
@@ -94,39 +96,28 @@ module ActionDispatch
false
end
end
-
- def valid_path?(path)
- path.valid_encoding? && !path.include?("\0")
- end
end
# This middleware will attempt to return the contents of a file's body from
# disk in the response. If a file is not found on disk, the request will be
# delegated to the application stack. This middleware is commonly initialized
- # to serve assets from a server's `public/` directory.
+ # to serve assets from a server's +public/+ directory.
#
# This middleware verifies the path to ensure that only files
# living in the root directory can be rendered. A request cannot
# produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
# requests will result in a file being returned.
class Static
- def initialize(app, path, deprecated_cache_control = :not_set, index: 'index', headers: {})
- if deprecated_cache_control != :not_set
- ActiveSupport::Deprecation.warn("The `cache_control` argument is deprecated," \
- "replaced by `headers: { 'Cache-Control' => #{deprecated_cache_control} }`, " \
- " and will be removed in Rails 5.1.")
- headers['Cache-Control'.freeze] = deprecated_cache_control
- end
-
+ def initialize(app, path, index: "index", headers: {})
@app = app
@file_handler = FileHandler.new(path, index: index, headers: headers)
end
def call(env)
- req = ActionDispatch::Request.new env
+ req = Rack::Request.new env
if req.get? || req.head?
- path = req.path_info.chomp('/'.freeze)
+ path = req.path_info.chomp("/".freeze)
if match = @file_handler.match?(path)
req.path_info = match
return @file_handler.serve(req)
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
new file mode 100644
index 0000000000..e1b129ccc5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
@@ -0,0 +1,21 @@
+<header>
+ <h1>
+ <%= @exception.class.to_s %>
+ <% if @request.parameters['controller'] %>
+ in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
+ <% end %>
+ </h1>
+</header>
+
+<div id="container">
+ <h2>
+ <%= h @exception.message %>
+ <% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
+ <br />To resolve this issue run: bin/rails active_storage:install
+ <% end %>
+ </h2>
+
+ <%= render template: "rescues/_source" %>
+ <%= render template: "rescues/_trace" %>
+ <%= render template: "rescues/_request_and_response" %>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb
new file mode 100644
index 0000000000..033518cf8a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb
@@ -0,0 +1,13 @@
+<%= @exception.class.to_s %><%
+ if @request.parameters['controller']
+%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
+<% end %>
+
+<%= @exception.message %>
+<% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
+To resolve this issue run: bin/rails active_storage:install
+<% end %>
+
+<%= render template: "rescues/_source" %>
+<%= render template: "rescues/_trace" %>
+<%= render template: "rescues/_request_and_response" %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index e0509f56f4..39ea25bdfc 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -106,6 +106,7 @@
.line {
padding-left: 10px;
+ white-space: pre;
}
.line:hover {
diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
index 429ea7057c..1fa0691303 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb
@@ -17,6 +17,10 @@
line-height: 15px;
}
+ #route_table thead tr.bottom th input#search {
+ -webkit-appearance: textfield;
+ }
+
#route_table tbody tr {
border-bottom: 1px solid #ddd;
}
@@ -60,7 +64,7 @@
<%= link_to "Path", "#", 'data-route-helper' => '_path',
title: "Returns a relative path (without the http or domain)" %> /
<%= link_to "Url", "#", 'data-route-helper' => '_url',
- title: "Returns an absolute url (with the http and domain)" %>
+ title: "Returns an absolute URL (with the http and domain)" %>
</th>
<th><%# HTTP Verb %>
</th>
@@ -93,7 +97,7 @@
}
}
- // get JSON from url and invoke callback with result
+ // get JSON from URL and invoke callback with result
function getJSON(url, success) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);