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 = "<script>x=1</script>"; + + /** 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)); + } +}
