safer templating; drop domain delocalization

This commit is contained in:
Thibault Duplessis 2017-05-28 19:13:55 +02:00
parent 8de600b27e
commit ddc8076e9d
5 changed files with 37 additions and 43 deletions

View file

@ -1,8 +1,8 @@
package lila.app
package templating
import play.twirl.api.Html
import ornicar.scalalib.Zero
import play.twirl.api.Html
import lila.user.{ User, UserContext }
@ -16,30 +16,33 @@ trait StringHelper { self: NumberHelper =>
val escapeHtml = lila.common.String.html.escape _
private val escapeHtmlUnsafe = lila.common.String.html.escapeUnsafe _
def shorten(text: String, length: Int, sep: String = "…"): Html = {
val t = text.replace("\n", " ")
if (t.size > (length + sep.size)) Html(escapeHtml(t take length).body ++ sep)
if (t.size > (length + sep.size)) Html(escapeHtmlUnsafe(t take length) ++ sep)
else escapeHtml(t)
}
def shortenWithBr(text: String, length: Int) = Html {
nl2br(escapeHtml(text).body.take(length)).replace("<br /><br />", "<br />")
nl2brUnsafe(escapeHtmlUnsafe(text).take(length)).replace("<br /><br />", "<br />")
}
def pluralize(s: String, n: Int) = s"$n $s${if (n > 1) "s" else ""}"
def autoLink(text: String): Html = Html(nl2br(addUserProfileLinks(addLinks(escapeHtml(text).body))))
def autoLink(text: String): Html = nl2br(addUserProfileLinksUnsafe(addLinksUnsafe(escapeHtmlUnsafe(text))))
def nl2br(text: String) = text.replace("\r\n", "<br />").replace("\n", "<br />")
private def nl2brUnsafe(text: String): String =
text.replace("\r\n", "<br />").replace("\n", "<br />")
def nl2br(text: String) = Html(nl2brUnsafe(text))
private val markdownLinkRegex = """\[([^\[]+)\]\(([^\)]+)\)""".r
def markdownLinks(text: String) = Html {
nl2br {
markdownLinkRegex.replaceAllIn(escapeHtml(text).body, m => {
s"""<a href="${m group 2}">${m group 1}</a>"""
})
}
def markdownLinks(text: String): Html = nl2br {
markdownLinkRegex.replaceAllIn(escapeHtmlUnsafe(text), m => {
s"""<a href="${m group 2}">${m group 1}</a>"""
})
}
private val urlRegex = """(?i)\b((https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,6}/)((?:[^\s<>]+|\(([^\s<>]+|(\([^\s<>]+\)))*\))+(?:\(([^\s<>]+|(\([^\s<>]+\)))*\)|[^\s`!\[\]{};:'".,<>?«»“”‘’])))""".r
@ -49,20 +52,25 @@ trait StringHelper { self: NumberHelper =>
* @param text The text to regex match
* @return The text as a HTML hyperlink
*/
def addUserProfileLinks(text: String) = User.atUsernameRegex.replaceAllIn(text, m => {
val user = m group 1
val url = s"//$netDomain/@/$user"
def addUserProfileLinks(text: String) = Html(addUserProfileLinksUnsafe(text))
s"""<a href="$url">@$user</a>"""
})
private def addUserProfileLinksUnsafe(text: String): String =
User.atUsernameRegex.replaceAllIn(text, m => {
val user = m group 1
val url = s"//$netDomain/@/$user"
def addLinks(text: String) = try {
s"""<a href="$url">@$user</a>"""
})
def addLinks(text: String) = Html(addLinksUnsafe(text))
private def addLinksUnsafe(text: String): String = try {
urlRegex.replaceAllIn(text, m => {
if (m.group(0) contains "&quot") m.group(0)
else if (m.group(2) == "http://" || m.group(2) == "https://") {
if (s"${delocalize(m.group(3))}/" startsWith s"$netDomain/") {
if (s"${m.group(3)}/" startsWith s"$netDomain/") {
// internal
val link = delocalize(m.group(3))
val link = m.group(3)
s"""<a rel="nofollow" href="//$link">$link</a>"""
} else {
// external
@ -70,9 +78,9 @@ trait StringHelper { self: NumberHelper =>
s"""<a rel="nofollow" href="$link" target="_blank">$link</a>"""
}
} else {
if (s"${delocalize(m.group(2))}/" startsWith s"$netDomain/") {
if (s"${m.group(2)}/" startsWith s"$netDomain/") {
// internal
val link = delocalize(m.group(1))
val link = m.group(1)
s"""<a rel="nofollow" href="//$link">$link</a>"""
} else {
// external
@ -87,8 +95,6 @@ trait StringHelper { self: NumberHelper =>
text
}
private val delocalize = new lila.common.String.Delocalizer(netDomain)
def showNumber(n: Int): String = if (n > 0) s"+$n" else n.toString
implicit def lilaRichString(str: String) = new {

View file

@ -151,7 +151,7 @@ description = describeUser(u)).some) {
<strong class="name">@name</strong>
}
@profile.nonEmptyBio.ifTrue(!u.troll || ctx.is(u)).map { bio =>
<p class="bio">@Html(addUserProfileLinks(addLinks(shorten(bio, 400).body)))</p>
<p class="bio">@addUserProfileLinks(addLinks(shorten(bio, 400).body).body)</p>
}
}
<div class="stats">

View file

@ -58,7 +58,7 @@ final class ChatApi(
def clear(chatId: ChatId) = coll.remove($id(chatId)).void
def system(chatId: ChatId, text: String) = {
val line = UserLine(systemUserId, Writer delocalize text, troll = false, deleted = false)
val line = UserLine(systemUserId, text, troll = false, deleted = false)
pushLine(chatId, line) >>-
lilaBus.publish(actorApi.ChatLine(chatId, line), channelOf(chatId)) inject line.some
}
@ -155,10 +155,9 @@ final class ChatApi(
import java.util.regex.Matcher.quoteReplacement
def preprocessUserInput(in: String) = multiline(noShouting(delocalize(noPrivateUrl(in))))
def preprocessUserInput(in: String) = multiline(noShouting(noPrivateUrl(in)))
def cut(text: String) = Some(text.trim take 140) filter (_.nonEmpty)
val delocalize = new lila.common.String.Delocalizer(netDomain)
private val domainRegex = netDomain.replace(".", """\.""")
private val gameUrlRegex = (domainRegex + """\b/([\w]{8})[\w]{4}\b""").r

View file

@ -24,13 +24,6 @@ object String {
}
}
final class Delocalizer(netDomain: String) {
private val regex = ("""\w{2}\.""" + quoteReplacement(netDomain)).r
def apply(url: String) = regex.replaceAllIn(url, netDomain)
}
def shorten(text: String, length: Int, sep: String = "…") = {
val t = text.replace("\n", " ")
if (t.size > (length + sep.size)) (t take length) ++ sep
@ -53,7 +46,9 @@ object String {
object html {
// from https://github.com/android/platform_frameworks_base/blob/d59921149bb5948ffbcb9a9e832e9ac1538e05a0/core/java/android/text/TextUtils.java#L1361
def escape(s: String): Html = {
def escape(s: String): Html = Html(escapeUnsafe(s))
def escapeUnsafe(s: String): String = {
val sb = new StringBuilder
var i = 0
while (i < s.length) {
@ -69,7 +64,7 @@ object String {
}
i += 1
}
Html(sb.toString)
sb.toString
}
}
}

View file

@ -1,5 +1,5 @@
export default function(text: string, parseMoves: boolean): string {
const escaped = escapeHtml(delocalize(text));
const escaped = escapeHtml(text);
const linked = autoLink(escaped);
const plied = parseMoves && linked === escaped ? addPlies(linked) : linked;
return plied;
@ -17,12 +17,6 @@ function autoLink(html: string) {
return html.replace(linkPattern, linkReplace);
}
const delocalizePattern = /(^|[\s\n]|<[A-Za-z]*\/?>)\w{2}\.lichess\.org/gi;
function delocalize(html: string) {
return html.replace(delocalizePattern, '$1lichess.org');
}
function escapeHtml(html: string) {
return html
.replace(/&/g, "&amp;")