discourse/lib/middleware/discourse_public_exceptions.rb
David Taylor 64be371749
DEV: Improve handling of invalid requests (#15841)
Our discourse_public_exceptions middleware is designed to catch bubbled exceptions from lower in the stack, and then use `ApplicationController.rescue_with_handler` to render an appropriate error response.

When the request itself is invalid, we had an escape-hatch to skip re-dispatching the request to ApplicationController. However, it was possible to work around this by 'layering' the errors. For example, if you made a request which resulted in a 404, but **also** had some other invalidity, the escape hatch would not be triggered.

This commit ensures that these kind of 'layered' errors are properly handled, without logging warnings. It also adds detection for invalid JSON bodies and badly-formed multipart requests.

The user-facing behavior is unchanged. This commit simply prevents warnings being logged for invalid requests.
2022-02-07 13:16:57 +00:00

66 lines
2.2 KiB
Ruby

# frozen_string_literal: true
# since all the rescue from clauses are not caught by the application controller for matches
# we need to handle certain exceptions here
module Middleware
class DiscoursePublicExceptions < ::ActionDispatch::PublicExceptions
INVALID_REQUEST_ERRORS = Set.new([
Rack::QueryParser::InvalidParameterError,
ActionController::BadRequest,
ActionDispatch::Http::Parameters::ParseError,
])
def initialize(path)
super
end
def call(env)
# this is so so gnarly
# sometimes we leak out exceptions prior to creating a controller instance
# this can happen if we have an exception in a route constraint in some cases
# this code re-dispatches the exception to our application controller so we can
# properly translate the exception to a page
exception = env["action_dispatch.exception"]
response = ActionDispatch::Response.new
exception = nil if INVALID_REQUEST_ERRORS.include?(exception)
if exception
begin
fake_controller = ApplicationController.new
fake_controller.response = response
fake_controller.request = request = ActionDispatch::Request.new(env)
# We can not re-dispatch bad mime types
begin
request.format
rescue Mime::Type::InvalidMimeType
return [400, { "Cache-Control" => "private, max-age=0, must-revalidate" }, ["Invalid MIME type"]]
end
# Or badly formatted multipart requests
begin
request.POST
rescue EOFError
return [400, { "Cache-Control" => "private, max-age=0, must-revalidate" }, ["Invalid request"]]
end
if ApplicationController.rescue_with_handler(exception, object: fake_controller)
body = response.body
if String === body
body = [body]
end
return [response.status, response.headers, body]
end
rescue => e
return super if INVALID_REQUEST_ERRORS.include?(e.class)
Discourse.warn_exception(e, message: "Failed to handle exception in exception app middleware")
end
end
super
end
end
end