├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .travis.yml
├── README.md
├── project.clj
├── src
└── ring
│ └── middleware
│ ├── absolute_redirects.clj
│ ├── authorization.clj
│ ├── default_charset.clj
│ ├── proxy_headers.clj
│ └── x_headers.clj
└── test
└── ring
└── middleware
├── absolute_redirects_test.clj
├── authorization_test.clj
├── default_charset_test.clj
├── proxy_headers_test.clj
└── x_headers_test.clj
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 | on: [push, pull_request]
3 | jobs:
4 | test:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - name: Checkout
8 | uses: actions/checkout@v3
9 |
10 | - name: Prepare java
11 | uses: actions/setup-java@v3
12 | with:
13 | distribution: 'zulu'
14 | java-version: '11'
15 |
16 | - name: Install clojure tools
17 | uses: DeLaGuardo/setup-clojure@10.1
18 | with:
19 | lein: 2.9.10
20 |
21 | - name: Cache clojure dependencies
22 | uses: actions/cache@v3
23 | with:
24 | path: ~/.m2/repository
25 | key: cljdeps-${{ hashFiles('project.clj', 'ring-*/project.clj') }}
26 | restore-keys: cljdeps-
27 |
28 | - name: Run tests
29 | run: lein test-all
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | /codox
5 | pom.xml
6 | pom.xml.asc
7 | *.jar
8 | *.class
9 | /.lein-*
10 | /.nrepl-port
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: clojure
2 | script: lein test-all
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ring-Headers [](https://github.com/ring-clojure/ring-headers/actions/workflows/test.yml)
2 |
3 | Ring middleware for parsing common request headers, and for adding and
4 | manipulating common response headers.
5 |
6 | ## Installation
7 |
8 | Add the following dependency to your `deps.edn` file:
9 |
10 | ring/ring-headers {:mvn/version "0.4.0"}
11 |
12 | Or to your Leiningen project file:
13 |
14 | [ring/ring-headers "0.4.0"]
15 |
16 | ## Documentation
17 |
18 | * [API Docs](http://ring-clojure.github.io/ring-headers)
19 |
20 | ## License
21 |
22 | Copyright © 2024 James Reeves
23 |
24 | Distributed under the MIT License, the same as Ring.
25 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject ring/ring-headers "0.4.0"
2 | :description "Ring middleware for common response headers"
3 | :url "https://github.com/ring-clojure/ring-headers"
4 | :license {:name "The MIT License"
5 | :url "http://opensource.org/licenses/MIT"}
6 | :dependencies [[org.clojure/clojure "1.9.0"]
7 | [ring/ring-core "1.12.1"]]
8 | :plugins [[lein-codox "0.10.8"]]
9 | :codox {:project {:name "Ring-Headers"}
10 | :output-path "codox"}
11 | :aliases {"test-all" ["with-profile" "default:+1.10:+1.11:+1.12" "test"]}
12 | :profiles
13 | {:dev {:dependencies [[ring/ring-mock "0.4.0"]]}
14 | :1.10 {:dependencies [[org.clojure/clojure "1.10.3"]]}
15 | :1.11 {:dependencies [[org.clojure/clojure "1.11.3"]]}
16 | :1.12 {:dependencies [[org.clojure/clojure "1.12.0-alpha9"]]}})
17 |
--------------------------------------------------------------------------------
/src/ring/middleware/absolute_redirects.clj:
--------------------------------------------------------------------------------
1 | (ns ring.middleware.absolute-redirects
2 | "Middleware for replacing relative redirects with absolute redirects. Useful
3 | for clients that do not yet implement RFC 7231 (RFC 2616 does not allow
4 | relative redirects)."
5 | (:require [ring.util.request :as req])
6 | (:import [java.net URL MalformedURLException]))
7 |
8 | (defn- redirect? [response]
9 | (#{201 301 302 303 307} (:status response)))
10 |
11 | (defn- get-header-key [response ^String header-name]
12 | (->> response :headers keys
13 | (filter #(.equalsIgnoreCase header-name %))
14 | first))
15 |
16 | (defn- update-header [response header f & args]
17 | (if-let [header (get-header-key response header)]
18 | (apply update-in response [:headers header] f args)
19 | response))
20 |
21 | (defn- url? [^String s]
22 | (try (URL. s) true
23 | (catch MalformedURLException _ false)))
24 |
25 | (defn- absolute-url [location request]
26 | (if (url? location)
27 | location
28 | (let [url (URL. (req/request-url request))]
29 | (str (URL. url location)))))
30 |
31 | (defn absolute-redirects-response
32 | "Convert a response that redirects to a relative URLs into a response that
33 | redirects to an absolute URL. See: wrap-absolute-redirects."
34 | [response request]
35 | (if (redirect? response)
36 | (update-header response "location" absolute-url request)
37 | response))
38 |
39 | (defn wrap-absolute-redirects
40 | "Middleware that converts redirects to relative URLs into redirects to
41 | absolute URLs. While many browsers can handle relative URLs in the Location
42 | header, RFC 2616 states that the Location header must contain an absolute
43 | URL."
44 | [handler]
45 | (fn
46 | ([request]
47 | (absolute-redirects-response (handler request) request))
48 | ([request respond raise]
49 | (handler request #(respond (absolute-redirects-response % request)) raise))))
50 |
--------------------------------------------------------------------------------
/src/ring/middleware/authorization.clj:
--------------------------------------------------------------------------------
1 | (ns ring.middleware.authorization
2 | (:require [clojure.string :as str]
3 | [ring.util.parsing :as parsing]))
4 |
5 | (def ^:private re-token68
6 | (re-pattern "[A-Za-z0-9._~+/-]+=*"))
7 |
8 | (def ^:private re-auth-param
9 | (re-pattern (str "(" parsing/re-token ")\\s*=\\s*(?:" parsing/re-value ")")))
10 |
11 | (defn- parse-auth-params [auth-params]
12 | (reduce (fn [m kv]
13 | (if-let [[_ k v1 v2] (re-matches re-auth-param kv)]
14 | (assoc m (str/lower-case k) (or v1 v2))
15 | m))
16 | {}
17 | (str/split auth-params #"\s*,\s*")))
18 |
19 | (defn- parse-authorization [request]
20 | (when-let [[auth-scheme token-or-params]
21 | (some-> (get-in request [:headers "authorization"])
22 | (str/split #"\s" 2))]
23 | (cond
24 | (empty? token-or-params)
25 | {:scheme (str/lower-case auth-scheme)}
26 |
27 | (re-matches re-token68 token-or-params)
28 | {:scheme (str/lower-case auth-scheme)
29 | :token token-or-params}
30 |
31 | :else
32 | {:scheme (str/lower-case auth-scheme)
33 | :params (parse-auth-params token-or-params)})))
34 |
35 | (defn authorization-request
36 | "Parses Authorization header in the request map. See: wrap-authorization."
37 | [request]
38 | (if (:authorization request)
39 | request
40 | (assoc request :authorization (parse-authorization request))))
41 |
42 | (defn wrap-authorization
43 | "Parses the Authorization header in the request map, then assocs the result
44 | to the :authorization key on the request.
45 |
46 | See RFC 7235 Section 2 and RFC 9110 Section 11:
47 | * https://datatracker.ietf.org/doc/html/rfc7235#section-2
48 | * https://datatracker.ietf.org/doc/html/rfc9110#section-11"
49 | [handler]
50 | (fn
51 | ([request]
52 | (handler (authorization-request request)))
53 | ([request respond raise]
54 | (handler (authorization-request request)
55 | respond
56 | raise))))
57 |
--------------------------------------------------------------------------------
/src/ring/middleware/default_charset.clj:
--------------------------------------------------------------------------------
1 | (ns ring.middleware.default-charset
2 | "Middleware for automatically adding a charset to the content-type header in
3 | response maps."
4 | (:require [ring.util.response :as response]))
5 |
6 | (defn- text-based-content-type? [content-type]
7 | (or (re-find #"text/" content-type)
8 | (re-find #"application/xml" content-type)))
9 |
10 | (defn- contains-charset? [content-type]
11 | (re-find #";\s*charset=[^;]*" content-type))
12 |
13 | (defn default-charset-response
14 | "Add a default charset to a response if the response has no charset and
15 | requires one. See: wrap-default-charset."
16 | [response charset]
17 | (if response
18 | (if-let [content-type (response/get-header response "Content-Type")]
19 | (if (and (text-based-content-type? content-type)
20 | (not (contains-charset? content-type)))
21 | (response/charset response charset)
22 | response)
23 | response)))
24 |
25 | (defn wrap-default-charset
26 | "Middleware that adds a charset to the content-type header of the response if
27 | one was not set by the handler."
28 | [handler charset]
29 | (fn
30 | ([request]
31 | (default-charset-response (handler request) charset))
32 | ([request respond raise]
33 | (handler request #(respond (default-charset-response % charset)) raise))))
34 |
--------------------------------------------------------------------------------
/src/ring/middleware/proxy_headers.clj:
--------------------------------------------------------------------------------
1 | (ns ring.middleware.proxy-headers
2 | "Middleware for handling headers set by HTTP proxies."
3 | (:require [clojure.string :as str]))
4 |
5 | (defn forwarded-remote-addr-request
6 | "Change the :remote-addr key of the request map to the last value present in
7 | the X-Forwarded-For header. See: wrap-forwarded-remote-addr."
8 | [request]
9 | (if-let [forwarded-for (get-in request [:headers "x-forwarded-for"])]
10 | (let [remote-addr (str/trim (re-find #"[^,]*$" forwarded-for))]
11 | (assoc request :remote-addr remote-addr))
12 | request))
13 |
14 | (defn wrap-forwarded-remote-addr
15 | "Middleware that changes the :remote-addr of the request map to the
16 | last value present in the X-Forwarded-For header."
17 | [handler]
18 | (fn
19 | ([request]
20 | (handler (forwarded-remote-addr-request request)))
21 | ([request respond raise]
22 | (handler (forwarded-remote-addr-request request) respond raise))))
23 |
--------------------------------------------------------------------------------
/src/ring/middleware/x_headers.clj:
--------------------------------------------------------------------------------
1 | (ns ring.middleware.x-headers
2 | "Middleware for adding various 'X-' response headers."
3 | (:require [clojure.string :as str]
4 | [ring.util.response :as resp]))
5 |
6 | (defn- allow-from? [frame-options]
7 | (and (map? frame-options)
8 | (= (keys frame-options) [:allow-from])
9 | (string? (:allow-from frame-options))))
10 |
11 | (defn- format-frame-options [frame-options]
12 | (if (map? frame-options)
13 | (str "ALLOW-FROM " (:allow-from frame-options))
14 | (str/upper-case (name frame-options))))
15 |
16 | (defn- format-xss-protection [enable? options]
17 | (str (if enable? "1" "0") (if options "; mode=block")))
18 |
19 | (defn- wrap-x-header [handler header-name header-value]
20 | (fn
21 | ([request]
22 | (some-> (handler request) (resp/header header-name header-value)))
23 | ([request respond raise]
24 | (handler request #(respond (some-> % (resp/header header-name header-value))) raise))))
25 |
26 | (defn frame-options-response
27 | "Add the X-Frame-Options header to the response. See: wrap-frame-options."
28 | [response frame-options]
29 | (some-> response (resp/header "X-Frame-Options" (format-frame-options frame-options))))
30 |
31 | (defn wrap-frame-options
32 | "Middleware that adds the X-Frame-Options header to the response. This governs
33 | whether your site can be rendered in a ,