Browse files

- Support .scala.js files as JavaScript templates

- Fix the scala/websocket-chat sample to prevent XSS attacks
  • Loading branch information...
1 parent 75f0f52 commit 0b0b37dd09543ecd3608e253b2267d2db2482396 @julienrf julienrf committed Apr 3, 2013
View
6 framework/src/play/src/main/scala/play/api/http/ContentTypeOf.scala
@@ -59,6 +59,12 @@ trait DefaultContentTypeOfs {
}
/**
+ * Default content type for `JavaScript` values.
+ */
+ implicit def contentTypeOf_JavaScript(implicit codec: Codec): ContentTypeOf[JavaScript] =
+ ContentTypeOf[JavaScript](Some(ContentTypes.JAVASCRIPT))
+
+ /**
* Default content type for `String` values (`text/plain`).
*/
implicit def contentTypeOf_String(implicit codec: Codec): ContentTypeOf[String] = {
View
39 framework/src/play/src/main/scala/play/api/templates/Templates.scala
@@ -3,6 +3,7 @@ package play.api.templates
import play.api.mvc._
import play.templates._
import play.api.http.MimeTypes
+import org.apache.commons.lang3.StringEscapeUtils
/**
@@ -174,6 +175,44 @@ object XmlFormat extends Format[Xml] {
}
+/**
+ * Type used in default JavaScript templates.
+ */
+class JavaScript(buffer: StringBuilder) extends BufferedContent[JavaScript](buffer) {
+ /**
+ * Content type of JavaScript
+ */
+ val contentType = MimeTypes.JAVASCRIPT
+}
+
+/**
+ * Helper for JavaScript utility methods.
+ */
+object JavaScript {
+ /**
+ * Creates a JavaScript fragment with initial content specified
+ */
+ def apply(content: String) = new JavaScript(new StringBuilder(content))
+}
+
+/**
+ * Formatter for JavaScript content.
+ */
+object JavaScriptFormat extends Format[JavaScript] {
+ /**
+ * Integrate `text` without performing any escaping process.
+ * @param text Text to integrate
+ */
+ def raw(text: String): JavaScript = JavaScript(text)
+
+ /**
+ * Escapes `text` using JavaScript String rules.
+ * @param text Text to integrate
+ */
+ def escape(text: String): JavaScript = JavaScript(StringEscapeUtils.escapeEcmaScript(text))
+
+}
+
/** Defines a magic helper for Play templates. */
object PlayMagic {
View
3 framework/src/sbt-plugin/src/main/scala/PlaySettings.scala
@@ -224,7 +224,8 @@ trait PlaySettings {
templatesTypes := Map(
"html" -> "play.api.templates.HtmlFormat",
"txt" -> "play.api.templates.TxtFormat",
- "xml" -> "play.api.templates.XmlFormat"
+ "xml" -> "play.api.templates.XmlFormat",
+ "js" -> "play.api.templates.JavaScriptFormat"
)
)
View
4 samples/scala/websocket-chat/app/controllers/Application.scala
@@ -32,6 +32,10 @@ object Application extends Controller {
)
}
}
+
+ def chatRoomJs(username: String) = Action { implicit request =>
+ Ok(views.js.chatRoom(username))
+ }
/**
* Handles the chat websocket.
View
57 samples/scala/websocket-chat/app/views/chatRoom.scala.html
@@ -25,61 +25,6 @@
</div>
</div>
- <script type="text/javascript" charset="utf-8">
-
- $(function() {
-
- var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
- var chatSocket = new WS("@routes.Application.chat(username).webSocketURL()")
-
- var sendMessage = function() {
- chatSocket.send(JSON.stringify(
- {text: $("#talk").val()}
- ))
- $("#talk").val('')
- }
-
- var receiveEvent = function(event) {
- var data = JSON.parse(event.data)
-
- // Handle errors
- if(data.error) {
- chatSocket.close()
- $("#onError span").text(data.error)
- $("#onError").show()
- return
- } else {
- $("#onChat").show()
- }
-
- // Create the message element
- var el = $('<div class="message"><span></span><p></p></div>')
- $("span", el).text(data.user)
- $("p", el).text(data.message)
- $(el).addClass(data.kind)
- if(data.user == '@username') $(el).addClass('me')
- $('#messages').append(el)
-
- // Update the members list
- $("#members").html('')
- $(data.members).each(function() {
- $("#members").append('<li>' + this + '</li>')
- })
- }
-
- var handleReturnKey = function(e) {
- if(e.charCode == 13 || e.keyCode == 13) {
- e.preventDefault()
- sendMessage()
- }
- }
-
- $("#talk").keypress(handleReturnKey)
-
- chatSocket.onmessage = receiveEvent
-
- })
-
- </script>
+ <script type="text/javascript" charset="utf-8" src="@routes.Application.chatRoomJs(username)"></script>
}
View
56 samples/scala/websocket-chat/app/views/chatRoom.scala.js
@@ -0,0 +1,56 @@
+@(username: String)(implicit r: RequestHeader)
+
+$(function() {
+
+ var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
+ var chatSocket = new WS("@routes.Application.chat(username).webSocketURL()")
+
+ var sendMessage = function() {
+ chatSocket.send(JSON.stringify(
+ {text: $("#talk").val()}
+ ))
+ $("#talk").val('')
+ }
+
+ var receiveEvent = function(event) {
+ var data = JSON.parse(event.data)
+
+ // Handle errors
+ if(data.error) {
+ chatSocket.close()
+ $("#onError span").text(data.error)
+ $("#onError").show()
+ return
+ } else {
+ $("#onChat").show()
+ }
+
+ // Create the message element
+ var el = $('<div class="message"><span></span><p></p></div>')
+ $("span", el).text(data.user)
+ $("p", el).text(data.message)
+ $(el).addClass(data.kind)
+ if(data.user == '@username') $(el).addClass('me')
+ $('#messages').append(el)
+
+ // Update the members list
+ $("#members").html('')
+ $(data.members).each(function() {
+ var li = document.createElement('li');
+ li.textContent = this;
+ $("#members").append(li);
+ })
+ }
+
+ var handleReturnKey = function(e) {
+ if(e.charCode == 13 || e.keyCode == 13) {
+ e.preventDefault()
+ sendMessage()
+ }
+ }
+
+ $("#talk").keypress(handleReturnKey)
+
+ chatSocket.onmessage = receiveEvent
+
+})
View
2 samples/scala/websocket-chat/app/views/main.scala.html
@@ -23,7 +23,7 @@
<a href="@routes.Application.index()">Disconnect</a>
</p>
}.getOrElse {
- <form action="@routes.Application.chatRoom()" class="pull-right">
+ <form action="@routes.Application.chatRoom(None)" class="pull-right">
<input id="username" name="username" class="input-small" type="text" placeholder="Username">
<button class="btn" type="submit">Sign in</button>
</form>
View
9 samples/scala/websocket-chat/conf/routes
@@ -3,9 +3,10 @@
# ~~~~
# Home page
-GET / controllers.Application.index
-GET /room controllers.Application.chatRoom(username: Option[String] ?= None)
-GET /room/chat controllers.Application.chat(username)
+GET / controllers.Application.index
+GET /room controllers.Application.chatRoom(username: Option[String])
+GET /room/chat controllers.Application.chat(username)
+GET /assets/javascripts/chatroom.js controllers.Application.chatRoomJs(username: String)
# Map static resources from the /public folder to the /assets URL path
-GET /assets/*file controllers.Assets.at(path="/public", file)
+GET /assets/*file controllers.Assets.at(path="/public", file)

0 comments on commit 0b0b37d

Please sign in to comment.