Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 4 additions & 24 deletions src/selmer/filter_parser.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,10 @@
arguments."
(:require
[selmer.filters :refer [get-filter]]
[selmer.util :refer [*escape-variables* parse-accessor]]
[selmer.util :refer [*escape-variables* *escape-fn* parse-accessor]]
[clojure.string :as str]))

;;; More Utils
(defn escape-html*
"HTML-escapes the given string. Escapes the same characters as django's escape."
[^String s]
;; This method is "Java in Clojure" for serious speedups.
;; Stolen from davidsantiago/quoin and modified.
(if *escape-variables*
(let [slength (count s)
sb (StringBuilder. slength)]
(loop [idx 0]
(if (>= idx slength)
(.toString sb)
(let [c (char (.charAt s idx))]
(case c
\& (.append sb "&")
\< (.append sb "&lt;")
\> (.append sb "&gt;")
\" (.append sb "&quot;")
\' (.append sb "&#39;")
(.append sb c))
(recur (inc idx))))))
s))

(defn strip-doublequotes
"Removes doublequotes from the start and end of a string if any."
Expand All @@ -58,8 +37,9 @@
(if (and (vector? x)
(= :safe (first x)))
(second x)
(let [s (str x)]
(escape-html* s))))
(if *escape-variables*
(*escape-fn* x)
(str x))))

;;; Compile filters

Expand Down
2 changes: 1 addition & 1 deletion src/selmer/tags.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
clojure.java.io
[clojure.string :as str]
selmer.node
[selmer.filter-parser :refer [literal? parse-literal safe-filter compile-filter-body get-accessor escape-html*]]
[selmer.filter-parser :refer [literal? parse-literal safe-filter compile-filter-body get-accessor]]
[selmer.filters :refer [filters]]
[selmer.util :refer :all])
(:import [selmer.node TextNode]))
Expand Down
25 changes: 25 additions & 0 deletions src/selmer/util.clj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@

(def ^:dynamic *escape-variables* true)

(defn escape-html*
"HTML-escapes the given string. Escapes the same characters as django's escape."
[input]
;; This method is "Java in Clojure" for serious speedups.
;; Stolen from davidsantiago/quoin and modified.
(if *escape-variables*
(let [s (str input)
slength (count s)
sb (StringBuilder. slength)]
(loop [idx 0]
(if (>= idx slength)
(.toString sb)
(let [c (char (.charAt s idx))]
(case c
\& (.append sb "&amp;")
\< (.append sb "&lt;")
\> (.append sb "&gt;")
\" (.append sb "&quot;")
\' (.append sb "&#39;")
(.append sb c))
(recur (inc idx))))))
(str input)))

(def ^:dynamic *escape-fn* escape-html*)

(defn turn-off-escaping! []
(alter-var-root #'*escape-variables*
(constantly false)))
Expand Down
22 changes: 22 additions & 0 deletions test/selmer/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,28 @@
(is (= "I <3 ponies" (render "{{name}}" {:name "I <3 ponies"})))
(finally (turn-on-escaping!)))))

(deftest escape-fn-test
(testing "default escaping coerces non-string values to strings"
(is (= "123" (render "{{n}}" {:n 123})))
(is (= "true" (render "{{b}}" {:b true}))))

(testing "*escape-fn* overrides the default escaping function"
(binding [*escape-fn* str/upper-case]
(is (= "I <3 PONIES" (render "{{name}}" {:name "i <3 ponies"}))))
;; ensure default escaping resumes outside the binding
(is (= "I &lt;3 ponies" (render "{{name}}" {:name "I <3 ponies"}))))

(testing "*escape-fn* is not applied to values marked safe"
(binding [*escape-fn* str/upper-case]
(is (= "I <3 ponies" (render "{{name|safe}}" {:name "I <3 ponies"})))))

(testing "*escape-fn* is bypassed when escaping is turned off"
(try
(turn-off-escaping!)
(binding [*escape-fn* str/upper-case]
(is (= "I <3 ponies" (render "{{name}}" {:name "I <3 ponies"}))))
(finally (turn-on-escaping!)))))

(deftest name-test
(testing "converts keywords to strings"
(is (= "foobar" (render "{{foo|name}}" {:foo :foobar})))
Expand Down
Loading