Skip to content

Commit

Permalink
Merge pull request #1809 from lift/escape-call
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Shadowfiend authored Sep 28, 2016
2 parents 5033c87 + 3a2fe97 commit c29eb04
Show file tree
Hide file tree
Showing 5 changed files with 411 additions and 39 deletions.
47 changes: 30 additions & 17 deletions web/webkit/src/main/scala/net/liftweb/http/HtmlNormalizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ private[http] final object HtmlNormalizer {
attributes: MetaData,
contextPath: String,
shouldRewriteUrl: Boolean, // whether to apply URLRewrite.rewriteFunc
extractInlineJavaScript: Boolean,
eventAttributes: List[EventAttribute] = Nil
): (Option[String], MetaData, List[EventAttribute]) = {
if (attributes == Null) {
Expand All @@ -100,6 +101,7 @@ private[http] final object HtmlNormalizer {
attributes.next,
contextPath,
shouldRewriteUrl,
extractInlineJavaScript,
eventAttributes
)

Expand All @@ -108,7 +110,7 @@ private[http] final object HtmlNormalizer {
EventAttribute.EventForAttribute(eventName),
attributeValue,
remainingAttributes
) if attributeValue.text.startsWith("javascript:") =>
) if attributeValue.text.startsWith("javascript:") && extractInlineJavaScript =>
val attributeJavaScript = {
// Could be javascript: or javascript://.
val base = attributeValue.text.substring(11)
Expand Down Expand Up @@ -150,7 +152,7 @@ private[http] final object HtmlNormalizer {

(id, newMetaData, remainingEventAttributes)

case UnprefixedAttribute(name, attributeValue, _) if name.startsWith("on") =>
case UnprefixedAttribute(name, attributeValue, _) if name.startsWith("on") && extractInlineJavaScript =>
val updatedEventAttributes =
EventAttribute(name.substring(2), attributeValue.text) ::
remainingEventAttributes
Expand Down Expand Up @@ -182,13 +184,20 @@ private[http] final object HtmlNormalizer {
}.foldLeft(Noop)(_ & _)
}

private[http] def normalizeElementAndAttributes(element: Elem, attributeToNormalize: String, contextPath: String, shouldRewriteUrl: Boolean): NodeAndEventJs = {
private[http] def normalizeElementAndAttributes(
element: Elem,
attributeToNormalize: String,
contextPath: String,
shouldRewriteUrl: Boolean,
extractEventJavaScript: Boolean
): NodeAndEventJs = {
val (id, normalizedAttributes, eventAttributes) =
normalizeUrlAndExtractEvents(
attributeToNormalize,
element.attributes,
contextPath,
shouldRewriteUrl
shouldRewriteUrl,
extractEventJavaScript
)

val attributesIncludingEventsAsData =
Expand Down Expand Up @@ -226,7 +235,12 @@ private[http] final object HtmlNormalizer {
}
}

private[http] def normalizeNode(node: Node, contextPath: String, stripComments: Boolean): Option[NodeAndEventJs] = {
private[http] def normalizeNode(
node: Node,
contextPath: String,
stripComments: Boolean,
extractEventJavaScript: Boolean
): Option[NodeAndEventJs] = {
node match {
case element: Elem =>
val (attributeToFix, shouldRewriteUrl) =
Expand All @@ -250,7 +264,8 @@ private[http] final object HtmlNormalizer {
element,
attributeToFix,
contextPath,
shouldRewriteUrl
shouldRewriteUrl,
extractEventJavaScript
)
)

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

/**
* Base for all the normalizeHtml* implementations; in addition to what it
* usually does, takes an `[[additionalChanges]]` function that is passed a
* state object and the current (post-normalization) node and can adjust the
* state and tweak the normalized nodes or even add more JsCmds to be
* included. That state is in turn passed to any invocations for any of the
* children of the current node. Note that state is '''not''' passed back up
* the node hierarchy, so state updates are '''only''' seen by children of
* the node.
* Normalizes `nodes` to adjust URLs with the given `contextPath`, stripping
* comments if `stripComments` is `true`, and extracting event JavaScript
* into the `js` part of the returned `NodesAndEventJs` if
* `extractEventJavaScript` is `true`.
*
* See `[[LiftMerge.merge]]` for sample usage.
*/
def normalizeHtmlAndEventHandlers(
nodes: NodeSeq,
contextPath: String,
stripComments: Boolean
stripComments: Boolean,
extractEventJavaScript: Boolean
): NodesAndEventJs = {
nodes.foldLeft(NodesAndEventJs(Vector[Node](), Noop)) { (soFar, nodeToNormalize) =>
normalizeNode(nodeToNormalize, contextPath, stripComments).map {
normalizeNode(nodeToNormalize, contextPath, stripComments, extractEventJavaScript).map {
case NodeAndEventJs(normalizedElement: Elem, js: JsCmd) =>
val NodesAndEventJs(normalizedChildren, childJs) =
normalizeHtmlAndEventHandlers(
normalizedElement.child,
contextPath,
stripComments
stripComments,
extractEventJavaScript
)

soFar
Expand Down
2 changes: 1 addition & 1 deletion web/webkit/src/main/scala/net/liftweb/http/LiftMerge.scala
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private[http] trait LiftMerge {
val bodyTail = childInfo.tailInBodyChild && ! tailInBodyChild

HtmlNormalizer
.normalizeNode(node, contextPath, stripComments)
.normalizeNode(node, contextPath, stripComments, LiftRules.extractInlineJavaScript)
.map {
case normalized @ NodeAndEventJs(normalizedElement: Elem, _) =>
val normalizedChildren =
Expand Down
35 changes: 30 additions & 5 deletions web/webkit/src/main/scala/net/liftweb/http/LiftRules.scala
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,11 @@ class LiftRules() extends Factory with FormVendor with LazyLoggable {

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

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

/**
* The attribute used to expose the names of event attributes that
* were removed from a given element for separate processing in JS.
* By default, Lift removes event attributes and attaches those
* behaviors via a separate JS file, to avoid inline JS invocations so
* that a restrictive Content-Security-Policy can be used.
* Enables or disables event attribute and script element extraction.
*
* Lift can extract script elements and event attributes like onclick,
* onchange, etc, and attach the event handlers in a separate JavaScript file
* that is generated per-page. This allows for populating these types of
* JavaScript in your snippets via CSS selector transforms, without needing
* to allow inline scripts in your content security policy (see
* `[[securityRules]]`).
*
* However, there are certain scenarios where event attribute extraction
* cannot provide a 1-to-1 reproduction of the behavior you'd get with inline
* attributes or scripts; if your application hits these scenarios and you
* would prefer not to adjust them to work with a restrictive content
* security policy, you can allow inline scripts and set
* `extractEventAttributes` to false to disable event extraction.
*/
@volatile var extractInlineJavaScript: Boolean = false

/**
* The attribute used to expose the names of event attributes that were
* removed from a given element for separate processing in JS (when
* `extractInlineJavaScript` is `true`). By default, Lift removes event
* attributes and attaches those behaviors via a separate JS file, to avoid
* inline JS invocations so that a restrictive content security policy can be
* used.
*
* You can set this variable so that the resulting HTML will have
* attribute information about the removed attributes, in case you
Expand Down
3 changes: 2 additions & 1 deletion web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1873,7 +1873,8 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri
HtmlNormalizer.normalizeHtmlAndEventHandlers(
nodes,
S.contextPath,
LiftRules.stripComments.vend
LiftRules.stripComments.vend,
LiftRules.extractInlineJavaScript
)
}

Expand Down
Loading

0 comments on commit c29eb04

Please sign in to comment.