This is an automated email from the ASF dual-hosted git repository.

dashorst pushed a commit to branch wicket-10.x
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit 6a9f1e33f233db2834ab8bedd17c52e378d90bd2
Author: Pedro Santos <[email protected]>
AuthorDate: Sat Sep 12 18:40:35 2026 -0300

    Only turn http, https and ftp URLs into links in DefaultLinkParser
    
    The URL pattern accepted any run of letters followed by "://", while the
    javadoc of SmartLinkLabel and SmartLinkMultiLineLabel only promises web
    URLs. Restrict the pattern to the http, https and ftp schemes, matched
    case-insensitively and not preceded by a letter, so that "sftp://"; is
    not read as "ftp://";. URL_RENDER_STRATEGY now leaves a target with any
    other scheme untouched as well, so a parser pairing it with a wider
    pattern renders the same links.
    
    The javadoc of both labels lists the schemes that are detected. Tests
    cover the accepted and refused schemes in the parser and, for the first
    time, both labels as rendered components.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
---
 .../markup/html/basic/DefaultLinkParser.java       |  28 +++++-
 .../markup/html/basic/SmartLinkLabel.java          |   3 +-
 .../markup/html/basic/SmartLinkMultiLineLabel.java |   3 +-
 .../markup/html/basic/DefaultLinkParserTest.java   |  62 ++++++++++++
 .../markup/html/basic/SmartLinkLabelTest.java      | 104 +++++++++++++++++++++
 5 files changed, 196 insertions(+), 4 deletions(-)

diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/DefaultLinkParser.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/DefaultLinkParser.java
index 61aff60740..dec8b1d963 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/DefaultLinkParser.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/DefaultLinkParser.java
@@ -16,10 +16,15 @@
  */
 package org.apache.wicket.extensions.markup.html.basic;
 
+import java.util.regex.Pattern;
+
 import org.apache.wicket.util.string.AppendingStringBuffer;
 
 /**
  * This implementation adds link render strategies for email addresses and 
urls.
+ * <p>
+ * Only URLs with an <code>http</code>, <code>https</code> or <code>ftp</code> 
scheme are turned
+ * into links. Text with any other scheme is left untouched.
  * 
  * @see SmartLinkLabel
  * @see SmartLinkMultiLineLabel
@@ -31,8 +36,18 @@ public class DefaultLinkParser extends LinkParser
        /** Email address pattern */
        private static final String emailPattern = 
"[\\w\\.\\-\\\\+]+@[\\w\\.\\-]+";
 
-       /** URL pattern */
-       private static final String urlPattern = 
"([a-zA-Z]+://[\\w\\.\\-\\:\\/~]+)[\\w\\.:\\-/?&=%]*";
+       /**
+        * URL pattern. The scheme is restricted to those 
<code>ALLOWED_URL_SCHEME</code> accepts and
+        * must not be preceded by a letter, so that <code>sftp://</code> is 
not read as
+        * <code>ftp://</code>.
+        */
+       private static final String urlPattern = 
"(?<![a-zA-Z])((?i:https?|ftp)://[\\w\\.\\-\\:\\/~]+)[\\w\\.:\\-/?&=%]*";
+
+       /**
+        * The URL schemes <code>URL_RENDER_STRATEGY</code> renders into a 
link. Schemes are
+        * case-insensitive, so <code>HTTP://</code> is accepted like 
<code>http://</code>.
+        */
+       private static final Pattern ALLOWED_URL_SCHEME = 
Pattern.compile("^(?i:https?|ftp)://");
 
        /**
         * Email address render strategy.<br/>
@@ -78,12 +93,21 @@ public class DefaultLinkParser extends LinkParser
        /**
         * Url render strategy.<br/>
         * Renders &lt;a href="{URL}"&gt;{URL}&lt;/a&gt;
+        * <p>
+        * A link target whose scheme is not <code>http</code>, 
<code>https</code> or <code>ftp</code>
+        * is returned as it is, without an anchor. The default URL pattern of 
this parser never matches
+        * such a target, so this only matters to a parser pairing this 
strategy with a wider pattern.
         */
        public static final ILinkRenderStrategy URL_RENDER_STRATEGY = new 
