From f0152093e7732b4ccbb56bcbe399ff0371e8305a Mon Sep 17 00:00:00 2001
From: "moandji.ezana"
Date: Thu, 29 Nov 2012 00:01:45 +0200
Subject: [PATCH] Mentions are expanded in rendered Essay. Page title reflects
Essay title.
---
pom.xml | 7 ++-
.../tent/essayist/EssayServlet.java | 2 +-
.../tent/essayist/EssaysServlet.java | 9 ++--
.../tent/essayist/PreviewServlet.java | 15 +++---
.../tent/essayist/WriteServlet.java | 27 +++-------
.../tent/essayist/config/JamonContext.java | 5 +-
.../tent/essayist/security/Csrf.java | 16 ++++--
.../essayist/text/TextTransformation.java | 53 +++++++++++++++++++
.../tent/essayist/EssayPage.jamon | 2 +-
.../moandjiezana/tent/essayist/Layout.jamon | 3 +-
.../essayist/partials/ReactionTemplate.jamon | 2 +-
src/main/webapp/assets/essayist.js | 15 +++---
.../tent/essayist/security/CsrfTest.java | 21 +++++---
.../essayist/text/TextTransformationTest.java | 30 +++++++++++
14 files changed, 151 insertions(+), 56 deletions(-)
create mode 100644 src/main/java/com/moandjiezana/tent/essayist/text/TextTransformation.java
create mode 100644 src/test/java/com/moandjiezana/tent/essayist/text/TextTransformationTest.java
diff --git a/pom.xml b/pom.xml
index 7976918..66138fc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -82,7 +82,7 @@
owasp-java-html-sanitizer
owasp-java-html-sanitizer
- r117
+ r129
junit
@@ -143,5 +143,10 @@
guava
13.0.1
+
+ com.moandjiezana.tent
+ tent-text
+ 1.0.0-SNAPSHOT
+
diff --git a/src/main/java/com/moandjiezana/tent/essayist/EssayServlet.java b/src/main/java/com/moandjiezana/tent/essayist/EssayServlet.java
index f329374..1e794a6 100644
--- a/src/main/java/com/moandjiezana/tent/essayist/EssayServlet.java
+++ b/src/main/java/com/moandjiezana/tent/essayist/EssayServlet.java
@@ -64,7 +64,7 @@ public class EssayServlet extends HttpServlet {
Post post = tentClient.getPost(essayId);
EssayistPostContent essayContent = post.getContentAs(EssayistPostContent.class);
- essayContent.setBody(csrf.stripScripts(essayContent.getBody()));
+ essayContent.setBody(csrf.permissive(essayContent.getBody()));
EssayPage essayPage = templates.essay();
if (user.owns(post)) {
diff --git a/src/main/java/com/moandjiezana/tent/essayist/EssaysServlet.java b/src/main/java/com/moandjiezana/tent/essayist/EssaysServlet.java
index 5b26bd7..108ebdc 100644
--- a/src/main/java/com/moandjiezana/tent/essayist/EssaysServlet.java
+++ b/src/main/java/com/moandjiezana/tent/essayist/EssaysServlet.java
@@ -6,6 +6,7 @@ import com.moandjiezana.tent.client.posts.PostQuery;
import com.moandjiezana.tent.client.posts.content.EssayContent;
import com.moandjiezana.tent.client.users.Permissions;
import com.moandjiezana.tent.essayist.tent.Entities;
+import com.moandjiezana.tent.essayist.text.TextTransformation;
import java.io.IOException;
import java.util.List;
@@ -18,18 +19,18 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.pegdown.PegDownProcessor;
-
@Singleton
public class EssaysServlet extends HttpServlet {
private Templates templates;
private Users users;
private Provider sessions;
+ private TextTransformation textTransformation;
@Inject
- public EssaysServlet(Users users, Templates templates, Provider sessions) {
+ public EssaysServlet(Users users, TextTransformation textTransformation, Templates templates, Provider sessions) {
this.users = users;
+ this.textTransformation = textTransformation;
this.templates = templates;
this.sessions = sessions;
}
@@ -62,7 +63,7 @@ public class EssaysServlet extends HttpServlet {
post.setLicenses(new String[] { "http://creativecommons.org/licenses/by/3.0/" });
EssayContent essay = new EssayContent();
essay.setTitle(req.getParameter("title"));
- essay.setBody(new PegDownProcessor().markdownToHtml(req.getParameter("body")));
+ essay.setBody(textTransformation.transformEssay(req.getParameter("body")));
essay.setExcerpt(req.getParameter("excerpt"));
post.setContent(essay);
diff --git a/src/main/java/com/moandjiezana/tent/essayist/PreviewServlet.java b/src/main/java/com/moandjiezana/tent/essayist/PreviewServlet.java
index e375552..09af82b 100644
--- a/src/main/java/com/moandjiezana/tent/essayist/PreviewServlet.java
+++ b/src/main/java/com/moandjiezana/tent/essayist/PreviewServlet.java
@@ -2,7 +2,7 @@ package com.moandjiezana.tent.essayist;
import com.google.common.io.CharStreams;
import com.moandjiezana.tent.essayist.auth.Authenticated;
-import com.moandjiezana.tent.essayist.security.Csrf;
+import com.moandjiezana.tent.essayist.text.TextTransformation;
import java.io.IOException;
@@ -13,25 +13,22 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.pegdown.PegDownProcessor;
-
@Singleton
@Authenticated
public class PreviewServlet extends HttpServlet {
- private final Csrf csrf;
+ private final TextTransformation textTransformation;
@Inject
- public PreviewServlet(Csrf csrf) {
- this.csrf = csrf;
+ public PreviewServlet(TextTransformation textTransformation) {
+ this.textTransformation = textTransformation;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String body = CharStreams.toString(req.getReader());
- String html = new PegDownProcessor().markdownToHtml(body);
- String sanitized = csrf.stripScripts(html);
+ String essay = textTransformation.transformEssay(body);
- resp.getWriter().write(sanitized);
+ resp.getWriter().write(essay);
}
}
diff --git a/src/main/java/com/moandjiezana/tent/essayist/WriteServlet.java b/src/main/java/com/moandjiezana/tent/essayist/WriteServlet.java
index 4482d5c..18f16b3 100644
--- a/src/main/java/com/moandjiezana/tent/essayist/WriteServlet.java
+++ b/src/main/java/com/moandjiezana/tent/essayist/WriteServlet.java
@@ -11,6 +11,7 @@ import com.moandjiezana.tent.client.users.Permissions;
import com.moandjiezana.tent.essayist.auth.Authenticated;
import com.moandjiezana.tent.essayist.config.Routes;
import com.moandjiezana.tent.essayist.tent.Entities;
+import com.moandjiezana.tent.essayist.text.TextTransformation;
import java.io.IOException;
import java.util.List;
@@ -23,8 +24,6 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.pegdown.PegDownProcessor;
-
@Singleton
@Authenticated
public class WriteServlet extends HttpServlet {
@@ -33,9 +32,11 @@ public class WriteServlet extends HttpServlet {
private Provider sessions;
private Tasks tasks;
private Provider routes;
+ private TextTransformation textTransformation;
@Inject
- public WriteServlet(Provider sessions, Templates templates, Provider routes, Tasks tasks) {
+ public WriteServlet(TextTransformation textTransformation, Provider sessions, Templates templates, Provider routes, Tasks tasks) {
+ this.textTransformation = textTransformation;
this.sessions = sessions;
this.templates = templates;
this.routes = routes;
@@ -71,7 +72,7 @@ public class WriteServlet extends HttpServlet {
EssayContent essay = new EssayContent();
essay.setTitle(req.getParameter("title"));
final String body = req.getParameter("body");
- essay.setBody(new PegDownProcessor().markdownToHtml(body));
+ essay.setBody(textTransformation.transformEssay(body));
essay.setExcerpt(req.getParameter("excerpt"));
post.setContent(essay);
@@ -89,27 +90,11 @@ public class WriteServlet extends HttpServlet {
tentClient.write(metadataPost);
resp.sendRedirect(req.getContextPath() + "/" + Entities.getForUrl(tentClient.getProfile().getCore().getEntity()) + "/essay/" + newPost.getId());
-
-// tasks.run(new Runnable() {
-// @Override
-// public void run() {
-// } else {
-// Post originalPost = posts.get(0);
-//
-// Post newPost = newPost();
-//
-// EssayistMetadataContent content = originalPost.getContentAs(EssayistMetadataContent.class);
-// content.setRaw(body);
-// }
-//
-// }
-// });
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
TentClient tentClient = newTentClient();
-// List posts = tentClient.getPosts(new PostQuery().mentionedPost(essayId).postTypes(EssayistMetadataContent.URI));
String essayId = req.getPathInfo().substring(1);
Post post = newPost();
@@ -118,7 +103,7 @@ public class WriteServlet extends HttpServlet {
EssayContent essay = new EssayContent();
essay.setTitle(req.getParameter("title"));
final String body = req.getParameter("body");
- essay.setBody(new PegDownProcessor().markdownToHtml(body));
+ essay.setBody(textTransformation.transformEssay(body));
essay.setExcerpt(req.getParameter("excerpt"));
post.setContent(essay);
diff --git a/src/main/java/com/moandjiezana/tent/essayist/config/JamonContext.java b/src/main/java/com/moandjiezana/tent/essayist/config/JamonContext.java
index 16dcbe2..a7cc925 100644
--- a/src/main/java/com/moandjiezana/tent/essayist/config/JamonContext.java
+++ b/src/main/java/com/moandjiezana/tent/essayist/config/JamonContext.java
@@ -5,6 +5,7 @@ import com.moandjiezana.tent.client.users.Profile;
import com.moandjiezana.tent.essayist.EssayistSession;
import com.moandjiezana.tent.essayist.User;
import com.moandjiezana.tent.essayist.security.Csrf;
+import com.moandjiezana.tent.essayist.text.TextTransformation;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
@@ -16,14 +17,16 @@ public class JamonContext {
public final String contextPath;
public final Routes routes;
public final Csrf csrf = new Csrf();
+ public final TextTransformation textTransformation;
private final HttpServletRequest req;
public final String currentUrl;
private final EssayistSession session;
@Inject
- public JamonContext(EssayistSession session, Routes routes, HttpServletRequest req) {
+ public JamonContext(EssayistSession session, TextTransformation textTransformation, Routes routes, HttpServletRequest req) {
this.session = session;
+ this.textTransformation = textTransformation;
this.routes = routes;
this.req = req;
this.contextPath = req.getContextPath();
diff --git a/src/main/java/com/moandjiezana/tent/essayist/security/Csrf.java b/src/main/java/com/moandjiezana/tent/essayist/security/Csrf.java
index 1d56dcb..96ba3c5 100644
--- a/src/main/java/com/moandjiezana/tent/essayist/security/Csrf.java
+++ b/src/main/java/com/moandjiezana/tent/essayist/security/Csrf.java
@@ -3,6 +3,7 @@ package com.moandjiezana.tent.essayist.security;
import javax.inject.Singleton;
import org.owasp.html.HtmlPolicyBuilder;
+import org.owasp.html.PolicyFactory;
@Singleton
public class Csrf {
@@ -13,12 +14,21 @@ public class Csrf {
.allowStandardUrlProtocols()
.allowStyling()
.allowElements("iframe", "img", "a", "table", "thead", "tbody", "tr", "th", "td", "em")
- .allowAttributes("width", "height", "title").globally()
+ .allowAttributes("width", "height", "title", "class").globally()
.allowAttributes("src", "frameborder", "webkitAllowFullScreen", "mozallowfullscreen", "allowFullScreen").onElements("iframe")
.allowAttributes("src", "alt").onElements("img")
- .allowAttributes("href").onElements("a");
+ .allowAttributes("href", "rel").onElements("a");
+
+ private final PolicyFactory restrictive = new HtmlPolicyBuilder()
+ .allowStandardUrlProtocols()
+ .allowElements("a")
+ .allowAttributes("href", "class", "rel").onElements("a").toFactory();
- public String stripScripts(String html) {
+ public String permissive(String html) {
return allowScripts.toFactory().sanitize(html);
}
+
+ public String restrictive(String html) {
+ return restrictive.sanitize(html);
+ }
}
diff --git a/src/main/java/com/moandjiezana/tent/essayist/text/TextTransformation.java b/src/main/java/com/moandjiezana/tent/essayist/text/TextTransformation.java
new file mode 100644
index 0000000..ecb3975
--- /dev/null
+++ b/src/main/java/com/moandjiezana/tent/essayist/text/TextTransformation.java
@@ -0,0 +1,53 @@
+package com.moandjiezana.tent.essayist.text;
+
+import com.moandjiezana.tent.essayist.security.Csrf;
+import com.moandjiezana.tent.text.Autolink;
+import com.moandjiezana.tent.text.Extractor.Entity;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.pegdown.PegDownProcessor;
+
+@Singleton
+public class TextTransformation {
+
+ private final Csrf csrf;
+ private final Autolink essayAutolink = new Autolink();
+ private final Autolink commentAutolink = new Autolink();
+
+ @Inject
+ public TextTransformation(Csrf csrf) {
+ this.csrf = csrf;
+ essayAutolink.setMentionIncludeSymbol(true);
+ Autolink.LinkTextModifier linkTextModifier = new Autolink.LinkTextModifier() {
+ @Override
+ public CharSequence modify(Entity entity, CharSequence text) {
+ if (text.charAt(0) == '^') {
+ return text.subSequence(1, text.length());
+ }
+ return text;
+ }
+ };
+ essayAutolink.setLinkTextModifier(linkTextModifier);
+ essayAutolink.setMentionClass("label label-inverse");
+
+ commentAutolink.setMentionIncludeSymbol(true);
+ commentAutolink.setLinkTextModifier(linkTextModifier);
+ commentAutolink.setMentionClass("label label-inverse");
+ }
+
+ public String transformEssay(String text) {
+ essayAutolink.setNoFollow(false);
+ String autoLinked = essayAutolink.autoLinkHashtags(essayAutolink.autoLinkMentionsAndLists(text));
+
+ String html = new PegDownProcessor().markdownToHtml(autoLinked);
+ String sanitized = csrf.permissive(html);
+
+ return sanitized;
+ }
+
+ public Object transformComment(String comment) {
+ return csrf.restrictive(commentAutolink.autoLink(comment));
+ }
+}
diff --git a/src/main/templates/com/moandjiezana/tent/essayist/EssayPage.jamon b/src/main/templates/com/moandjiezana/tent/essayist/EssayPage.jamon
index e79c022..4ee5bbf 100644
--- a/src/main/templates/com/moandjiezana/tent/essayist/EssayPage.jamon
+++ b/src/main/templates/com/moandjiezana/tent/essayist/EssayPage.jamon
@@ -19,7 +19,7 @@ final EssayContent content = essay.getContentAs(EssayContent.class);
final SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
final SimpleDateFormat dateTimeFormat = new SimpleDateFormat("dd MMMM yyyy HH:mm ZZZZ");
%java>
-<&| Layout; active = active &>
+<&| Layout; active = active; title = Entities.essayTitle(content) &>
<& partials/Essay; essay=essay; entityForUrl=Entities.getForUrl(essay.getEntity()); entityName=Entities.getName(profile); essayId=essay.getId(); formattedPublicationDate=dateFormat.format(new Date(essay.getPublishedAt() * 1000)); display=true; &>
<& partials/Reactions; reactions=reactions; essay=essay; autoLoad=true; &>
diff --git a/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon b/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon
index 5ff4996..b5c7264 100644
--- a/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon
+++ b/src/main/templates/com/moandjiezana/tent/essayist/Layout.jamon
@@ -8,6 +8,7 @@ com.moandjiezana.tent.essayist.config.*;
<%args>
boolean showNav = true;
String active = null;
+String title = "Essayist";
%args>
<%def navItem>
@@ -21,7 +22,7 @@ String url;
- Essayist
+ <% title %>
diff --git a/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionTemplate.jamon b/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionTemplate.jamon
index 084c3e5..533c2fb 100644
--- a/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionTemplate.jamon
+++ b/src/main/templates/com/moandjiezana/tent/essayist/partials/ReactionTemplate.jamon
@@ -18,7 +18,7 @@ String formattedDate = dateTimeFormat.format(new Date(reaction.getPublishedAt()
<%if Post.Types.equalsIgnoreVersion(Post.Types.status("v0.1.0"), reaction.getType()) %>
<% displayEntity %> commented <% formattedDate %>
- <% reaction.getContentAs(StatusContent.class).getText() %>
+ <% jamonContext.textTransformation.transformComment(reaction.getContentAs(StatusContent.class).getText()) #n %>
<%elseif Post.Types.equalsIgnoreVersion(Favorite.URI, reaction.getType()) %>
Favourited by <% displayEntity %> <% formattedDate %>
diff --git a/src/main/webapp/assets/essayist.js b/src/main/webapp/assets/essayist.js
index 0d56a4d..c156c5d 100644
--- a/src/main/webapp/assets/essayist.js
+++ b/src/main/webapp/assets/essayist.js
@@ -2,11 +2,15 @@ function initNavigation() {
var essayContainers = document.querySelectorAll("[data-essay=container]");
var i;
- var displaySection = function (section, target) {
+ var displaySection = function (section, target, title) {
var j;
var detailDisplay = section === "essay" ? "block" : "none";
var listDisplay = section === "list" ? "block" : "none";
+ if (title !== undefined) {
+ document.title = title;
+ }
+
if (target !== document) {
target.querySelector("[data-essay=summary]").style.display = listDisplay;
target.querySelector("[data-essay=full]").style.display = detailDisplay;
@@ -46,12 +50,12 @@ function initNavigation() {
}
var scrollPosition = document.body.scrollTop;
- displaySection("essay", event.currentTarget);
+ displaySection("essay", event.currentTarget, event.target.text);
var reactionsContainer = container.querySelector('[data-essay="reactions"]');
fetchReactions(reactionsContainer);
- history.pushState({ essayId: event.currentTarget.dataset.essayId, essayAuthor: event.currentTarget.dataset.essayAuthor, scrollPosition: scrollPosition }, "essay title", event.target.href);
+ history.pushState({ essayId: event.currentTarget.dataset.essayId, essayAuthor: event.currentTarget.dataset.essayAuthor, essayTitle: event.target.text, scrollPosition: scrollPosition }, "essay title", event.target.href);
return false;
};
@@ -59,11 +63,10 @@ function initNavigation() {
var essayPopStateHandler = function (event) {
var essayContainer;
if (window.location.href.indexOf("/essays") > -1 || window.location.href.indexOf("/global") > -1 || window.location.href.indexOf("/read") > -1) {
- displaySection("list", document);
- window.scrollTo(0);
+ displaySection("list", document, "Essayist");
} else if (event.state !== null) {
essayContainer = document.querySelector("[data-essay-author=\"" + event.state.essayAuthor + "\"][data-essay-id=\"" + event.state.essayId + "\"]");
- displaySection("essay", essayContainer);
+ displaySection("essay", essayContainer, event.state.essayTitle);
}
};
diff --git a/src/test/java/com/moandjiezana/tent/essayist/security/CsrfTest.java b/src/test/java/com/moandjiezana/tent/essayist/security/CsrfTest.java
index aa81000..5fe096a 100644
--- a/src/test/java/com/moandjiezana/tent/essayist/security/CsrfTest.java
+++ b/src/test/java/com/moandjiezana/tent/essayist/security/CsrfTest.java
@@ -8,14 +8,14 @@ public class CsrfTest {
@Test
public void should_remove_external_scripts() {
- String sanitized = new Csrf().stripScripts("title
sub-title
Some text
Some more text
");
+ String sanitized = new Csrf().permissive("title
sub-title
Some text
Some more text
");
assertEquals("title
sub-title
Some text
Some more text
", sanitized);
}
@Test
public void should_remove_internal_scripts() {
- String sanitized = new Csrf().stripScripts("title
sub-title
Some text
Some more text
");
+ String sanitized = new Csrf().permissive("title
sub-title
Some text
Some more text
");
assertEquals("title
sub-title
Some text
Some more text
", sanitized);
}
@@ -24,14 +24,14 @@ public class CsrfTest {
public void should_allow_images() {
String img = "
";
- assertEquals(img, new Csrf().stripScripts(img));
+ assertEquals(img, new Csrf().permissive(img));
}
@Test
public void should_allow_links() {
String link = "Oskar van Rijswijk";
- assertEquals(link, new Csrf().stripScripts(link));
+ assertEquals(link, new Csrf().permissive(link));
}
@Test
@@ -39,20 +39,27 @@ public class CsrfTest {
String vimeoEmbed = "
An excerpt from a three screen triptych which is a part of a bigger installation.
Sound, photography & video by Einat Schlagmann.
";
String expected = "
An excerpt from a three screen triptych which is a part of a bigger installation.
Sound, photography & video by Einat Schlagmann.
";
- assertEquals(expected, new Csrf().stripScripts(vimeoEmbed));
+ assertEquals(expected, new Csrf().permissive(vimeoEmbed));
}
@Test
public void should_allow_tables() {
String table = "";
- assertEquals(table, new Csrf().stripScripts(table));
+ assertEquals(table, new Csrf().permissive(table));
}
@Test
public void should_allow_emphasis() {
String emphasis = "emphasised";
- assertEquals(emphasis, new Csrf().stripScripts(emphasis));
+ assertEquals(emphasis, new Csrf().permissive(emphasis));
+ }
+
+ @Test
+ public void restrictive_should_only_allow_links() {
+ String html = "link emphasis bold";
+
+ assertEquals("link emphasis bold", new Csrf().restrictive(html));
}
}
diff --git a/src/test/java/com/moandjiezana/tent/essayist/text/TextTransformationTest.java b/src/test/java/com/moandjiezana/tent/essayist/text/TextTransformationTest.java
new file mode 100644
index 0000000..80aef45
--- /dev/null
+++ b/src/test/java/com/moandjiezana/tent/essayist/text/TextTransformationTest.java
@@ -0,0 +1,30 @@
+package com.moandjiezana.tent.essayist.text;
+
+import static org.junit.Assert.assertEquals;
+
+import com.moandjiezana.tent.essayist.security.Csrf;
+
+import org.junit.Test;
+
+public class TextTransformationTest {
+
+ private final TextTransformation textTransformation = new TextTransformation(new Csrf());
+
+ @Test
+ public void should_expand_markdown_and_all_entities_in_essay() {
+ String text = "[Link](http://www.example.com) by ^mention is #good!";
+
+ String expected = "Link by mention is #good!
";
+
+ assertEquals(expected, textTransformation.transformEssay(text));
+ }
+
+ @Test
+ public void should_expand_all_entities_in_comment() {
+ String comment = "Hey ^somerandombloke, have you seen https://github.com/tent/tent.io/wiki/Explaining-Tent ?";
+
+ String expected = "Hey somerandombloke, have you seen https://github.com/tent/tent.io/wiki/Explaining-Tent ?";
+
+ assertEquals(expected, textTransformation.transformComment(comment));
+ }
+}