├── .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 [![Build Status](https://github.com/ring-clojure/ring-headers/actions/workflows/test.yml/badge.svg)](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 ,