From 7035d8063c00c7c109b458f75c857ee5871065ea Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Tue, 6 Sep 2016 19:08:14 -0300 Subject: [PATCH] Clean HTML from RSS feeds with Jsoup and show Link Warning --- .../res/layout/fragment_link_dialog.xml | 73 ++++++++++++++++++ briar-android/res/values/strings.xml | 6 ++ .../android/blogs/BlogPostViewHolder.java | 10 ++- .../android/util/AndroidUtils.java | 37 +++++++++ .../android/widget/LinkDialogFragment.java | 77 +++++++++++++++++++ briar-core/build.gradle | 2 + .../briarproject/feed/FeedManagerImpl.java | 38 ++++----- .../src/org/briarproject/util/HtmlUtils.java | 16 ++++ .../org/briarproject/util/PrivacyUtils.java | 1 - 9 files changed, 238 insertions(+), 22 deletions(-) create mode 100644 briar-android/res/layout/fragment_link_dialog.xml create mode 100644 briar-android/src/org/briarproject/android/widget/LinkDialogFragment.java create mode 100644 briar-core/src/org/briarproject/util/HtmlUtils.java diff --git a/briar-android/res/layout/fragment_link_dialog.xml b/briar-android/res/layout/fragment_link_dialog.xml new file mode 100644 index 0000000000..307b7110ee --- /dev/null +++ b/briar-android/res/layout/fragment_link_dialog.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/margin_large"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/link_warning_title" + android:textColor="@color/briar_primary" + android:textSize="@dimen/text_size_large" + android:textStyle="bold"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_large" + android:text="@string/link_warning_intro" + android:textColor="@color/briar_primary" + android:textSize="@dimen/text_size_medium"/> + + <TextView + android:id="@+id/urlView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_large" + android:textIsSelectable="true" + android:typeface="monospace" + tools:text="http://very.bad.site.com"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_large" + android:text="@string/link_warning_text" + android:textColor="@color/briar_primary" + android:textSize="@dimen/text_size_medium"/> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Button + android:id="@+id/cancelButton" + style="@style/BriarButtonFlat.Positive" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:text="@string/cancel"/> + + <Button + android:id="@+id/openButton" + style="@style/BriarButtonFlat.Negative" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:text="@string/link_warning_open_link"/> + + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index e866545097..626f3d8a88 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -308,6 +308,12 @@ <string name="feedback_settings_title">Feedback</string> <string name="send_feedback">Send feedback</string> + <!-- Link Warning --> + <string name="link_warning_title">Link Warning</string> + <string name="link_warning_intro">You are about to open the following link with an external app.</string> + <string name="link_warning_text">This can be used to identify you. Think about whether you trust the person that sent you this link and consider opening it with Orfox.</string> + <string name="link_warning_open_link">Open Link</string> + <!-- Multiple Identities --> <string name="anonymous">Anonymous</string> <string name="new_identity_title">New Identity</string> diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java index 13a1b3f177..2fb84671e2 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogPostViewHolder.java @@ -30,6 +30,8 @@ import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.android.blogs.BasePostPagerFragment.POST_ID; import static org.briarproject.android.util.AndroidUtils.TEASER_LENGTH; import static org.briarproject.android.util.AndroidUtils.getTeaser; +import static org.briarproject.android.util.AndroidUtils.getSpanned; +import static org.briarproject.android.util.AndroidUtils.makeLinksClickable; import static org.briarproject.api.blogs.MessageType.POST; @UiThread @@ -108,15 +110,17 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder { } // post body - CharSequence bodyText = item.getBody(); if (listener == null) { + body.setText(getSpanned(item.getBody())); + makeLinksClickable(body); body.setTextIsSelectable(true); } else { body.setTextIsSelectable(false); if (item.getBody().length() > TEASER_LENGTH) - bodyText = getTeaser(ctx, item.getBody()); + body.setText(getTeaser(ctx, item.getBody())); + else + body.setText(item.getBody()); } - body.setText(bodyText); // reblog button reblogButton.setOnClickListener(new OnClickListener() { diff --git a/briar-android/src/org/briarproject/android/util/AndroidUtils.java b/briar-android/src/org/briarproject/android/util/AndroidUtils.java index 66593d0f82..6a591f1dc6 100644 --- a/briar-android/src/org/briarproject/android/util/AndroidUtils.java +++ b/briar-android/src/org/briarproject/android/util/AndroidUtils.java @@ -6,14 +6,24 @@ import android.content.Context; import android.os.Build; import android.provider.Settings; import android.support.design.widget.TextInputLayout; +import android.support.v4.app.FragmentManager; import android.support.v4.content.ContextCompat; +import android.support.v7.app.AppCompatActivity; +import android.text.Html; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.format.DateUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; +import android.text.style.URLSpan; +import android.view.View; +import android.widget.TextView; import org.briarproject.R; +import org.briarproject.android.widget.LinkDialogFragment; import org.briarproject.util.IoUtils; import org.briarproject.util.StringUtils; @@ -142,4 +152,31 @@ public class AndroidUtils { return builder; } + public static Spanned getSpanned(String s) { + return Html.fromHtml(s); + } + + public static void makeLinksClickable(TextView v) { + SpannableStringBuilder ssb = new SpannableStringBuilder(v.getText()); + URLSpan[] spans = ssb.getSpans(0, ssb.length(), URLSpan.class); + for (URLSpan span : spans) { + int start = ssb.getSpanStart(span); + int end = ssb.getSpanEnd(span); + final String url = span.getURL(); + ssb.removeSpan(span); + ClickableSpan cSpan = new ClickableSpan() { + @Override + public void onClick(View v2) { + LinkDialogFragment f = LinkDialogFragment.newInstance(url); + FragmentManager fm = ((AppCompatActivity) v2.getContext()) + .getSupportFragmentManager(); + f.show(fm, f.getUniqueTag()); + } + }; + ssb.setSpan(cSpan, start, end, 0); + } + v.setText(ssb); + v.setMovementMethod(LinkMovementMethod.getInstance()); + } + } diff --git a/briar-android/src/org/briarproject/android/widget/LinkDialogFragment.java b/briar-android/src/org/briarproject/android/widget/LinkDialogFragment.java new file mode 100644 index 0000000000..1a6e636eef --- /dev/null +++ b/briar-android/src/org/briarproject/android/widget/LinkDialogFragment.java @@ -0,0 +1,77 @@ +package org.briarproject.android.widget; + + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.briarproject.R; + + +public class LinkDialogFragment extends DialogFragment { + + private static final String TAG = LinkDialogFragment.class.getName(); + + private String url; + + public static LinkDialogFragment newInstance(String url) { + LinkDialogFragment f = new LinkDialogFragment(); + + Bundle args = new Bundle(); + args.putString("url", url); + f.setArguments(args); + + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + url = getArguments().getString("url"); + + setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View v = inflater.inflate(R.layout.fragment_link_dialog, container, + false); + + TextView urlView = (TextView) v.findViewById(R.id.urlView); + urlView.setText(url); + + Button openButton = (Button) v.findViewById(R.id.openButton); + openButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + startActivity(i); + } + }); + + Button cancelButton = (Button) v.findViewById(R.id.cancelButton); + cancelButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + getDialog().cancel(); + } + }); + + return v; + } + + public String getUniqueTag() { + return TAG; + } + +} diff --git a/briar-core/build.gradle b/briar-core/build.gradle index ae5071be7f..9ffd0987f5 100644 --- a/briar-core/build.gradle +++ b/briar-core/build.gradle @@ -13,6 +13,7 @@ dependencies { compile 'org.jdom:jdom2:2.0.6' compile 'org.slf4j:slf4j-api:1.7.21' compile 'com.squareup.okhttp3:okhttp:3.3.1' + compile 'org.jsoup:jsoup:1.9.2' } dependencyVerification { @@ -25,6 +26,7 @@ dependencyVerification { 'com.squareup.okhttp3:okhttp:a47f4efa166551cd5acc04f1071d82dafbf05638c21f9ca13068bc6633e3bff6', 'com.rometools:rome-utils:2be18a1edc601c31fe49c2000bb5484dd75182309270c2a2561d71888d81587a', 'com.squareup.okio:okio:5cfea5afe6c6e441a4dbf6053a07a733b1249d1009382eb44ac2255ccedd0c15', + 'org.jsoup:jsoup:9c1885f1b182256e06f1e30b8451caed0c0dee96299d6348f968d18b54d0a46a', ] } diff --git a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java index 6384f06c03..b9263c0627 100644 --- a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java +++ b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java @@ -66,6 +66,9 @@ import static org.briarproject.api.feed.FeedConstants.FETCH_DELAY_INITIAL; import static org.briarproject.api.feed.FeedConstants.FETCH_INTERVAL; import static org.briarproject.api.feed.FeedConstants.FETCH_UNIT; import static org.briarproject.api.feed.FeedConstants.KEY_FEEDS; +import static org.briarproject.util.HtmlUtils.article; +import static org.briarproject.util.HtmlUtils.clean; +import static org.briarproject.util.HtmlUtils.stripAll; class FeedManagerImpl implements FeedManager, Client, EventListener { @@ -337,13 +340,13 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { SyndFeed f = getSyndFeed(getFeedInputStream(feed.getUrl())); title = StringUtils.isNullOrEmpty(f.getTitle()) ? null : f.getTitle(); - if (title != null) title = stripHTML(title); + if (title != null) title = clean(title, stripAll); description = StringUtils.isNullOrEmpty(f.getDescription()) ? null : f.getDescription(); - if (description != null) description = stripHTML(description); + if (description != null) description = clean(description, stripAll); author = StringUtils.isNullOrEmpty(f.getAuthor()) ? null : f.getAuthor(); - if (author != null) author = stripHTML(author); + if (author != null) author = clean(author, stripAll); if (f.getEntries().size() == 0) throw new FeedException("Feed has no entries"); @@ -418,23 +421,23 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { // build post body StringBuilder b = new StringBuilder(); if (feed.getTitle() != null) { - // HTML in feed title was already stripped - b.append(feed.getTitle()).append("\n\n"); + b.append("<h3>").append(feed.getTitle()).append("</h3>"); } if (!StringUtils.isNullOrEmpty(entry.getTitle())) { - b.append(stripHTML(entry.getTitle())).append("\n\n"); + b.append("<h1>").append(entry.getTitle()).append("</h1>"); } for (SyndContent content : entry.getContents()) { - // extract content and do a very simple HTML tag stripping if (content.getValue() != null) - b.append(stripHTML(content.getValue())); + b.append(content.getValue()); } if (entry.getContents().size() == 0) { - if (entry.getDescription().getValue() != null) - b.append(stripHTML(entry.getDescription().getValue())); + if (entry.getDescription() != null && + entry.getDescription().getValue() != null) + b.append(entry.getDescription().getValue()); } + b.append("<p>"); if (!StringUtils.isNullOrEmpty(entry.getAuthor())) { - b.append("\n\n-- ").append(stripHTML(entry.getAuthor())); + b.append("\n\n-- ").append(clean(entry.getAuthor(), stripAll)); } if (entry.getPublishedDate() != null) { b.append(" (").append(entry.getPublishedDate().toString()) @@ -443,8 +446,11 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { b.append(" (").append(entry.getUpdatedDate().toString()) .append(")"); } - if (!StringUtils.isNullOrEmpty(entry.getLink())) { - b.append("\n\n").append(stripHTML(entry.getLink())); + b.append("</p>"); + String link = entry.getLink(); + if (!StringUtils.isNullOrEmpty(link)) { + b.append("<a href=\"").append(link).append("\">").append(link) + .append("</a>"); } // get other information for post @@ -476,12 +482,8 @@ class FeedManagerImpl implements FeedManager, Client, EventListener { } } - private String stripHTML(String s) { - s = s.replaceAll("<script.*?>(?s).*?</script>", ""); - return StringUtils.trim(s.replaceAll("<(?s).*?>", "")); - } - private String getPostBody(String text) { + text = clean(text, article); if (text.length() <= MAX_BLOG_POST_BODY_LENGTH) return text; else return text.substring(0, MAX_BLOG_POST_BODY_LENGTH); } diff --git a/briar-core/src/org/briarproject/util/HtmlUtils.java b/briar-core/src/org/briarproject/util/HtmlUtils.java new file mode 100644 index 0000000000..e9c338401b --- /dev/null +++ b/briar-core/src/org/briarproject/util/HtmlUtils.java @@ -0,0 +1,16 @@ +package org.briarproject.util; + +import org.jsoup.Jsoup; +import org.jsoup.safety.Whitelist; + +public class HtmlUtils { + + public static Whitelist stripAll = Whitelist.none(); + public static Whitelist article = + Whitelist.basic().addTags("h1", "h2", "h3", "h4", "h5", "h6"); + + public static String clean(String s, Whitelist list) { + return Jsoup.clean(s, list); + } + +} diff --git a/briar-core/src/org/briarproject/util/PrivacyUtils.java b/briar-core/src/org/briarproject/util/PrivacyUtils.java index 247ccda3f2..c2bf426287 100644 --- a/briar-core/src/org/briarproject/util/PrivacyUtils.java +++ b/briar-core/src/org/briarproject/util/PrivacyUtils.java @@ -1,6 +1,5 @@ package org.briarproject.util; -import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; -- GitLab