From e9c2395f2beb1e6f56543e6cdaf2414ca897bbcb Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Thu, 15 Aug 2019 18:43:46 +0200
Subject: [PATCH] OO-4192: allow javascript:parent.goto()

---
 .../core/util/filter/impl/OpenOLATPolicy.java | 92 ++++++++++++++++++-
 .../util/filter/impl/XSSFilterParamTest.java  |  2 +-
 2 files changed, 92 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/olat/core/util/filter/impl/OpenOLATPolicy.java b/src/main/java/org/olat/core/util/filter/impl/OpenOLATPolicy.java
index 4586307ab7a..ab8cb3d454a 100644
--- a/src/main/java/org/olat/core/util/filter/impl/OpenOLATPolicy.java
+++ b/src/main/java/org/olat/core/util/filter/impl/OpenOLATPolicy.java
@@ -19,14 +19,21 @@
  */
 package org.olat.core.util.filter.impl;
 
+import java.util.List;
 import java.util.regex.Pattern;
 
 import org.owasp.html.HtmlPolicyBuilder;
+import org.owasp.html.HtmlStreamEventProcessor;
+import org.owasp.html.HtmlStreamEventReceiver;
+import org.owasp.html.HtmlStreamEventReceiverWrapper;
 import org.owasp.html.PolicyFactory;
 
 import com.google.common.base.Predicate;
 
 /**
+ * The policy allow very specific onclicks values. It has a pre and
+ * post processor to handle the javascript:parent.goto(273846)
+ * 
  * 
  * Initial date: 22 mai 2019<br>
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
@@ -160,7 +167,7 @@ public class OpenOLATPolicy {
 			.matching(new Patterns(ONSITEURL, OFFSITEURL, OLATINTERNALURL))
 			.onElements("a")
 	    .allowAttributes("onclick")
-			.matching(false, "o_XHRWikiEvent(this);", "o_XHRWikiEvent(this);return(false);")
+			.matching(new OnClickValues())
 			.onElements("a")
 		// link edu-sharing
 		.allowAttributes("data-es_show_infos")
@@ -267,8 +274,91 @@ public class OpenOLATPolicy {
 			.allowWithoutAttributes("span")
 		.allowElements("center")
 			.allowWithoutAttributes("center")
+		.withPreprocessor(new OpenOLATPreprocessor())
+		.withPostprocessor(new OpenOLATPostprocessor())
 		.toFactory();
 
+	private static class OpenOLATPreprocessor implements HtmlStreamEventProcessor {
+
+		@Override
+		public HtmlStreamEventReceiver wrap(HtmlStreamEventReceiver sink) {
+			return new OpenOLATPreReceiver(sink);
+		}
+	}
+	
+	private static class OpenOLATPreReceiver extends HtmlStreamEventReceiverWrapper {
+		
+		public OpenOLATPreReceiver(HtmlStreamEventReceiver sink) {
+			super(sink);
+		}
+
+		@Override
+		public void openTag(String elementName, List<String> attrs) {
+			if("a".equals(elementName) && attrs != null) {
+				int numOfAttrs = attrs.size();
+				for(int i=0; i<numOfAttrs; i++) {
+					String attr = attrs.get(i);
+					if("href".equals(attr) && i+1 < numOfAttrs
+							&& attrs.get(i+1).startsWith("javascript:parent.goto")
+							&& OLATINTERNALURL.matcher(attrs.get(i + 1)).matches()) {
+						attrs.set(i, "onclick");
+					}	
+				}
+			}
+			super.openTag(elementName, attrs);
+		}
+	}
+	
+	private static class OpenOLATPostprocessor implements HtmlStreamEventProcessor {
+
+		@Override
+		public HtmlStreamEventReceiver wrap(HtmlStreamEventReceiver sink) {
+			return new OpenOLATostReceiver(sink);
+		}
+	}
+	
+	private static class OpenOLATostReceiver extends HtmlStreamEventReceiverWrapper {
+		
+		public OpenOLATostReceiver(HtmlStreamEventReceiver sink) {
+			super(sink);
+		}
+
+		@Override
+		public void openTag(String elementName, List<String> attrs) {
+			if("a".equals(elementName) && attrs != null) {
+				int numOfAttrs = attrs.size();
+				for(int i=0; i<numOfAttrs; i++) {
+					String attr = attrs.get(i);
+					if("onclick".equals(attr) && i+1 < numOfAttrs
+							&& attrs.get(i+1).startsWith("javascript:parent.goto")
+							&& OLATINTERNALURL.matcher(attrs.get(i + 1)).matches()) {
+						attrs.set(i, "href");
+					}	
+				}
+			}
+			super.openTag(elementName, attrs);
+		}
+	}
+	
+	private static class OnClickValues implements Predicate<String> {
+		
+		@Override
+		public boolean apply(String s) {
+			if("o_XHRWikiEvent(this);".equals(s) || "o_XHRWikiEvent(this);return(false);".equals(s)) {
+				return true;
+			}
+			return OLATINTERNALURL.matcher(s).matches();
+		}
+		
+		// Needed for Java8 compat with later Guava that extends
+		// java.util.function.Predicate.
+		// For some reason the default test method implementation that calls
+		// through to apply is not assumed here.
+		@SuppressWarnings("unused")
+		public boolean test(String s) {
+			return apply(s);
+		}
+	}
 	
 	private static class Patterns implements Predicate<String> {
 		
diff --git a/src/test/java/org/olat/core/util/filter/impl/XSSFilterParamTest.java b/src/test/java/org/olat/core/util/filter/impl/XSSFilterParamTest.java
index 258691f98a0..6bbd766efaf 100644
--- a/src/test/java/org/olat/core/util/filter/impl/XSSFilterParamTest.java
+++ b/src/test/java/org/olat/core/util/filter/impl/XSSFilterParamTest.java
@@ -184,7 +184,7 @@ public class XSSFilterParamTest {
 /* 100 */	{ "<img src=\"/olat/edusharing/preview?objectUrl=ccrep://OpenOLAT/d5130470-14b4-4ad4-88b7-dfb3ebe943da&version=1.0\" data-es_identifier=\"2083dbe64f00b07232b11608ec0842fc\" data-es_objecturl=\"ccrep://OpenOLAT/d5130470-14b4-4ad4-88b7-dfb3ebe943da\" data-es_version=\"1.0\" data-es_version_current=\"1.0\" data-es_mediatype='i23' data-es_mimetype=\"image/png\" data-es_width=\"1000\" data-es_height=\"446\" data-es_first_edit=\"false\" class=\"edusharing\" alt=\"Bildschirmfoto 2018-11-07 um 16.09.49.png\" title=\"Bildschirmfoto 2018-11-07 um 16.09.49.png\" width=\"1000\" height=\"446\">",
 				"<img src=\"/olat/edusharing/preview?objectUrl&#61;ccrep://OpenOLAT/d5130470-14b4-4ad4-88b7-dfb3ebe943da&amp;version&#61;1.0\" data-es_identifier=\"2083dbe64f00b07232b11608ec0842fc\" data-es_objecturl=\"ccrep://OpenOLAT/d5130470-14b4-4ad4-88b7-dfb3ebe943da\" data-es_version=\"1.0\" data-es_version_current=\"1.0\" data-es_mediatype=\"i23\" data-es_mimetype=\"image/png\" data-es_width=\"1000\" data-es_height=\"446\" data-es_first_edit=\"false\" class=\"edusharing\" alt=\"Bildschirmfoto 2018-11-07 um 16.09.49.png\" title=\"Bildschirmfoto 2018-11-07 um 16.09.49.png\" width=\"1000\" height=\"446\" />"	
 			},
-			{ "Before<br>After<br>More", "Before<br />After<br />More" },
+			{ "<a href=\"javascript:parent.gotonode(100055283652712)\">Test</a>", "<a href=\"javascript:parent.gotonode(100055283652712)\">Test</a>" },
 			{ null, "" } // be tolerant
         });
     }
-- 
GitLab