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

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

commit 2db7d75061e1d7258122f75f5e60c967fb8d9f76
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 | 153 +++++++++++++++++++++
 4 files changed, 164 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 8ac6d85d90..d9cf78ed9e 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 d061ac4413..17368aa4b3 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;
 
 /**
@@ -403,7 +404,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 f38883d44e..19ccfc6e7a 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
@@ -28,6 +28,7 @@ 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.convert.IConverter;
+import org.apache.wicket.util.string.Strings;
 
 /**
  * An inplace editor much like {@link AjaxEditableLabel}, but now with support 
for multi line
@@ -107,7 +108,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..cbe25a621f
--- /dev/null
+++ 
b/wicket-extensions/src/test/java/org/apache/wicket/extensions/ajax/markup/html/AjaxEditableLabelNullLabelEscapeTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.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.jupiter.api.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.
+ */
+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(response.contains(ESCAPED), "the null label should 
be escaped");
+               assertFalse(response.contains(MARKUP),
+                       "the null label should not reach the markup as markup");
+       }
+
+       @Test
+       void nullLabelOfAnEditableLabelIsEscaped()
+       {
+               assertNullLabelEscaped(plainLabel());
+       }
+
+       @Test
+       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
+       void nullLabelOfAnEditableChoiceLabelIsEscaped()
+       {
+               assertNullLabelEscaped(choiceLabel());
+       }
+
+       /**
+        * Escaping follows the setting the panel passes to its label, so 
markup can still be shown on
+        * purpose.
+        */
+       @Test
+       void nullLabelIsNotEscapedWhenEscapeModelStringsIsFalse()
+       {
+               AjaxEditableLabel<String> label = plainLabel();
+               label.setEscapeModelStrings(false);
+               tester.startPage(new NullLabelPage(label));
+
+               assertTrue(tester.getLastResponseAsString().contains(MARKUP),
+                       "escaping is off, so the null label should be written 
as is");
+       }
+}

Reply via email to