This is an automated email from the ASF dual-hosted git repository. juanpablo pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/jspwiki.git
commit 5b9dab42817c0c2f67eb19ffe7921a833d3d5b7c Author: Juan Pablo Santos RodrÃguez <juanpablo.san...@gmail.com> AuthorDate: Sat Apr 5 14:46:27 2025 +0200 Proper HTML escaping + introduce WikiHtmlInline to be able to avoid HTML escaping performed by Flexmark --- .../ImageLinkNodePostProcessorState.java | 7 ++- .../LocalLinkNodePostProcessorState.java | 11 +++-- .../NodePostProcessorStateCommonOperations.java | 11 ++--- .../PluginLinkNodePostProcessorState.java | 15 +++---- .../VariableLinkNodePostProcessorState.java | 5 +-- .../jspwikilinks/postprocessor/WikiHtmlInline.java | 52 ++++++++++++++++++++++ .../markdown/renderer/JSPWikiLinkRenderer.java | 13 +++++- .../wiki/render/markdown/MarkdownRendererTest.java | 36 +++++++++++++-- 8 files changed, 116 insertions(+), 34 deletions(-) diff --git a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/ImageLinkNodePostProcessorState.java b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/ImageLinkNodePostProcessorState.java index 705f0a1b4..5c723cea7 100644 --- a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/ImageLinkNodePostProcessorState.java +++ b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/ImageLinkNodePostProcessorState.java @@ -18,7 +18,6 @@ */ package org.apache.wiki.markdown.extensions.jspwikilinks.postprocessor; -import com.vladsch.flexmark.ast.HtmlInline; import com.vladsch.flexmark.util.ast.Node; import com.vladsch.flexmark.util.ast.NodeTracker; import com.vladsch.flexmark.util.sequence.CharSubSequence; @@ -49,9 +48,9 @@ public class ImageLinkNodePostProcessorState implements NodePostProcessorState< */ @Override public void process( final NodeTracker state, final JSPWikiLink link ) { - final HtmlInline img = new HtmlInline( CharSubSequence.of( "<img class=\"inline\" " + - "src=\"" + urlRef + "\" " + - "alt=\"" + link.getText().toString() + "\" />" ) ); + final WikiHtmlInline img = WikiHtmlInline.of( "<img class=\"inline\" " + + "src=\"" + urlRef + "\" " + + "alt=\"" + link.getText().toString() + "\" />" ); if( ( isLinkFromText && linkOperations.isExternalLink( link.getText().toString() ) ) || ( isLinkFromText && linkOperations.linkExists( link.getText().toString() ) ) ) { link.setUrl( CharSubSequence.of( urlRef ) ); diff --git a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/LocalLinkNodePostProcessorState.java b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/LocalLinkNodePostProcessorState.java index 9e011f624..5fe77cf99 100644 --- a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/LocalLinkNodePostProcessorState.java +++ b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/LocalLinkNodePostProcessorState.java @@ -18,7 +18,6 @@ */ package org.apache.wiki.markdown.extensions.jspwikilinks.postprocessor; -import com.vladsch.flexmark.ast.HtmlInline; import com.vladsch.flexmark.util.ast.Node; import com.vladsch.flexmark.util.ast.NodeTracker; import com.vladsch.flexmark.util.sequence.CharSubSequence; @@ -66,7 +65,7 @@ public class LocalLinkNodePostProcessorState implements NodePostProcessorState< final String attlink = wikiContext.getURL( ContextEnum.PAGE_ATTACH.getRequestContext(), link.getUrl().toString() ); link.setUrl( CharSubSequence.of( attlink ) ); link.removeChildren(); - final HtmlInline content = new HtmlInline( CharSubSequence.of( link.getText().toString() ) ); + final WikiHtmlInline content = WikiHtmlInline.of( link.getText().toString(), wikiContext ); link.appendChild( content ); state.nodeAddedWithChildren( content ); addAttachmentLink( state, link ); @@ -79,7 +78,7 @@ public class LocalLinkNodePostProcessorState implements NodePostProcessorState< final String matchedLink = linkOperations.linkIfExists( link.getUrl().toString() ); if( matchedLink != null ) { String sectref = "#section-" + wikiContext.getEngine().encodeName( matchedLink + "-" + MarkupParser.wikifyLink( namedSection ) ); - sectref = sectref.replace('%', '_'); + sectref = sectref.replace( '%', '_' ); link.setUrl( CharSubSequence.of( wikiContext.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), link.getUrl().toString() + sectref ) ) ); } else { link.setUrl( CharSubSequence.of( wikiContext.getURL( ContextEnum.PAGE_EDIT.getRequestContext(), link.getUrl().toString() ) ) ); @@ -96,9 +95,9 @@ public class LocalLinkNodePostProcessorState implements NodePostProcessorState< void addAttachmentLink( final NodeTracker state, final JSPWikiLink link ) { final String infolink = wikiContext.getURL( ContextEnum.PAGE_INFO.getRequestContext(), link.getWikiLink() ); final String imglink = wikiContext.getURL( ContextEnum.PAGE_NONE.getRequestContext(), "images/attachment_small.png" ); - final HtmlInline aimg = new HtmlInline( CharSubSequence.of( "<a href=\""+ infolink + "\" class=\"infolink\">" + - "<img src=\""+ imglink + "\" border=\"0\" alt=\"(info)\" />" + - "</a>" ) ); + final WikiHtmlInline aimg = WikiHtmlInline.of( "<a href=\""+ infolink + "\" class=\"infolink\">" + + "<img src=\""+ imglink + "\" border=\"0\" alt=\"(info)\" />" + + "</a>" ) ; link.insertAfter( aimg ); state.nodeAdded( aimg ); } diff --git a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/NodePostProcessorStateCommonOperations.java b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/NodePostProcessorStateCommonOperations.java index a8a34782c..5b5239140 100644 --- a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/NodePostProcessorStateCommonOperations.java +++ b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/NodePostProcessorStateCommonOperations.java @@ -18,10 +18,8 @@ */ package org.apache.wiki.markdown.extensions.jspwikilinks.postprocessor; -import com.vladsch.flexmark.ast.HtmlInline; import com.vladsch.flexmark.util.ast.Node; import com.vladsch.flexmark.util.ast.NodeTracker; -import com.vladsch.flexmark.util.sequence.CharSubSequence; import org.apache.wiki.api.core.Context; import org.apache.wiki.api.core.ContextEnum; import org.apache.wiki.markdown.nodes.JSPWikiLink; @@ -52,8 +50,7 @@ class NodePostProcessorStateCommonOperations { if( useOutlinkImage && !wysiwygEditorMode ) { final String m_outlinkImageURL = wikiContext.getURL( ContextEnum.PAGE_NONE.getRequestContext(), MarkupParser.OUTLINK_IMAGE ); - final HtmlInline img = new HtmlInline( CharSubSequence.of( "<img class=\""+ MarkupParser.OUTLINK + "\" " + - "alt=\"\" src=\""+ m_outlinkImageURL + "\" />" ) ); + final WikiHtmlInline img = WikiHtmlInline.of( "<img class=\""+ MarkupParser.OUTLINK + "\" " + "alt=\"\" src=\""+ m_outlinkImageURL + "\" />" ) ; node.insertAfter( img ); state.nodeAdded( img ); } @@ -62,16 +59,14 @@ class NodePostProcessorStateCommonOperations { static String inlineLinkTextOnWysiwyg( final NodeTracker state, final JSPWikiLink link, final boolean wysiwygEditorMode ) { final String line = link.getUrl().toString(); if( wysiwygEditorMode ) { - final HtmlInline content = new HtmlInline( CharSubSequence.of( "[" + line + "]()" ) ); + final WikiHtmlInline content = WikiHtmlInline.of( "[" + line + "]()" ); addContent( state, link, content ); } return line; } static void makeError( final NodeTracker state, final Node node, final String errMsg ) { - final HtmlInline error = new HtmlInline( CharSubSequence.of( "<span class=\"error\">" + - errMsg + - "</span>" ) ); + final WikiHtmlInline error = WikiHtmlInline.of( "<span class=\"error\">" + errMsg + "</span>" ); addContent( state, node, error ); } diff --git a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/PluginLinkNodePostProcessorState.java b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/PluginLinkNodePostProcessorState.java index e4dfeb72f..f28a01c31 100644 --- a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/PluginLinkNodePostProcessorState.java +++ b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/PluginLinkNodePostProcessorState.java @@ -18,7 +18,6 @@ */ package org.apache.wiki.markdown.extensions.jspwikilinks.postprocessor; -import com.vladsch.flexmark.ast.HtmlInline; import com.vladsch.flexmark.ext.toc.TocBlock; import com.vladsch.flexmark.util.ast.Node; import com.vladsch.flexmark.util.ast.NodeTracker; @@ -71,7 +70,7 @@ public class PluginLinkNodePostProcessorState implements NodePostProcessorState< // if( pluginContent != null ) { final String pluginInvocation = pluginInvocation( link.getText().toString(), pluginContent ); - final HtmlInline content = new HtmlInline( CharSubSequence.of( pluginInvocation ) ); + final WikiHtmlInline content = WikiHtmlInline.of( pluginInvocation ); pluginContent.executeParse( wikiContext ); NodePostProcessorStateCommonOperations.addContent( state, link, content ); } @@ -110,12 +109,12 @@ public class PluginLinkNodePostProcessorState implements NodePostProcessorState< void handleTableOfContentsPlugin(final NodeTracker state, final JSPWikiLink link) { if( !m_wysiwygEditorMode ) { final ResourceBundle rb = Preferences.getBundle( wikiContext, Plugin.CORE_PLUGINS_RESOURCEBUNDLE ); - final HtmlInline divToc = new HtmlInline( CharSubSequence.of( "<div class=\"toc\">\n" ) ); - final HtmlInline divCollapseBox = new HtmlInline( CharSubSequence.of( "<div class=\"collapsebox\">\n" ) ); - final HtmlInline divsClosing = new HtmlInline( CharSubSequence.of( "</div>\n</div>\n" ) ); - final HtmlInline h4Title = new HtmlInline( CharSubSequence.of( "<h4 id=\"section-TOC\">" + // FIXME proper plugin parameters handling - rb.getString( "tableofcontents.title" ) + - "</h4>\n" ) ); + final WikiHtmlInline divToc = WikiHtmlInline.of( "<div class=\"toc\">\n" ); + final WikiHtmlInline divCollapseBox = WikiHtmlInline.of( "<div class=\"collapsebox\">\n" ); + final WikiHtmlInline divsClosing = WikiHtmlInline.of( "</div>\n</div>\n" ); + final WikiHtmlInline h4Title = WikiHtmlInline.of( "<h4 id=\"section-TOC\">" + // FIXME proper plugin parameters handling + rb.getString( "tableofcontents.title" ) + + "</h4>\n" ); final TocBlock toc = new TocBlock( CharSubSequence.of( "[TOC]" ), CharSubSequence.of( "levels=1-3" ) ); link.insertAfter( divToc ); diff --git a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/VariableLinkNodePostProcessorState.java b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/VariableLinkNodePostProcessorState.java index 491716c8c..55a5317ac 100644 --- a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/VariableLinkNodePostProcessorState.java +++ b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/VariableLinkNodePostProcessorState.java @@ -18,11 +18,8 @@ */ package org.apache.wiki.markdown.extensions.jspwikilinks.postprocessor; -import com.vladsch.flexmark.ast.HtmlInline; import com.vladsch.flexmark.util.ast.Node; import com.vladsch.flexmark.util.ast.NodeTracker; -import com.vladsch.flexmark.util.sequence.CharSubSequence; -import org.apache.commons.text.StringEscapeUtils; import org.apache.wiki.api.core.Context; import org.apache.wiki.api.exceptions.NoSuchVariableException; import org.apache.wiki.markdown.nodes.JSPWikiLink; @@ -54,7 +51,7 @@ public class VariableLinkNodePostProcessorState implements NodePostProcessorStat if( !m_wysiwygEditorMode ) { try { final String parsedVariable = wikiContext.getEngine().getManager( VariableManager.class ).parseAndGetValue( wikiContext, variable ); - final HtmlInline content = new HtmlInline( CharSubSequence.of( StringEscapeUtils.escapeXml11( parsedVariable ) ) ); + final WikiHtmlInline content = WikiHtmlInline.of( parsedVariable, wikiContext ); NodePostProcessorStateCommonOperations.addContent( state, link, content ); } catch( final NoSuchVariableException e ) { NodePostProcessorStateCommonOperations.makeError( state, link, "No such variable: " + variable + " (" + e.getMessage() + ")" ); diff --git a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/WikiHtmlInline.java b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/WikiHtmlInline.java new file mode 100644 index 000000000..851a9538a --- /dev/null +++ b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/extensions/jspwikilinks/postprocessor/WikiHtmlInline.java @@ -0,0 +1,52 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + */ +package org.apache.wiki.markdown.extensions.jspwikilinks.postprocessor; + +import com.vladsch.flexmark.ast.HtmlInline; +import com.vladsch.flexmark.util.sequence.BasedSequence; +import org.apache.wiki.api.core.Context; +import org.apache.wiki.parser.MarkupParser; +import org.apache.wiki.util.TextUtil; + + +/** + * <p>Regular {@link HtmlInline} get escaped depending on the value of the {@code MarkupParser.PROP_ALLOWHTML} property.</p> + * <p>However, wikilink post processors inject additional HtmlInline that must not be escaped. Subclassing {@link HtmlInline} + * allows us to register a custom {@code NodeRenderingHandler} at {@code JSPWikiLinkRenderer} to bypass this limitation.</p> + */ +public class WikiHtmlInline extends HtmlInline { + + private WikiHtmlInline( final BasedSequence chars ) { + super( chars ); + } + + public static WikiHtmlInline of( final String str ) { + return new WikiHtmlInline( BasedSequence.of( str ) ); + } + + public static WikiHtmlInline of( final String str, final Context context ) { + final boolean allowHtml = context.getBooleanWikiProperty( MarkupParser.PROP_ALLOWHTML, false ); + if( allowHtml ) { + return WikiHtmlInline.of( str ); + } else { + return new WikiHtmlInline( BasedSequence.of( TextUtil.escapeHTMLEntities( str ) ) ); + } + } + +} diff --git a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/renderer/JSPWikiLinkRenderer.java b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/renderer/JSPWikiLinkRenderer.java index 79ca8fe2c..06e876588 100644 --- a/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/renderer/JSPWikiLinkRenderer.java +++ b/jspwiki-markdown/src/main/java/org/apache/wiki/markdown/renderer/JSPWikiLinkRenderer.java @@ -24,6 +24,7 @@ import com.vladsch.flexmark.html.renderer.NodeRenderer; import com.vladsch.flexmark.html.renderer.NodeRendererContext; import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; import com.vladsch.flexmark.html.renderer.ResolvedLink; +import org.apache.wiki.markdown.extensions.jspwikilinks.postprocessor.WikiHtmlInline; import org.apache.wiki.markdown.nodes.JSPWikiLink; import java.util.HashSet; @@ -49,7 +50,7 @@ public class JSPWikiLinkRenderer implements NodeRenderer { * {@inheritDoc} */ @Override - public void render(final JSPWikiLink node, final NodeRendererContext context, final HtmlWriter html) { + public void render( final JSPWikiLink node, final NodeRendererContext context, final HtmlWriter html ) { if (context.isDoNotRenderLinks()) { context.renderChildren(node); } else { @@ -66,6 +67,16 @@ public class JSPWikiLinkRenderer implements NodeRenderer { } } } ) ); + set.add( new NodeRenderingHandler<>( WikiHtmlInline.class, new NodeRenderingHandler.CustomNodeRenderer<>() { + + /** + * {@inheritDoc} + */ + @Override + public void render( final WikiHtmlInline node, final NodeRendererContext context, final HtmlWriter html ) { + html.raw( node.getChars().normalizeEOL() ); + } + } ) ); return set; } diff --git a/jspwiki-markdown/src/test/java/org/apache/wiki/render/markdown/MarkdownRendererTest.java b/jspwiki-markdown/src/test/java/org/apache/wiki/render/markdown/MarkdownRendererTest.java index cf3d8f6e5..1d0f05013 100644 --- a/jspwiki-markdown/src/test/java/org/apache/wiki/render/markdown/MarkdownRendererTest.java +++ b/jspwiki-markdown/src/test/java/org/apache/wiki/render/markdown/MarkdownRendererTest.java @@ -83,6 +83,36 @@ public class MarkdownRendererTest { testEngine.getWikiProperties().remove( "jspwiki.translatorReader.useOutlinkImage" ); } + @Test + public void testMarkupExtensionHtmlEscaping() throws Exception { + testEngine.getWikiProperties().setProperty( "jspwiki.translatorReader.useOutlinkImage", "true" ); + final String src = "This should be an [external <strong>link</strong>](https://jspwiki.apache.org)"; + + Assertions.assertEquals( "<p>This should be an <a href=\"https://jspwiki.apache.org\" class=\"external\">external <strong>link</strong></a><img class=\"outlink\" alt=\"\" src=\"/test/images/out.png\" /></p>\n", + translate( src ) ); + testEngine.getWikiProperties().remove( "jspwiki.translatorReader.useOutlinkImage" ); + } + + @Test + public void testAttachmentLink1() throws Exception { + newPage( "Hyperlink" ); + final String expected = "<p>This should be a <a href=\"/test/attach/Link%20%3Cstrong%3Ebold%3C/strong%3E\" class=\"attachment\">Link <strong>bold</strong></a><a href=\"/test/PageInfo.jsp?page=Link%20%3Cstrong%3Ebold%3C/strong%3E\" class=\"infolink\"><img src=\"/test/images/attachment_small.png\" border=\"0\" alt=\"(info)\" /></a></p>\n"; + Assertions.assertEquals( expected, translate( "This should be a [Link <strong>bold</strong>]()" ) ); + } + + @Test + public void testMarkupExtensionHtmlAllowsMDInsideLinks() throws Exception { + newPage( "Hyperlink" ); + final String expected = "<p>This should be a <a href=\"/test/Edit.jsp?page=Link%20**bold**\" title=\"Create "Link **bold**"\" class=\"createpage\">Link <strong>bold</strong></a></p>\n"; + Assertions.assertEquals( expected, translate( "This should be a [Link **bold**]()" ) ); + } + + @Test + public void testMarkupExtensionAllowsHtmlFromPlugins() throws Exception { + final String src = "<strong>string</strong> [{SamplePlugin text=test tag=strong}]()"; + Assertions.assertEquals( "<p><strong>string</strong> <strong>test</strong></p>\n", translate( src ) ); + } + @Test public void testMarkupExtensionInterWikiLink() throws Exception { final String src = "This should be an [interwiki link](JSPWiki:About)"; @@ -158,8 +188,8 @@ public class MarkdownRendererTest { @Test public void testMarkupExtensionPlugin() throws Exception { - final String src = "[{SamplePlugin text=test}]()"; - Assertions.assertEquals( "<p>test</p>\n", translate( src ) ); + final String src = "<strong>string</strong> [{SamplePlugin text=test}]()"; + Assertions.assertEquals( "<p><strong>string</strong> test</p>\n", translate( src ) ); } @Test @@ -230,7 +260,7 @@ public class MarkdownRendererTest { } @Test - public void testAttachmentLink() throws Exception { + public void testAttachmentLink0() throws Exception { final String src = "This should be an [attachment link](Test/TestAtt.txt)"; newPage( "Test" );