Or see the list of project sponsors.
Documentation | 😀 |
Docstrings | 😀 |
Tests | 😀 |
Examples | 😀 |
RepositoryActivity | 🥺 |
CI | 🥺 |
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.