Index: kernel-impl/src/test/java/org/sakaiproject/util/impl/FormattedTextTest.java =================================================================== --- kernel-impl/src/test/java/org/sakaiproject/util/impl/FormattedTextTest.java (revision 130992) +++ kernel-impl/src/test/java/org/sakaiproject/util/impl/FormattedTextTest.java (working copy) @@ -22,8 +22,19 @@ package org.sakaiproject.util.impl; import java.util.regex.Pattern; + +import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.component.cover.ComponentManager; +import org.sakaiproject.component.impl.BasicConfigurationService; +import org.sakaiproject.id.api.IdManager; +import org.sakaiproject.id.impl.UuidV4IdComponent; +import org.sakaiproject.thread_local.api.ThreadLocalManager; +import org.sakaiproject.thread_local.impl.ThreadLocalComponent; +import org.sakaiproject.tool.api.SessionManager; +import org.sakaiproject.tool.api.ToolManager; +import org.sakaiproject.tool.impl.SessionComponent; import org.sakaiproject.util.api.FormattedText.Level; +import org.sakaiproject.util.BasicConfigItem; import junit.framework.TestCase; @@ -31,10 +42,38 @@ FormattedTextImpl formattedText; + private SessionManager sessionManager; + private ServerConfigurationService serverConfigurationService; + @Override protected void setUp() throws Exception { + // instantiate the services we need for our test + final IdManager idManager = new UuidV4IdComponent(); + final ThreadLocalManager threadLocalManager = new ThreadLocalComponent(); + serverConfigurationService = new BasicConfigurationService(); // cannot use home or server methods + sessionManager = new SessionComponent() { + @Override + protected ToolManager toolManager() { + return null; // not needed for this test + } + @Override + protected ThreadLocalManager threadLocalManager() { + return threadLocalManager; + } + @Override + protected IdManager idManager() { + return idManager; + } + }; + + // add in the config so we can test it + serverConfigurationService.registerConfigItem(BasicConfigItem.makeDefaultedConfigItem("content.cleaner.errors.return.to.tool", true, "FormattedTextTest")); + ComponentManager.testingMode = true; + // instantiate what we are testing formattedText = new FormattedTextImpl(); + formattedText.setServerConfigurationService(serverConfigurationService); + formattedText.setSessionManager(sessionManager); formattedText.init(); } @@ -204,7 +243,7 @@ assertFalse( result.contains("src=")); assertFalse( result.contains("data:image/svg+xml;base64")); assertFalse( result.contains(" 10); } - */ + */ } public void testKNL_528() { @@ -286,7 +325,7 @@ String strFromBrowser = null; String result = null; StringBuilder errorMessages = null; - + strFromBrowser = SVG_BAD; errorMessages = new StringBuilder(); result = formattedText.processFormattedText(strFromBrowser, errorMessages, true); @@ -309,7 +348,7 @@ assertFalse( result.contains("data:image/svg+xml;base64")); assertFalse( result.contains("]]>"; String CDATA_TRICKY = "
]]>
"; @@ -334,7 +373,7 @@ assertFalse( result.contains("SRC=")); assertFalse( result.contains("data:image/svg+xml;base64")); assertFalse( result.contains("http://www.apple.com and stuff", formattedText.encodeUrlsAsHtml(formattedText.escapeHtml("I like http://www.apple.com and stuff"))); } - + public void testCanDoSsl() { assertEquals("https://sakaiproject.org", formattedText.encodeUrlsAsHtml("https://sakaiproject.org")); } - + public void testCanIgnoreTrailingExclamation() { assertEquals("Hey, it's http://sakaiproject.org!", formattedText.encodeUrlsAsHtml("Hey, it's http://sakaiproject.org!")); } - + public void testCanIgnoreTrailingQuestion() { assertEquals("Have you ever seen http://sakaiproject.org? Just wondering.", formattedText.encodeUrlsAsHtml("Have you ever seen http://sakaiproject.org? Just wondering.")); } - + public void testCanEncodeQueryString() { assertEquals("See http://sakaiproject.org/index.php?task=blogcategory&id=181 for more info.", formattedText.encodeUrlsAsHtml(formattedText.escapeHtml("See http://sakaiproject.org/index.php?task=blogcategory&id=181 for more info."))); } - + public void testCanTakePortNumber() { assertEquals("http://localhost:8080/portal", formattedText.encodeUrlsAsHtml("http://localhost:8080/portal")); } - + public void testCanTakePortNumberAndQueryString() { assertEquals("http://www.loco.com:3000/portal?person=224", formattedText.encodeUrlsAsHtml("http://www.loco.com:3000/portal?person=224")); } - + public void testCanIgnoreExistingHref() { assertEquals("Sakai Project", formattedText.encodeUrlsAsHtml("Sakai Project")); } - + public void testALongUrlFromNyTimes() { assertEquals("http://www.nytimes.com/mem/MWredirect.html?MW=http://custom.marketwatch.com/custom/nyt-com/html-companyprofile.asp&symb=LLNW", formattedText.encodeUrlsAsHtml(formattedText.escapeHtml("http://www.nytimes.com/mem/MWredirect.html?MW=http://custom.marketwatch.com/custom/nyt-com/html-companyprofile.asp&symb=LLNW"))); Index: kernel-impl/src/test/resources/messagebundle/sakai.properties =================================================================== --- kernel-impl/src/test/resources/messagebundle/sakai.properties (revision 130992) +++ kernel-impl/src/test/resources/messagebundle/sakai.properties (working copy) @@ -3,4 +3,6 @@ # number of milliseconds before rechecking database for updates, the default is 30 secs load.bundles.from.db.timeout=30000 -scheduleDelay@org.sakaiproject.messagebundle.api.MessageBundleService.target=1 \ No newline at end of file +scheduleDelay@org.sakaiproject.messagebundle.api.MessageBundleService.target=1 + +content.cleaner.errors.return.to.tool=true Index: kernel-impl/src/main/java/org/sakaiproject/util/impl/FormattedTextImpl.java =================================================================== --- kernel-impl/src/main/java/org/sakaiproject/util/impl/FormattedTextImpl.java (revision 130992) +++ kernel-impl/src/main/java/org/sakaiproject/util/impl/FormattedTextImpl.java (working copy) @@ -41,6 +41,10 @@ import org.owasp.validator.html.PolicyException; import org.owasp.validator.html.ScanException; import org.sakaiproject.component.api.ServerConfigurationService; +import org.sakaiproject.tool.api.Session; +import org.sakaiproject.tool.api.SessionManager; +import org.sakaiproject.util.Resource; +import org.sakaiproject.util.ResourceLoader; import org.sakaiproject.util.Xml; import org.sakaiproject.util.api.FormattedText; @@ -57,6 +61,11 @@ this.serverConfigurationService = serverConfigurationService; } + private SessionManager sessionManager = null; + public void setSessionManager(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + /** * This is the high level html cleaner object */ @@ -66,21 +75,41 @@ */ private AntiSamy antiSamyLow = null; + /** + * KNL-1075 - This decides whether we show the AntiSamy cleaner errors to the end user + */ + private boolean showErrorToUser = true; + private boolean showDetailedErrorToUser = true; + private boolean returnErrorToTool = false; + + private final String DEFAULT_RESOURCECLASS = "org.sakaiproject.localization.util.ContentProperties"; + protected final String DEFAULT_RESOURCEBUNDLE = "org.sakaiproject.localization.bundle.content.content"; + private final String RESOURCECLASS = "resource.class.content"; + protected final String RESOURCEBUNDLE = "resource.bundle.content"; + public void init() { boolean useLegacy = false; if (serverConfigurationService != null) { // this keeps the tests from dying useLegacy = serverConfigurationService.getBoolean("content.cleaner.use.legacy.html", useLegacy); + + // KNL-1075 + showErrorToUser = serverConfigurationService.getBoolean("content.cleaner.errors.display", false); + showDetailedErrorToUser = serverConfigurationService.getBoolean("content.cleaner.errors.detailed", false); + returnErrorToTool = serverConfigurationService.getBoolean("content.cleaner.errors.return.to.tool", false); + M_log.info("FormattedText settings: return errors to tool=" + showErrorToUser + + "; return detailed error to tool=" + showDetailedErrorToUser + + "; return error to tool=" + returnErrorToTool); } if (useLegacy) { M_log.error( - "**************************************************\n" - +"* -----------<<< WARNING >>>---------------- *\n" - +"* The LEGACY Sakai content scanner is no longer *\n" - +"* available. It has been deprecated and removed. *\n" - +"* Content scanning uses AntiSamy scanner now. *\n" - +"* https://jira.sakaiproject.org/browse/KNL-1127 *\n" - +"**************************************************\n" - ); + "**************************************************\n" + +"* -----------<<< WARNING >>>---------------- *\n" + +"* The LEGACY Sakai content scanner is no longer *\n" + +"* available. It has been deprecated and removed. *\n" + +"* Content scanning uses AntiSamy scanner now. *\n" + +"* https://jira.sakaiproject.org/browse/KNL-1127 *\n" + +"**************************************************\n" + ); } /* INIT Antisamy @@ -148,6 +177,13 @@ return defaultLowSecurity; } + public ResourceLoader getResourceLoader() { + String resourceClass = serverConfigurationService.getString(RESOURCECLASS, DEFAULT_RESOURCECLASS); + String resourceBundle = serverConfigurationService.getString(RESOURCEBUNDLE, DEFAULT_RESOURCEBUNDLE); + ResourceLoader loader = new Resource().getLoader(resourceClass, resourceBundle); + return loader; + } + /** * @return the path to the sakai home directory on the server */ @@ -177,8 +213,8 @@ Pattern.CASE_INSENSITIVE | Pattern.DOTALL); /** Matches all anchor tags that do not have a target attribute. */ public final Pattern M_patternAnchorTagWithOutTarget = - Pattern.compile("([<]a\\s)(?![^>]*target=)([^>]*?)[>]", - Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + Pattern.compile("([<]a\\s)(?![^>]*target=)([^>]*?)[>]", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL); /** Matches href attribute */ private Pattern M_patternHref = Pattern.compile("\\shref\\s*=\\s*(\".*?\"|'.*?')", @@ -251,6 +287,10 @@ */ public String processFormattedText(final String strFromBrowser, StringBuilder errorMessages, Level level, boolean checkForEvilTags, boolean replaceWhitespaceTags, boolean doNotUseLegacySakaiCleaner) { + + // KNL-1075: bypass the old error system and present our formatted text errors using growl notification + StringBuilder formattedTextErrors = new StringBuilder(); + if (level == null || Level.DEFAULT.equals(level)) { // Select the default policy as high or low - KNL-1015 level = defaultLowSecurity() ? Level.LOW : Level.HIGH; // default to system setting @@ -285,7 +325,7 @@ if (cr.getNumberOfErrors() > 0) { // TODO currently no way to get internationalized versions of error messages for (Object errorMsg : cr.getErrorMessages()) { - errorMessages.append(errorMsg.toString()+"\n\r"); + formattedTextErrors.append(errorMsg.toString() + "
"); } } val = cr.getCleanHTML(); @@ -317,11 +357,31 @@ // opportunity to work around the issue, rather than causing a tool stack trace M_log.warn("Unexpected error processing text", e); - // TODO no i18n for now since the other strings are also untranslated - errorMessages.append("Unknown error processing text markup\n");//getResourceLoader().getString("unknown_error_markup")); + formattedTextErrors.append(getResourceLoader().getString("unknown_error_markup") + "\n"); val = null; } + // KNL-1075: re-do the way error messages are displayed + if (showErrorToUser && formattedTextErrors.length() > 0) { + Session session = sessionManager.getCurrentSession(); + + if (showDetailedErrorToUser) { + session.setAttribute("userWarning", formattedTextErrors.toString()); + } else { + session.setAttribute("userWarning", getResourceLoader().getString("content_has_been_cleaned")); + } + } else if (formattedTextErrors.length() > 0 && M_log.isDebugEnabled()) { + Session session = sessionManager.getCurrentSession(); + if (M_log.isDebugEnabled()) M_log.debug("User " + session.getUserEid() + " formatted text error: " + formattedTextErrors.toString()); + } + + /* KNL-1075 - This is primarily for continuing to pass kernel tests + * although it could be used by someone who wants to preserve legacy behavior + */ + if (returnErrorToTool && formattedTextErrors.length() > 0) { + errorMessages.append(formattedTextErrors); + } + return val; } @@ -1013,38 +1073,38 @@ { switch (i) { - case 0: - return '0'; - case 1: - return '1'; - case 2: - return '2'; - case 3: - return '3'; - case 4: - return '4'; - case 5: - return '5'; - case 6: - return '6'; - case 7: - return '7'; - case 8: - return '8'; - case 9: - return '9'; - case 10: - return 'A'; - case 11: - return 'B'; - case 12: - return 'C'; - case 13: - return 'D'; - case 14: - return 'E'; - case 15: - return 'F'; + case 0: + return '0'; + case 1: + return '1'; + case 2: + return '2'; + case 3: + return '3'; + case 4: + return '4'; + case 5: + return '5'; + case 6: + return '6'; + case 7: + return '7'; + case 8: + return '8'; + case 9: + return '9'; + case 10: + return 'A'; + case 11: + return 'B'; + case 12: + return 'C'; + case 13: + return 'D'; + case 14: + return 'E'; + case 15: + return 'F'; } throw new IllegalArgumentException("Invalid digit:" + i); Index: kernel-component/src/main/webapp/WEB-INF/util-components.xml =================================================================== --- kernel-component/src/main/webapp/WEB-INF/util-components.xml (revision 130992) +++ kernel-component/src/main/webapp/WEB-INF/util-components.xml (working copy) @@ -80,6 +80,7 @@ class="org.sakaiproject.util.impl.FormattedTextImpl" init-method="init" singleton="true"> +