Skip to content

Commit

Permalink
Optimize escape functions
Browse files Browse the repository at this point in the history
- Use java.lang.StringBuilder instead of scala wrapper
- Rewrite Html.buildString to use bulk copy methods
  when possible.
  • Loading branch information
isaacl committed Aug 20, 2018
1 parent af0de40 commit 9fa13fd
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 19 deletions.
13 changes: 7 additions & 6 deletions api/shared/src/main/scala/play/twirl/api/Content.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package play.twirl.api

import scala.collection.immutable
import java.lang.{StringBuilder => jStringBuilder}

/**
* Generic type representing content to be sent over an HTTP response.
Expand Down Expand Up @@ -33,13 +34,13 @@ trait Content {
* @tparam A self-type
*/
abstract class BufferedContent[A <: BufferedContent[A]](protected val elements: immutable.Seq[A], protected val text: String) extends Appendable[A] with Content { this: A =>
protected def buildString(builder: StringBuilder): Unit = {
protected def buildString(sb: jStringBuilder): Unit = {
if (!elements.isEmpty) {
elements.foreach { e =>
e.buildString(builder)
e.buildString(sb)
}
} else {
builder.append(text)
sb.append(text)
}
}

Expand All @@ -48,9 +49,9 @@ abstract class BufferedContent[A <: BufferedContent[A]](protected val elements:
* to avoid unneeded memory allocation.
*/
private lazy val builtBody = {
val builder = new StringBuilder()
buildString(builder)
builder.toString
val sb = new jStringBuilder()
buildString(sb)
sb.toString
}

override def toString = builtBody
Expand Down
33 changes: 22 additions & 11 deletions api/shared/src/main/scala/play/twirl/api/Formats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package play.twirl.api

import play.twirl.api.utils.StringEscapeUtils
import scala.collection.immutable
import java.lang.{StringBuilder => jStringBuilder}

object MimeTypes {
val TEXT = "text/plain"
Expand Down Expand Up @@ -36,28 +37,38 @@ class Html private[api] (elements: immutable.Seq[Html], text: String, escape: Bo
* of Strings, if it doesn't, performance actually goes down (measured 10%), due to the fact that the JVM can't
* optimise the invocation of buildString as well because there are two different possible implementations.
*/
override protected def buildString(builder: StringBuilder): Unit = {
if (elements.nonEmpty) {
override protected def buildString(sb: jStringBuilder): Unit = {
if (!elements.isEmpty) {
elements.foreach { e =>
e.buildString(builder)
e.buildString(sb)
}
} else if (escape) {
// Using our own algorithm here because commons lang escaping wasn't designed for protecting against XSS, and there
// don't seem to be any other good generic escaping tools out there.
val len = text.length
var copyIdx = 0
var i = 0
while (i < text.length) {
while (i < len) {
text.charAt(i) match {
case '<' => builder.append("&lt;")
case '>' => builder.append("&gt;")
case '"' => builder.append("&quot;")
case '\'' => builder.append("&#x27;")
case '&' => builder.append("&amp;")
case c => builder += c
case '<' | '>' | '"' | '\'' | '&' => {
sb.append(text, copyIdx, i)
text.charAt(i) match {
case '<' => sb.append("&lt;")
case '>' => sb.append("&gt;")
case '"' => sb.append("&quot;")
case '\'' => sb.append("&#x27;")
case '&' => sb.append("&amp;")
}
copyIdx = i + 1
}
case _ => ()
}
i += 1
}
if (copyIdx == 0) sb.append(text)
else sb.append(text, copyIdx, len)
} else {
builder.append(text)
sb.append(text)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
*/
package play.twirl.api.utils

import java.lang.{StringBuilder => jStringBuilder}

object StringEscapeUtils {

def escapeEcmaScript(input: String): String = {
val s = new StringBuilder()
val len = input.length
val s = new jStringBuilder(len)
var pos = 0
while (pos < len) {
input.charAt(pos) match {
Expand Down Expand Up @@ -36,8 +38,8 @@ object StringEscapeUtils {
def escapeXml11(input: String): String = {
// Implemented per XML spec:
// http://www.w3.org/International/questions/qa-controls
val s = new StringBuilder()
val len = input.length
val s = new jStringBuilder(len)
var pos = 0

while (pos < len) {
Expand Down

0 comments on commit 9fa13fd

Please sign in to comment.