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

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

commit 6d6930c0ec3aa3f2e80933f74ebb4abd7359ad09
Author: Emond Papegaaij <[email protected]>
AuthorDate: Thu Aug 20 12:32:22 2026 +0200

    Escape the value an editable label shows for an empty model
    
    The label of each of the three editable labels writes defaultNullLabel() to 
the
    markup as it came when the model is empty, and hands the value over to the 
label
    component when it is not, which escapes it according to escapeModelStrings. 
So the
    placeholder was written as markup while the value beside it was escaped, and
    defaultNullLabel() is protected, so what it returns is not necessarily the
    constant the default implementation gives back.
    
    AjaxEditableChoiceLabel shows it plainest. One branch of its 
onComponentTagBody
    writes the value the IChoiceRenderer produced, which is escaped, and the 
other
    writes defaultNullLabel(), which was not.
    
    The value is escaped where it is written, following escapeModelStrings. The 
label
    takes that setting from the panel in onConfigure, next to the delegation 
that is
    already there, so turning escaping off on the panel turns it off for this 
value as
    well; the test covers both.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../ajax/markup/html/AjaxEditableChoiceLabel.java  |   4 +-
 .../ajax/markup/html/AjaxEditableLabel.java        |   5 +-
 .../markup/html/AjaxEditableMultiLineLabel.java    |   5 +-
 .../html/AjaxEditableLabelNullLabelEscapeTest.java | 154 +++++++++++++++++++++
 4 files changed, 165 insertions(+), 3 deletions(-)

diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableChoiceLabel.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableChoiceLabel.java
index 431b790e7b..c546046950 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableChoiceLabel.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableChoiceLabel.java
@@ -304,7 +304,9 @@ public class AjaxEditableChoiceLabel<T> extends 
AjaxEditableLabel<T>
 
                                if (Strings.isEmpty(displayValue))
                                {
-                                       replaceComponentTagBody(markupStream, 
openTag, defaultNullLabel());
+                                       String nullLabel = defaultNullLabel();
+                                       replaceComponentTagBody(markupStream, 
openTag,
+                                               getEscapeModelStrings() ? 
Strings.escapeMarkup(nullLabel) : nullLabel);
                                }
                                else
                                {
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabel.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabel.java
index 47329aa74e..0dc999ed03 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabel.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabel.java
@@ -40,6 +40,7 @@ import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.IObjectClassAwareModel;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.util.convert.IConverter;
+import org.apache.wicket.util.string.Strings;
 import org.apache.wicket.validation.IValidator;
 
 /**
@@ -415,7 +416,9 @@ public class AjaxEditableLabel<T> extends Panel implements 
IGenericComponent<T,
                                Object modelObject = getDefaultModelObject();
                                if ((modelObject == null) || (modelObject 
instanceof String && ((String) modelObject).isEmpty()))
                                {
-                                       replaceComponentTagBody(markupStream, 
openTag, defaultNullLabel());
+                                       String nullLabel = defaultNullLabel();
+                                       replaceComponentTagBody(markupStream, 
openTag,
+                                               getEscapeModelStrings() ? 
Strings.escapeMarkup(nullLabel) : nullLabel);
                                }
                                else
                                {
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableMultiLineLabel.java
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableMultiLineLabel.java
index 65e297d8ec..c1587574a7 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableMultiLineLabel.java
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableMultiLineLabel.java
@@ -27,6 +27,7 @@ import org.apache.wicket.markup.html.basic.MultiLineLabel;
 import org.apache.wicket.markup.html.form.FormComponent;
 import org.apache.wicket.markup.html.form.TextArea;
 import org.apache.wicket.model.IModel;
+import org.apache.wicket.util.string.Strings;
 
 /**
  * An inplace editor much like {@link AjaxEditableLabel}, but now with support 
for multi line
@@ -100,7 +101,9 @@ public class AjaxEditableMultiLineLabel<T> extends 
AjaxEditableLabel<T>
                                Object modelObject = getDefaultModelObject();
                                if ((modelObject == null) || (modelObject 
instanceof String && ((String) modelObject).isEmpty()))
                                {
-                                       replaceComponentTagBody(markupStream, 
openTag, defaultNullLabel());
+                                       String nullLabel = defaultNullLabel();
+                                       replaceComponentTagBody(markupStream, 
openTag,
+                                               getEscapeModelStrings() ? 
Strings.escapeMarkup(nullLabel) : nullLabel);
                                }
                                else
                                {
diff --git 
a/wicket-extensions/src/test/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabelNullLabelEscapeTest.java
 
b/wicket-extensions/src/test/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabelNullLabelEscapeTest.java
new file mode 100644
index 0000000000..4e25b66128
--- /dev/null
+++ 
b/wicket-extensions/src/test/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabelNullLabelEscapeTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.ajax.markup.html;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.markup.IMarkupResourceStreamProvider;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.StringResourceStream;
+import org.apache.wicket.util.tester.WicketTestCase;
+import org.junit.Test;
+
+/**
+ * The value an editable label shows when its model is empty comes from an 
overridable method, so it
+ * is escaped where it is written, the same way the label escapes the model 
value it normally shows.
+ * <p>
+ * The escaping follows escapeModelStrings, which the label takes from the 
panel in onConfigure, so
+ * setting it on the panel decides whether the null label is escaped.
+ */
+public class AjaxEditableLabelNullLabelEscapeTest extends WicketTestCase
+{
+       private static final String MARKUP = "<script>x=1</script>";
+
+       private static final String ESCAPED = 
"&lt;script&gt;x=1&lt;/script&gt;";
+
+       /** Renders one editable label over an empty model, so the null label 
is what is shown. */
+       private static class NullLabelPage extends WebPage implements 
IMarkupResourceStreamProvider
+       {
+               private NullLabelPage(AjaxEditableLabel<String> label)
+               {
+                       add(label);
+               }
+
+               @Override
+               public IResourceStream getMarkupResourceStream(MarkupContainer 
container,
+                       Class<?> containerClass)
+               {
+                       return new StringResourceStream(
+                               "<html><body><div 
wicket:id=\"label\"></div></body></html>");
+               }
+       }
+
+       private static AjaxEditableLabel<String> plainLabel()
+       {
+               return new AjaxEditableLabel<String>("label", Model.of(""))
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       protected String defaultNullLabel()
+                       {
+                               return MARKUP;
+                       }
+               };
+       }
+
+       private static AjaxEditableLabel<String> multiLineLabel()
+       {
+               return new AjaxEditableMultiLineLabel<String>("label", 
Model.of(""))
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       protected String defaultNullLabel()
+                       {
+                               return MARKUP;
+                       }
+               };
+       }
+
+       private static AjaxEditableLabel<String> choiceLabel()
+       {
+               IModel<java.util.List<String>> choices = 
Model.ofList(Arrays.asList("A", "B"));
+               return new AjaxEditableChoiceLabel<String>("label", 
Model.of(""), choices)
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       protected String defaultNullLabel()
+                       {
+                               return MARKUP;
+                       }
+               };
+       }
+
+       private void assertNullLabelEscaped(AjaxEditableLabel<String> label)
+       {
+               tester.startPage(new NullLabelPage(label));
+
+               String response = tester.getLastResponseAsString();
+               assertTrue("the null label should be escaped",
+                       response.contains(ESCAPED));
+               assertFalse("the null label should not reach the markup as 
markup",
+                       response.contains(MARKUP));
+       }
+
+       @Test
+       public void nullLabelOfAnEditableLabelIsEscaped()
+       {
+               assertNullLabelEscaped(plainLabel());
+       }
+
+       @Test
+       public void nullLabelOfAnEditableMultiLineLabelIsEscaped()
+       {
+               assertNullLabelEscaped(multiLineLabel());
+       }
+
+       /**
+        * The choice label writes the null label in one branch and the value 
the renderer produced in
+        * the other, so both branches of the same method have to escape.
+        */
+       @Test
+       public void nullLabelOfAnEditableChoiceLabelIsEscaped()
+       {
+               assertNullLabelEscaped(choiceLabel());
+       }
+
+       /**
+        * Escaping follows the setting the panel passes to its label, so 
markup can still be shown on
+        * purpose.
+        */
+       @Test
+       public void nullLabelIsNotEscapedWhenEscapeModelStringsIsFalse()
+       {
+               AjaxEditableLabel<String> label = plainLabel();
+               label.setEscapeModelStrings(false);
+               tester.startPage(new NullLabelPage(label));
+
+               assertTrue("escaping is off, so the null label should be 
written as is",
+                       tester.getLastResponseAsString().contains(MARKUP));
+       }
+}

Reply via email to