ILinkRenderStrategy()
        {
                @Override
                public String buildLink(final String linkTarget)
                {
+                       if (!ALLOWED_URL_SCHEME.matcher(linkTarget).find())
+                       {
+                               return linkTarget;
+                       }
+
                        int indexOfQuestion = linkTarget.indexOf('?');
                        return "<a href=\"" + linkTarget + "\">" +
                                (indexOfQuestion == -1 ? linkTarget : 
linkTarget.substring(0, indexOfQuestion)) +
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkLabel.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkLabel.java
index 6df3179eb1..bbc70d2eea 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkLabel.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkLabel.java
@@ -32,7 +32,8 @@ import org.apache.wicket.model.Model;
  * is the email address that was detected.
  * <p>
  * Web URLs will be wrapped with a &lt;a href="xxx"&gt;xxx&lt;/a&gt; tag, 
where "xxx" is the URL
- * that was detected (it can be any valid URL type, http://, https://, ftp://, 
etc...)
+ * that was detected. Only URLs with an http://, https:// or ftp:// scheme are 
detected, a URL with
+ * any other scheme is displayed as plain text.
  * 
  * @author Juergen Donnerstag
  */
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkMultiLineLabel.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkMultiLineLabel.java
index 74cb1ae880..c3b1dcb638 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkMultiLineLabel.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkMultiLineLabel.java
@@ -32,7 +32,8 @@ import org.apache.wicket.util.string.Strings;
  * is the email address that was detected.
  * <p>
  * Web URLs will be wrapped with a &lt;a href="xxx"&gt;xxx&lt;/a&gt; tag, 
where "xxx" is the URL
- * that was detected (it can be any valid URL type, http://, https://, ftp://, 
etc...)
+ * that was detected. Only URLs with an http://, https:// or ftp:// scheme are 
detected, a URL with
+ * any other scheme is displayed as plain text.
  * 
  * @author Juergen Donnerstag
  */
diff --git 
a/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/basic/DefaultLinkParserTest.java
 
b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/basic/DefaultLinkParserTest.java
index 1f723b0b94..e2d824d543 100644
--- 
a/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/basic/DefaultLinkParserTest.java
+++ 
b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/basic/DefaultLinkParserTest.java
@@ -20,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Test for {@link DefaultLinkParser}.
@@ -109,4 +111,64 @@ public class DefaultLinkParserTest
                assertEquals(testExpectedLink, result,
                        "Expected chars to left of - to be included in the 
link.");
        }
+
+       /**
+        * The schemes the javadoc promises are linkified, case-insensitively.
+        */
+       @ParameterizedTest
+       @ValueSource(strings = { "http://www.test.com";, "https://www.test.com";, 
"ftp://ftp.test.com/pub";,
+                       "HTTPS://www.test.com", "Http://www.test.com" })
+       public void allowedSchemesAreLinkified(String url)
+       {
+               ILinkParser parser = new DefaultLinkParser();
+               assertEquals("<a href=\"" + url + "\">" + url + "</a>", 
parser.parse(url));
+               assertEquals("text <a href=\"" + url + "\">" + url + "</a> 
text",
+                       parser.parse("text " + url + " text"));
+       }
+
+       /**
+        * A URL with any other scheme is left as it is, alone and inside other 
text.
+        */
+       @ParameterizedTest
+       @ValueSource(strings = { "sftp://ftp.test.com/pub";, 
"ssh://www.test.com", "file://etc/hosts",
+                       "gopher://www.test.com/1";, "httpx://www.test.com", 
"Ftps://ftp.test.com",
+                       "news://www.test.com?x=1"; })
+       public void otherSchemesAreNotLinkified(String url)
+       {
+               ILinkParser parser = new DefaultLinkParser();
+               assertEquals(url, parser.parse(url));
+               assertEquals("Look at " + url + " please", parser.parse("Look 
at " + url + " please"));
+       }
+
+       /**
+        * A URL that is not linkified does not affect an allowed one next to 
it, and the other way
+        * round.
+        */
+       @Test
+       public void mixedSchemesInOneText()
+       {
+               ILinkParser parser = new DefaultLinkParser();
+               assertEquals(
+                       "sftp://ftp.test.com and <a 
href=\"http://www.test.com\";>http://www.test.com</a>",
+                       parser.parse("sftp://ftp.test.com and 
http://www.test.com";));
+       }
+
+       /**
+        * The render strategy only builds links for http, https and ftp on its 
own, whatever the pattern
+        * it is paired with matched.
+        */
+       @Test
+       public void urlRenderStrategyOnlyLinksAllowedSchemes()
+       {
+               ILinkRenderStrategy strategy = 
DefaultLinkParser.URL_RENDER_STRATEGY;
+               assertEquals("<a 
href=\"http://www.test.com\";>http://www.test.com</a>",
+                       strategy.buildLink("http://www.test.com";));
+               assertEquals("sftp://ftp.test.com/pub";, 
strategy.buildLink("sftp://ftp.test.com/pub";));
+               assertEquals("Ssh://www.test.com", 
strategy.buildLink("Ssh://www.test.com"));
+
+               LinkParser wide = new LinkParser();
+               wide.addLinkRenderStrategy("[a-zA-Z]+://[\\w\\.\\/]+", 
strategy);
+               assertEquals("sftp://ftp.test.com/pub";, 
wide.parse("sftp://ftp.test.com/pub";));
+               assertEquals("<a href=\"http://test\";>http://test</a>", 
wide.parse("http://test";));
+       }
 }
diff --git 
a/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkLabelTest.java
 
b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkLabelTest.java
new file mode 100644
index 0000000000..777282426f
--- /dev/null
+++ 
b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/basic/SmartLinkLabelTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.wicket.extensions.markup.html.basic;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link SmartLinkLabel} and {@link SmartLinkMultiLineLabel} as 
rendered components.
+ */
+public class SmartLinkLabelTest extends WicketTestCase
+{
+       private static final String OTHER_URL = "sftp://ftp.test.com/pub";;
+
+       @Test
+       public void labelLinkifiesWebUrl()
+       {
+               String body = render(new SmartLinkLabel("component", "see 
http://www.test.com now"));
+
+               assertEquals("see <a 
href=\"http://www.test.com\";>http://www.test.com</a> now", body);
+       }
+
+       @Test
+       public void labelDoesNotLinkifyOtherScheme()
+       {
+               String body = render(new SmartLinkLabel("component", "see " + 
OTHER_URL + " now"));
+
+               assertEquals("see " + OTHER_URL + " now", body);
+       }
+
+       @Test
+       public void labelStillEscapesMarkup()
+       {
+               String body = render(new SmartLinkLabel("component", 
"<b>bold</b> & http://www.test.com";));
+
+               assertEquals(
+                       "&lt;b&gt;bold&lt;/b&gt; &amp; <a 
href=\"http://www.test.com\";>http://www.test.com</a>",
+                       body);
+       }
+
+       /**
+        * A quote or an angle bracket in the model text is escaped before the 
parser runs. The URL
+        * pattern admits '&amp;' but not ';', so an escaped quote right after 
a URL leaves a truncated
+        * '&amp;quot' inside the href, which a browser decodes into the 
attribute's value.
+        */
+       @Test
+       public void labelEscapesMarkupAroundUrl()
+       {
+               String body = render(new SmartLinkLabel("component",
+                       "see http://www.test.com/a\"; and <b>bold</b> now"));
+
+               assertEquals("see <a 
href=\"http://www.test.com/a&quot\";>http://www.test.com/a&quot</a>"
+                       + "; and &lt;b&gt;bold&lt;/b&gt; now", body);
+       }
+
+       @Test
+       public void multiLineLabelLinkifiesWebUrl()
+       {
+               String body = render(
+                       new SmartLinkMultiLineLabel("component", "line 1 
http://www.test.com\nline 2"));
+
+               assertEquals("<p>line 1 <a 
href=\"http://www.test.com\";>http://www.test.com</a><br/>line 2</p>",
+                       body);
+       }
+
+       @Test
+       public void multiLineLabelDoesNotLinkifyOtherScheme()
+       {
+               String body = render(
+                       new SmartLinkMultiLineLabel("component", "line 1 " + 
OTHER_URL + "\nline 2"));
+
+               assertEquals("<p>line 1 " + OTHER_URL + "<br/>line 2</p>", 
body);
+       }
+
+       private String render(Component component)
+       {
+               tester.startComponentInPage(component);
+               String response = tester.getLastResponseAsString();
+
+               String open = "<span wicket:id=\"component\">";
+               int start = response.indexOf(open);
+               int end = response.indexOf("</span>", start);
+               assertTrue(start != -1 && end != -1, "component not rendered 
in:\n" + response);
+               return response.substring(start + open.length(), end);
+       }
+}

Reply via email to