-
Notifications
You must be signed in to change notification settings - Fork 174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Escape all strings by default #122
Conversation
Thanks for the patch. I have a few suggested changes.
|
@@ -237,8 +241,11 @@ | |||
(doall (for [expr content] | |||
(cond | |||
(vector? expr) (compile-element expr) | |||
(string? expr) `(escape-html ~expr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you know the expression is a literal string, you can just use (escape-html expr)
and precompute it. Same for the next line.
Thanks for the feedback, it is very helpful, and I agree with most of your points. There are a few things though where I would take a different approach:
You pointed out that
|
I'm going to have to disagree pretty strongly with this point. If we must introduce breaking changes, then it's better to have them all in one release, rather than spread across multiple releases. In the long term it's much less painful. The reason for this is that any breaking change, no matter how small, carries with it a huge fixed cost. The is particularly true for a language like Clojure that doesn't allow multiple versions of the same package in the same dependency tree. For example, if someone is using the Ring-Devel package, which depends on Hiccup 1.x, they won't be able to use Hiccup 2.x. So I'd prefer that people have to deal with all the issues with moving to Hiccup 2.0 once, rather than spread the pain across Hiccup 3.0, 4.0 and so forth.
RawString is the return value from We shouldn't be half-hearted in how we define APIs. Either we hide an abstraction away entirely, or we embrace it. We shouldn't have an abstraction that we expect the end user to make use of, and at the same time try and pretend it's not there.
This would work, except that "partial templates" are a very common use case in Hiccup. Typically in Hiccup you'll have functions like: (defn panel [title & body]
[:div.panel [:h2.title title] body]) And if you want to speed them up, you add a (defn panel [title & body]
(html [:div.panel [:h2.title title] body])) This precomputes a lot of the output, effectively reducing the runtime problem down to a series of string concatenations, which is much more performant. On the other hand, actually transforming the (extend-protocol compojure.response/Renderable
hiccup.compiler.RawString
(render [raw _] (str raw)) And then you could keep using If we're going to optimise for the common case, then |
See discussion at pull request weavejester#122.
See discussion at pull request weavejester#122.
See discussion at pull request weavejester#122.
See discussion at pull request weavejester#122.
See discussion at pull request weavejester#122.
All right, I tried to finish the implementation as discussed above. I hope it's ready (or very close to ready) for a merge now.
Got it. To be honest, I would probably go with a backwards-compatible change (e.g. keep
Let me put it in a different way. The semantics of the RawString class is something like this: "it is a value that is almost a string, but It is true that it is more convenient if there are nested So I would not optimize for convenience of the nested Anyway, it is my last wall-of-text ;) I just wanted to make sure that this change does not make things more difficult than necessary... If you change your mind, I would be happy to modify the code. Or I will finish it this way (if it still needs improvements). |
Unfortunately, |
My preference is to favour consistency over convenience in APIs. If Even if a user never nests Further, returning a custom type by default opens the way for additional functionality in future without breaking backward compatibility. At the moment we simply wrap a string, but we could potentially wrap a more complex structure that would allow, for instance, rendering with different whitespace options, or for different doctypes. The fundamental problem with Hiccup 1.x is that strings are not a rich enough data type to represent everything we might want to communicate in the output. Not escaping by default is merely the most visible problem caused by the underlying design decision to rely on strings. I also don't think we're losing much in terms of convenience. At most we're introducing an additional six characters the user has to type. At least we introduce zero and everything works as before. That seems a small price to pay for a more consistent, functional, and future-proof API. |
I lost track of this PR a little over Christmas. I believe there's still a little work to do on it? I'll add some notes to the source code. |
@@ -152,7 +156,7 @@ | |||
"True if x is a literal value that can be rendered as-is." | |||
[x] | |||
(and (not (unevaluated? x)) | |||
(or (not (or (vector? x) (map? x))) | |||
(or (not (or (vector? x) (map? x) (set? x))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sets aren't used in Hiccup. Not sure why this is here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I misunderstood the intention of this code. This escapes literals when they are not treated specially (e.g. when you write [:span {} {:foo "bar"}]
- not that it is too useful...). I thought it would be nice to treat sets the same way. But I revert this commit then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But sets aren't used in Hiccup, so (set? x)
will never be true.
Aside from those two points, I think this PR can be squashed and merged. |
See discussion at pull request weavejester#122.
Sorry for the long delay... I modified the code according to your requests. If it's OK, I will squash the commits (the last commit is quite large, so I thought it's easier to review it this way). |
Object | ||
(^String toString [this] s) | ||
(^boolean equals [this other] | ||
(cond |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe instead write this as:
(and (instance? RawString other) (= s (.toString other)))
Thanks for the update! I have one small code suggestion, and if you could remove the Squashing the commits is a good idea :) |
This commit implements issue weavejester#114 (Safe HTML by default). It introduces a few breaking changes: 1. All strings that are passed to `html` are escaped. To support raw, unescaped strings a new object type (RawString) was introduced. Use `hiccup.util/raw-string` to prevent a string from being escaped. 2. To allow the nested usage of the `html` macro, `html` now returns a RawString object instead of a string. It can be converted to a string with the `str` function. 3. The `h` helper function was removed.
Done. I also removed an unnecessary test: checking whether |
Thanks for the updated commit! It looks good. I'll find some time this weekend to give it a last look over and manual test. If all goes well, I'll merge. |
Thanks for all your hard work on this PR, and thanks for being patient with my feedback :) Incidentally, I think I've changed my mind about dropping |
This commit implements issue #114 (Safe HTML by default). It introduces
a few breaking changes:
string literals or expressions will not work. Use
hiccup.util/raw-str
to prevent these values from being escaped.
hiccup.util/escape-html
,hiccup.core/h
andhiccup.core/html
returns a RawString object instead of a String. They can be converted to
java.lang.String with the built-in
str
function.Note that unlike
hiccup.util/html
, macros inhiccup.page
(html4
,html5
,xhtml
) still return a string so that their result can bepassed directly to Ring. In the rare cases when the output is embedded
to another html page (e.g. into an iframe) it should be also tagged with
hiccup.util/raw-str
.