reitit
reitit copied to clipboard
Can't get descendants of classes
Problem
It's possible to evade the ring exception handler middleware and make it throw an exception, leading to no ring response at all:
(#'reitit.ring.middleware.exception/call-error-handler
{} ; handlers for exception `class` or `ex-data :type`
(ex-info "" {:type java.lang.ClassCastException})
{:re 'quest})
throws
java.lang.UnsupportedOperationException: Can't get descendants of classes
at clojure.core$descendants.invokeStatic(core.clj:5611)
at clojure.core$descendants.invokeStatic(core.clj:5603)
at clojure.core$descendants.invoke(core.clj:5603)
at reitit.ring.middleware.exception$call_error_handler.invokeStatic(exception.clj:9)
at reitit.ring.middleware.exception$call_error_handler.invoke(exception.clj:22)
at reitit.ring.middleware.exception$on_exception.invokeStatic(exception.clj:40)
at reitit.ring.middleware.exception$on_exception.invoke(exception.clj:38)
at reitit.ring.middleware.exception$wrap$fn__1760$fn__1761.invoke(exception.clj:51)
at muuntaja.middleware$wrap_format_response$fn__18356.invoke(middleware.clj:132)
at muuntaja.middleware$wrap_format_negotiate$fn__18349.invoke(middleware.clj:96)
at ring.middleware.params$wrap_params$fn__18297.invoke(params.clj:75)
...
Solution
(or (get handlers type)
(get handlers ex-class)
(when-not (class? type)
(some
(partial get handlers)
(descendants type)))
(some
(partial get handlers)
(super-classes ex-class))
(get handlers ::default))
instead of the current
(or (get handlers type)
(get handlers ex-class)
(some
(partial get handlers)
(descendants type))
(some
(partial get handlers)
(super-classes ex-class))
(get handlers ::default))
It might also make sense to just unit test the call-error-handler, to simplify the reitit.ring.middleware.exception-test/exception-test
or maybe even extract a find-error-handler from the call-error-handler and just unit test that.
To be clear, I'm not sure yet how such an exception happens in our code, but regardless, I think it shows a bug in call-error-handler, because it might call clojure.core/descendants with a class, which is not supported:
(defn descendants
"Returns the immediate and indirect children of tag, through a
relationship established via derive. h must be a hierarchy obtained
from make-hierarchy, if not supplied defaults to the global
hierarchy. Note: does not work on Java type inheritance
relationships."
{:added "1.0"}
([tag] (descendants global-hierarchy tag))
([h tag] (if (class? tag)
(throw (java.lang.UnsupportedOperationException. "Can't get descendants of classes"))
(not-empty (get (:descendants h) tag)))))