Compare commits

...

7 commits

Author SHA1 Message Date
jonasseglare
1ff6aa0321
Merge f1ade4b089 into e3306e1876 2026-01-28 09:07:08 -08:00
Joel Kaasinen
e3306e1876
Merge pull request #774 from metosin/feat/753-doc-parameter-coercion
Some checks failed
testsuite / Check cljdoc analysis (push) Has been cancelled
testsuite / Clojure 11 (Java 11) (push) Has been cancelled
testsuite / Clojure 11 (Java 17) (push) Has been cancelled
testsuite / Clojure 11 (Java 21) (push) Has been cancelled
testsuite / Clojure 11 (Java 25) (push) Has been cancelled
testsuite / Clojure 12 (Java 11) (push) Has been cancelled
testsuite / Clojure 12 (Java 17) (push) Has been cancelled
testsuite / Clojure 12 (Java 21) (push) Has been cancelled
testsuite / Clojure 12 (Java 25) (push) Has been cancelled
testsuite / ClojureScript (push) Has been cancelled
testsuite / Lint cljdoc.edn (push) Has been cancelled
doc: document nuances of reitit.coercion/default-parameter-coercion
2026-01-23 14:38:39 +02:00
Joel Kaasinen
e6e1bfd5c4
doc: clarify malli body coercion doc 2026-01-23 14:25:19 +02:00
Joel Kaasinen
8391fafbe2
Merge pull request #776 from metosin/feat/337-fix-external-redirect
fix: redirect-trailing-slash-handler won't make external redirects
2026-01-23 14:16:11 +02:00
Joel Kaasinen
71a777b4fa
fix: redirect-trailing-slash-handler won't make external redirects
A redirect header like

Location: //malicious.com/foo

Would result in a redirect to https://malicious.com/foo . We never
want Locations like that out of redirect-trailing-slash-handler.

Fixes #337
2026-01-23 09:54:26 +02:00
Joel Kaasinen
a6b68cc3d6
doc: document nuances of reitit.coercion/default-parameter-coercion
for #753
2026-01-23 09:00:47 +02:00
Jonas Östlund
f1ade4b089 Extend -compile-model for reitit.coercsion.spec 2024-05-21 13:49:52 +02:00
4 changed files with 49 additions and 4 deletions

View file

@ -63,8 +63,6 @@ Handlers can access the coerced parameters via the `:parameters` key in the requ
{:status 200
:body {:total total}}))})
```
### Nested parameter definitions
Parameters are accumulated recursively along the route tree, just like
@ -94,6 +92,26 @@ handling for merging eg. malli `:map` schemas.
; [:task-id :int]]}
```
### Differences in behaviour for different parameters
All parameter coercions *except* `:body`:
1. Allow keys outside the schema (by opening up the schema using eg. `malli.util/open-schema`)
2. Keywordize the keys (ie. header & query parameter names) of the input before coercing
In contrast, the `:body` coercion:
1. Uses the specified schema
* depending on the coercion, it can be configured as open or closed, see specific coercion docs for details
2. Does not keywordize the keys of the input before coercion
* however, coercions like malli might do the keywordization when coercing json bodies, depending on configuration
This admittedly confusing behaviour is retained currently due to
backwards compatibility reasons. It can be configured by passing
option `:reitit.coercion/parameter-coercion` to `reitit.ring/router`
or `reitit.coercion/compile-request-coercers`. See also:
`reitit.coercion/default-parameter-coercion`.
## Coercion Middleware
Defining a coercion for a route data doesn't do anything, as it's just data. We have to attach some code to apply the actual coercion. We can use the middleware from `reitit.ring.coercion`:

View file

@ -173,7 +173,8 @@
(letfn [(maybe-redirect [{:keys [query-string] :as request} path]
(if (and (seq path) (r/match-by-path (::r/router request) path))
{:status (if (= (:request-method request) :get) 301 308)
:headers {"Location" (if query-string (str path "?" query-string) path)}
:headers {"Location" (let [path (str/replace-first path #"^/+" "/")] ; Locations starting with // redirect to another hostname. Avoid these due to security implications.
(if query-string (str path "?" query-string) path))}
:body ""}))
(redirect-handler [request]
(let [uri (:uri request)]

View file

@ -122,6 +122,8 @@
(= (count model) 1) (first model)
;; here be dragons, best effort
(every? map? model) (apply mm/meta-merge model)
;; all elements are the same
(apply = model) (first model)
;; fail fast
:else (ex/fail! ::model-error {:message "Can't merge nested clojure specs", :spec model}))
name))

View file

@ -416,7 +416,31 @@
:get "/slash-less//" "/slash-less?kikka=kukka"
:post "/with-slash" "/with-slash/?kikka=kukka"
:post "/slash-less/" "/slash-less?kikka=kukka"
:post "/slash-less//" "/slash-less?kikka=kukka"))))))
:post "/slash-less//" "/slash-less?kikka=kukka"))))
;; See issue #337
(testing "Avoid external redirects"
(let [app (ring/ring-handler
(ring/router [["*" {:get (constantly nil)}]])
(ring/redirect-trailing-slash-handler))
resp (fn [uri & [query-string]]
(let [r (app {:request-method :get :uri uri :query-string query-string})]
{:status (:status r)
:Location (get-in r [:headers "Location"])}))]
(testing "without query params"
(is (= {:status 301 :Location "/malicious.com/foo/"} (resp "//malicious.com/foo")))
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "//malicious.com/foo/")))
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "//malicious.com/foo//")))
(is (= {:status 301 :Location "/malicious.com/foo/"} (resp "///malicious.com/foo")))
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "///malicious.com/foo/")))
(is (= {:status 301 :Location "/malicious.com/foo"} (resp "///malicious.com/foo//"))))
(testing "with query params"
(is (= {:status 301 :Location "/malicious.com/foo/?bar=quux"} (resp "//malicious.com/foo" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "//malicious.com/foo/" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "//malicious.com/foo//" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo/?bar=quux"} (resp "///malicious.com/foo" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "///malicious.com/foo/" "bar=quux")))
(is (= {:status 301 :Location "/malicious.com/foo?bar=quux"} (resp "///malicious.com/foo//" "bar=quux"))))))))
(deftest async-ring-test
(let [promise #(let [value (atom ::nil)]