diff options
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware')
13 files changed, 216 insertions, 93 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 5b2ad36dd5..87fe19225b 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -24,10 +24,8 @@ module ActionDispatch def call(env) error = nil result = run_callbacks :call do - begin - @app.call(env) - rescue => error - end + @app.call(env) + rescue => error end raise error if error result diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 34331b7e4b..26d3fd936f 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -9,7 +9,7 @@ require "rack/utils" module ActionDispatch class Request def cookie_jar - fetch_header("action_dispatch.cookies".freeze) do + fetch_header("action_dispatch.cookies") do self.cookie_jar = Cookies::CookieJar.build(self, cookies) end end @@ -22,11 +22,11 @@ module ActionDispatch } def have_cookie_jar? - has_header? "action_dispatch.cookies".freeze + has_header? "action_dispatch.cookies" end def cookie_jar=(jar) - set_header "action_dispatch.cookies".freeze, jar + set_header "action_dispatch.cookies", jar end def key_generator @@ -172,21 +172,21 @@ module ActionDispatch # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or # only HTTP. Defaults to +false+. class Cookies - HTTP_HEADER = "Set-Cookie".freeze - GENERATOR_KEY = "action_dispatch.key_generator".freeze - 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 - USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata".freeze + HTTP_HEADER = "Set-Cookie" + GENERATOR_KEY = "action_dispatch.key_generator" + SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt" + ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt" + ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt" + AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt" + USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption" + ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher" + SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest" + SECRET_TOKEN = "action_dispatch.secret_token" + SECRET_KEY_BASE = "action_dispatch.secret_key_base" + COOKIES_SERIALIZER = "action_dispatch.cookies_serializer" + COOKIES_DIGEST = "action_dispatch.cookies_digest" + COOKIES_ROTATIONS = "action_dispatch.cookies_rotations" + USE_COOKIES_WITH_METADATA = "action_dispatch.use_cookies_with_metadata" # Cookies can typically store 4096 bytes. MAX_COOKIE_SIZE = 4096 @@ -535,7 +535,7 @@ module ActionDispatch end module SerializedCookieJars # :nodoc: - MARSHAL_SIGNATURE = "\x04\x08".freeze + MARSHAL_SIGNATURE = "\x04\x08" SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer protected diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 5f5fdbc66a..61773d97a2 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -3,53 +3,14 @@ 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", __dir__) - - class DebugView < ActionView::Base - def debug_params(params) - clean_params = params.clone - clean_params.delete("action") - clean_params.delete("controller") - - if clean_params.empty? - "None" - else - PP.pp(clean_params, +"", 200) - end - end - - def debug_headers(headers) - if headers.present? - headers.inspect.gsub(",", ",\n") - else - "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 - cattr_reader :interceptors, instance_accessor: false, default: [] def self.register_interceptor(object = nil, &block) @@ -87,11 +48,9 @@ module ActionDispatch wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) @interceptors.each do |interceptor| - begin - interceptor.call(request, exception) - rescue Exception - log_error(request, wrapper) - end + interceptor.call(request, exception) + rescue Exception + log_error(request, wrapper) end end @@ -152,7 +111,7 @@ module ActionDispatch end def create_template(request, wrapper) - DebugView.new([RESCUES_TEMPLATE_PATH], + DebugView.new( request: request, exception_wrapper: wrapper, exception: wrapper.exception, @@ -180,11 +139,14 @@ module ActionDispatch 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 + message = [] + message << " " + message << "#{exception.class} (#{exception.message}):" + message.concat(exception.annoted_source_code) if exception.respond_to?(:annoted_source_code) + message << " " + message.concat(trace) + + log_array(logger, message) end end diff --git a/actionpack/lib/action_dispatch/middleware/debug_locks.rb b/actionpack/lib/action_dispatch/middleware/debug_locks.rb index d39377f174..93c6c85a71 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_locks.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_locks.rb @@ -32,7 +32,7 @@ module ActionDispatch req = ActionDispatch::Request.new env if req.get? - path = req.path_info.chomp("/".freeze) + path = req.path_info.chomp("/") if path == @path return render_details(req) end diff --git a/actionpack/lib/action_dispatch/middleware/debug_view.rb b/actionpack/lib/action_dispatch/middleware/debug_view.rb new file mode 100644 index 0000000000..ac12dc13a1 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/debug_view.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "pp" + +require "action_view" +require "action_view/base" + +module ActionDispatch + class DebugView < ActionView::Base # :nodoc: + RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__) + + def initialize(assigns) + super([RESCUES_TEMPLATE_PATH], assigns) + end + + def debug_params(params) + clean_params = params.clone + clean_params.delete("action") + clean_params.delete("controller") + + if clean_params.empty? + "None" + else + PP.pp(clean_params, +"", 200) + end + end + + def debug_headers(headers) + if headers.present? + headers.inspect.gsub(",", ",\n") + else + "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 +end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index fd05eec172..cf9165d008 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -38,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" module RequestMethods # Access the contents of the flash. Use <tt>flash["notice"]</tt> to diff --git a/actionpack/lib/action_dispatch/middleware/host_authorization.rb b/actionpack/lib/action_dispatch/middleware/host_authorization.rb new file mode 100644 index 0000000000..447b70112a --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/host_authorization.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require "action_dispatch/http/request" + +module ActionDispatch + # This middleware guards from DNS rebinding attacks by white-listing the + # hosts a request can be sent to. + # + # When a request comes to an unauthorized host, the +response_app+ + # application will be executed and rendered. If no +response_app+ is given, a + # default one will run, which responds with +403 Forbidden+. + class HostAuthorization + class Permissions # :nodoc: + def initialize(hosts) + @hosts = sanitize_hosts(hosts) + end + + def empty? + @hosts.empty? + end + + def allows?(host) + @hosts.any? do |allowed| + allowed === host + rescue + # IPAddr#=== raises an error if you give it a hostname instead of + # IP. Treat similar errors as blocked access. + false + end + end + + private + + def sanitize_hosts(hosts) + Array(hosts).map do |host| + case host + when Regexp then sanitize_regexp(host) + when String then sanitize_string(host) + else host + end + end + end + + def sanitize_regexp(host) + /\A#{host}\z/ + end + + def sanitize_string(host) + if host.start_with?(".") + /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/ + else + host + end + end + end + + DEFAULT_RESPONSE_APP = -> env do + request = Request.new(env) + + format = request.xhr? ? "text/plain" : "text/html" + template = DebugView.new(host: request.host) + body = template.render(template: "rescues/blocked_host", layout: "rescues/layout") + + [403, { + "Content-Type" => "#{format}; charset=#{Response.default_charset}", + "Content-Length" => body.bytesize.to_s, + }, [body]] + end + + def initialize(app, hosts, response_app = nil) + @app = app + @permissions = Permissions.new(hosts) + @response_app = response_app || DEFAULT_RESPONSE_APP + end + + def call(env) + return @app.call(env) if @permissions.empty? + + request = Request.new(env) + + if authorized?(request) + mark_as_authorized(request) + @app.call(env) + else + @response_app.call(env) + end + end + + private + + def authorized?(request) + origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "") + forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "") + + @permissions.allows?(origin_host) && + (forwarded_host.blank? || @permissions.allows?(forwarded_host)) + end + + def mark_as_authorized(request) + request.set_header("action_dispatch.authorized_host", request.host) + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 35158f9062..a5667573f4 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -162,14 +162,12 @@ module ActionDispatch # 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. - range = IPAddr.new(ip).to_range - # We want to make sure nobody is sneaking a netmask in. - range.begin == range.end - rescue ArgumentError - nil - end + # 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. + range.begin == range.end + rescue ArgumentError + nil end end diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index da2871b551..fcc0c72240 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -15,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" #:nodoc: def initialize(app) @app = app @@ -30,7 +30,7 @@ module ActionDispatch private def make_request_id(request_id) if request_id.presence - request_id.gsub(/[^\w\-@]/, "".freeze).first(255) + request_id.gsub(/[^\w\-@]/, "").first(255) else internal_request_id end diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 9c9ccfa16f..00902ede21 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -83,7 +83,7 @@ module ActionDispatch private def set_hsts_header!(headers) - headers["Strict-Transport-Security".freeze] ||= @hsts_header + headers["Strict-Transport-Security"] ||= @hsts_header end def normalize_hsts_options(options) @@ -109,16 +109,16 @@ module ActionDispatch end def flag_cookies_as_secure!(headers) - if cookies = headers["Set-Cookie".freeze] - cookies = cookies.split("\n".freeze) + if cookies = headers["Set-Cookie"] + cookies = cookies.split("\n") - headers["Set-Cookie".freeze] = cookies.map { |cookie| + headers["Set-Cookie"] = cookies.map { |cookie| if !/;\s*secure\s*(;|$)/i.match?(cookie) "#{cookie}; secure" else cookie end - }.join("\n".freeze) + }.join("\n") end end diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 277074f216..1f2f7757a3 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -79,7 +79,7 @@ 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") end def gzip_encoding_accepted?(request) @@ -116,7 +116,7 @@ module ActionDispatch req = Rack::Request.new env if req.get? || req.head? - path = req.path_info.chomp("/".freeze) + path = req.path_info.chomp("/") 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/blocked_host.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb new file mode 100644 index 0000000000..2fa78dd385 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb @@ -0,0 +1,7 @@ +<header> + <h1>Blocked host: <%= @host %></h1> +</header> +<div id="container"> + <h2>To allow requests to <%= @host %>, add the following configuration:</h2> + <pre>Rails.application.config.hosts << "<%= @host %>"</pre> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb new file mode 100644 index 0000000000..4e2d1d0b08 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb @@ -0,0 +1,5 @@ +Blocked host: <%= @host %> + +To allow requests to <%= @host %>, add the following configuration: + + Rails.application.config.hosts << "<%= @host %>" |