Today we continue to investigate poftheday
’s dependencies and will look
at the well known cl-who
library. CL-Who
is a library Edmund Weitz and
provides a DSL for HTML generation.
For those who are not familiar with cl-who
, here is a quick example:
POFTHEDAY> (cl-who:with-html-output-to-string (s)
(:body
(:p "Hello world!")))
"<body><p>Hello world!</p></body>"
If you want to insert a variable, you have to use a local macro
esc
. There is also another macro - str
, and it very easy to misuse
it. That is one of the reasons why I don’t like cl-who
and prefer
spinneret
.
Let’s pretend we want to output a username in the comment list on our page. The correct way to do so will be:
POFTHEDAY> (defclass user ()
((name :initarg :name
:reader get-name)))
POFTHEDAY> (let ((user (make-instance
'user
:name "Bob <script>alert('You are hacked')</script>"))
(comment-text "Hello from Bob!"))
(cl-who:with-html-output-to-string (s nil :indent t)
(:div :class "comment"
(:div :class "username"
(cl-who:esc (get-name user)))
(:div :class "text"
(cl-who:esc comment-text)))))
"
<div class='comment'>
<div class='username'>Bob <script>alert('You are hacked')</script>
</div>
<div class='text'>Hello from Bob!
</div>
</div>"
As I said, this was a correct way, but it is very easy to misuse cl-who
and make your beautiful site open for XSS attacks. You only have to use
str
instead of esc
:
POFTHEDAY> (let ((user (make-instance
'user
:name "Bob <script>alert('You are hacked')</script>"))
(comment-text "Hello from Bob!"))
(cl-who:with-html-output-to-string (s nil :indent t)
(:div :class "comment"
(:div :class "username"
(cl-who:str (get-name user)))
(:div :class "text"
(cl-who:str comment-text)))))
"
<div class='comment'>
<div class='username'>Bob <script>alert('You are hacked')</script>
</div>
<div class='text'>Hello from Bob!
</div>
</div>"
Here script
tag that was not escaped. This way, any code an evil user will
enter as his name will be executed in other users browsers.
Another inconvenience of cl-who
is that you have to use htm
macro if
want to mix HTML
pieces with lisp forms. For example, if you want to
output a list of items, this will not work:
POFTHEDAY> (let ((list (list 1 2 3 4 5))) (cl-who:with-html-output-to-string (s nil :indent t) (:ul (loop for item in list do (:li (cl-who:esc (format nil "Item number ~A" item))))))) ; in: LET ((LIST (LIST 1 2 3 4 5))) ; (:LI (CL-WHO:ESC (FORMAT NIL "Item number ~A" POFTHEDAY::ITEM))) ; ; caught STYLE-WARNING: ; undefined function: :LI ; ; compilation unit finished ; Undefined function: ; :LI ; caught 1 STYLE-WARNING condition
You have to wrap :li
form with a htm
macro, like that:
POFTHEDAY> (let ((list (list 1 2 3 4 5)))
(cl-who:with-html-output-to-string (s nil :indent t)
(:ul
(loop for item in list
do (cl-who:htm
(:li
(cl-who:esc
(format nil "Item number ~A"
item))))))))
"
<ul>
<li>Item number 1
</li>
<li>Item number 2
</li>
<li>Item number 3
</li>
<li>Item number 4
</li>
<li>Item number 5
</li>
</ul>"
The Common Lisp Project of the Day’s blog uses cl-who
only because this
is a dependency of the cl-bootstrap. Personally, I prefer spinneret
and
probably will rewrite #poftheday site to use it.
The review on Spinneret was published.