Skip to content

Commit c29eb04

Browse files
authored
Merge pull request #1809 from lift/escape-call
Escape Call: Add LiftRules.extractInlineJavaScript. Disabled by default, this can be set to true as users are ready to crank up their content security policy settings. Should be fairly self-explanatory… The only question is whether to default it to true or false. Given the major version bump and the breaking nature of having this enabled to pre-3.0 code, we've gone with setting it to false by default.
2 parents 5033c87 + 3a2fe97 commit c29eb04

5 files changed

Lines changed: 411 additions & 39 deletions

File tree

web/webkit/src/main/scala/net/liftweb/http/HtmlNormalizer.scala

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private[http] final object HtmlNormalizer {
8787
attributes: MetaData,
8888
contextPath: String,
8989
shouldRewriteUrl: Boolean, // whether to apply URLRewrite.rewriteFunc
90+
extractInlineJavaScript: Boolean,
9091
eventAttributes: List[EventAttribute] = Nil
9192
): (Option[String], MetaData, List[EventAttribute]) = {
9293
if (attributes == Null) {
@@ -100,6 +101,7 @@ private[http] final object HtmlNormalizer {
100101
attributes.next,
101102
contextPath,
102103
shouldRewriteUrl,
104+
extractInlineJavaScript,
103105
eventAttributes
104106
)
105107

@@ -108,7 +110,7 @@ private[http] final object HtmlNormalizer {
108110
EventAttribute.EventForAttribute(eventName),
109111
attributeValue,
110112
remainingAttributes
111-
) if attributeValue.text.startsWith("javascript:") =>
113+
) if attributeValue.text.startsWith("javascript:") && extractInlineJavaScript =>
112114
val attributeJavaScript = {
113115
// Could be javascript: or javascript://.
114116
val base = attributeValue.text.substring(11)
@@ -150,7 +152,7 @@ private[http] final object HtmlNormalizer {
150152

151153
(id, newMetaData, remainingEventAttributes)
152154

153-
case UnprefixedAttribute(name, attributeValue, _) if name.startsWith("on") =>
155+
case UnprefixedAttribute(name, attributeValue, _) if name.startsWith("on") && extractInlineJavaScript =>
154156
val updatedEventAttributes =
155157
EventAttribute(name.substring(2), attributeValue.text) ::
156158
remainingEventAttributes
@@ -182,13 +184,20 @@ private[http] final object HtmlNormalizer {
182184
}.foldLeft(Noop)(_ & _)
183185
}
184186

185-
private[http] def normalizeElementAndAttributes(element: Elem, attributeToNormalize: String, contextPath: String, shouldRewriteUrl: Boolean): NodeAndEventJs = {
187+
private[http] def normalizeElementAndAttributes(
188+
element: Elem,
189+
attributeToNormalize: String,
190+
contextPath: String,
191+
shouldRewriteUrl: Boolean,
192+
extractEventJavaScript: Boolean
193+
): NodeAndEventJs = {
186194
val (id, normalizedAttributes, eventAttributes) =
187195
normalizeUrlAndExtractEvents(
188196
attributeToNormalize,
189197
element.attributes,
190198
contextPath,
191-
shouldRewriteUrl
199+
shouldRewriteUrl,
200+
extractEventJavaScript
192201
)
193202

194203
val attributesIncludingEventsAsData =
@@ -226,7 +235,12 @@ private[http] final object HtmlNormalizer {
226235
}
227236
}
228237

229-
private[http] def normalizeNode(node: Node, contextPath: String, stripComments: Boolean): Option[NodeAndEventJs] = {
238+
private[http] def normalizeNode(
239+
node: Node,
240+
contextPath: String,
241+
stripComments: Boolean,
242+
extractEventJavaScript: Boolean
243+
): Option[NodeAndEventJs] = {
230244
node match {
231245
case element: Elem =>
232246
val (attributeToFix, shouldRewriteUrl) =
@@ -250,7 +264,8 @@ private[http] final object HtmlNormalizer {
250264
element,
251265
attributeToFix,
252266
contextPath,
253-
shouldRewriteUrl
267+
shouldRewriteUrl,
268+
extractEventJavaScript
254269
)
255270
)
256271

@@ -263,30 +278,28 @@ private[http] final object HtmlNormalizer {
263278
}
264279

265280
/**
266-
* Base for all the normalizeHtml* implementations; in addition to what it
267-
* usually does, takes an `[[additionalChanges]]` function that is passed a
268-
* state object and the current (post-normalization) node and can adjust the
269-
* state and tweak the normalized nodes or even add more JsCmds to be
270-
* included. That state is in turn passed to any invocations for any of the
271-
* children of the current node. Note that state is '''not''' passed back up
272-
* the node hierarchy, so state updates are '''only''' seen by children of
273-
* the node.
281+
* Normalizes `nodes` to adjust URLs with the given `contextPath`, stripping
282+
* comments if `stripComments` is `true`, and extracting event JavaScript
283+
* into the `js` part of the returned `NodesAndEventJs` if
284+
* `extractEventJavaScript` is `true`.
274285
*
275286
* See `[[LiftMerge.merge]]` for sample usage.
276287
*/
277288
def normalizeHtmlAndEventHandlers(
278289
nodes: NodeSeq,
279290
contextPath: String,
280-
stripComments: Boolean
291+
stripComments: Boolean,
292+
extractEventJavaScript: Boolean
281293
): NodesAndEventJs = {
282294
nodes.foldLeft(NodesAndEventJs(Vector[Node](), Noop)) { (soFar, nodeToNormalize) =>
283-
normalizeNode(nodeToNormalize, contextPath, stripComments).map {
295+
normalizeNode(nodeToNormalize, contextPath, stripComments, extractEventJavaScript).map {
284296
case NodeAndEventJs(normalizedElement: Elem, js: JsCmd) =>
285297
val NodesAndEventJs(normalizedChildren, childJs) =
286298
normalizeHtmlAndEventHandlers(
287299
normalizedElement.child,
288300
contextPath,
289-
stripComments
301+
stripComments,
302+
extractEventJavaScript
290303
)
291304

292305
soFar

web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ private[http] trait LiftMerge {
164164
val bodyTail = childInfo.tailInBodyChild && ! tailInBodyChild
165165

166166
HtmlNormalizer
167-
.normalizeNode(node, contextPath, stripComments)
167+
.normalizeNode(node, contextPath, stripComments, LiftRules.extractInlineJavaScript)
168168
.map {
169169
case normalized @ NodeAndEventJs(normalizedElement: Elem, _) =>
170170
val normalizedChildren =

web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,11 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable {
403403

404404
/**
405405
* Holds the JS library specific UI artifacts. By default it uses JQuery's artifacts
406+
*
407+
* Please note that currently any setting other than `JQueryArtifacts` will switch
408+
* you to using Lift's liftVanilla implementation, which is meant to work independent
409+
* of any framework. '''This implementation is experimental in Lift 3.0, so use it at
410+
* your own risk and make sure you test your application!'''
406411
*/
407412
@volatile var jsArtifacts: JSArtifacts = JQueryArtifacts
408413

@@ -570,11 +575,31 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable {
570575
@volatile var displayHelpfulSiteMapMessages_? = true
571576

572577
/**
573-
* The attribute used to expose the names of event attributes that
574-
* were removed from a given element for separate processing in JS.
575-
* By default, Lift removes event attributes and attaches those
576-
* behaviors via a separate JS file, to avoid inline JS invocations so
577-
* that a restrictive Content-Security-Policy can be used.
578+
* Enables or disables event attribute and script element extraction.
579+
*
580+
* Lift can extract script elements and event attributes like onclick,
581+
* onchange, etc, and attach the event handlers in a separate JavaScript file
582+
* that is generated per-page. This allows for populating these types of
583+
* JavaScript in your snippets via CSS selector transforms, without needing
584+
* to allow inline scripts in your content security policy (see
585+
* `[[securityRules]]`).
586+
*
587+
* However, there are certain scenarios where event attribute extraction
588+
* cannot provide a 1-to-1 reproduction of the behavior you'd get with inline
589+
* attributes or scripts; if your application hits these scenarios and you
590+
* would prefer not to adjust them to work with a restrictive content
591+
* security policy, you can allow inline scripts and set
592+
* `extractEventAttributes` to false to disable event extraction.
593+
*/
594+
@volatile var extractInlineJavaScript: Boolean = false
595+
596+
/**
597+
* The attribute used to expose the names of event attributes that were
598+
* removed from a given element for separate processing in JS (when
599+
* `extractInlineJavaScript` is `true`). By default, Lift removes event
600+
* attributes and attaches those behaviors via a separate JS file, to avoid
601+
* inline JS invocations so that a restrictive content security policy can be
602+
* used.
578603
*
579604
* You can set this variable so that the resulting HTML will have
580605
* attribute information about the removed attributes, in case you

web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1873,7 +1873,8 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri
18731873
HtmlNormalizer.normalizeHtmlAndEventHandlers(
18741874
nodes,
18751875
S.contextPath,
1876-
LiftRules.stripComments.vend
1876+
LiftRules.stripComments.vend,
1877+
LiftRules.extractInlineJavaScript
18771878
)
18781879
}
18791880

0 commit comments

Comments
 (0)