Easy HTML/Javascript JSON escaping in Scalatra
Join the DZone community and get the full member experience.
Join For Free
scalatra
is a small web-framework that allows to easily build rest api for web applications. we are using it in two projects to serve json data consumed by angularjs based front-ends. and in one of this project we had a problem with text messages that could contain html/javascript elements. user could save message and then this message could be used on two pages. on first we need to display escaped data (read-only view) and on second (edit page) we wanted to display message in the exactly same way as user typed it.
so basically we needed easy and declarative way to define which scalatra rest end-points should return escaped and not-escaped json data.
basic scalatra project
to show how it could be accomplished, we need a simple sample project using scalatra. i didn’t want to create it from scratch by myself so i removed some swagger related stuff from project by my colleague krzysztof ciesielski (
his post about scalatra and swagger
) and i was ready to go
full commit with this basic project is available
here
, but the most interesting class is shown below:
class exampleservlet() extends scalatraservlet with jacksonjsonsupport with jvalueresult { protected implicit val jsonformats: formats = defaultformats val messages = list( message(1, "<script><alert>script hacker</alert></script>", "johny hacker", new date()), message(2, "decent message", "john smith", new date()), message(3, "message with <b>bolded fragment</b>", "kate norton", new date()) ) before() { contenttype = formats("json") } get("/") { messages } get("/:id") { val id = params("id").toint; val messageoptional = messages.find((m: message) => m.id == id) log("optional =" + messageoptional) messageoptional match { case some(e) => e case _ => } } } case class message(id: int, text: string, author: string, created: date)
ok, so what we have here? simple rest service that returns json. get method without any params returns list of messages and get with passed id returns single message with given id. for the sake of simplicity, i have hardcoded list of three messages with two of them containing some html/js elements.
definition of done
it is always good to have clear definition of done so in our case there are two things we want our methods to do:
- get /rest/example without parameters should return list of messages with escaped content
- get /rest/example/1 (with parameter) should return single messages without escaping
implementation details
to add escaping we need to find place in scalatra where objects returned from method are converted to json. after some digging i found jacksonjsonoutput trait with method writejon :
trait jacksonjsonoutput extends jsonoutput[jvalue] with jackson.jsonmethods { protected def writejson(json: jvalue, writer: writer) { mapper.writevalue(writer, json) } }
so what we have to do is override method and somehow define logic to perform actual escaping for us. luckily jvalue has function map that allows to apply passed function to every value in json. so we could fire escapehtml4 method from apache commons lang on each string value in json:
override def writejson(json: jvalue, writer: writer) { val escapedjson = json.map((x: jvalue) => x match { // let's check what type is this element case jstring(y) => jstring(escapehtml4(y)) // if it is a string, escape it case _ => x // keep value of other type as is } ) mapper.writevalue(writer, escapedjson) }
as you can see all logic is done by map function, we only need to add a few lines of code that will filter and escape string values in json. so far so good, but now we are escaping every methods from our rest api. this is not exactly what we wanted to achieve. to get it done right, we need to define a mechanism to disable escaping for a specific method.
wrapping marker object
so we need a marker “something” (class, interface or trait) that will tell
writejson
method which data shouldn’t be escaped.
and after trying different approaches it turned out that only thing we need is to wrap value returned from method in a marker object and then in
writejson
check if its is wrapped or not and escape only not wrapped data. so workflow looks as follows:
- if we dont’ want escaping, wrap returned object in a notescapedjsonwrapper
- in writejson check if object to write is a wrapper. if so, only unwrap it and write. it it is not a wrapper, escape all strings inside object and wride data after that
looks easy. we need simple wrapper class:
case class notescapedjsonwrapper[t](notescapeddata: t)
and we need to wrap response with this object:
get("/:id") { val id = params("id").toint; val messageoptional = messages.find((m: message) => m.id == id) log("optional =" + messageoptional) messageoptional match { case some(e) => notescapedjsonwrapper(e) // wrap data that we don't want to be escaped case _ => } }
and after that we have to modify writejson to check if object to be written is wrapped with “not-escape-me” marker object or not.
override def writejson(json: jvalue, writer: writer) { (json \ "notescapeddata") match { // check if json contains field 'notescapeddata' meaning that it is wrapping object case jnothing => { // no wrapper, so we perform escaping val escapedjson = json.map((x: jvalue) => x match { case jstring(y) => jstring(escapehtml4(y)) case _ => x } ) mapper.writevalue(writer, escapedjson) } case _ => { // field 'notescapeddata' detected, unwrap and return clean object mapper.writevalue(writer, json \ "notescapeddata") } } }
and after that we could compile and start container with our small application. when we enter /rest/example we will get list of all messages with every string properly escaped. and if we request /rest/example/1 we will get single message as not-escaped data. that’s all, we have our escaping working.
summary
in this short post i’ve described how to implement html/js escaping in scalatra based application in a pretty straightforward way that can be easily defined in one, common place without need to add manual escaping in every method that needs that. thanks to this, our application is much safer and only explicitly declared methods can return potentially unsafe data.
source code of this small example app is available
on github
.
Published at DZone with permission of Tomasz Dziurko, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments