This is an automated email from the ASF dual-hosted git repository. papegaaij pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/wicket.git
commit 83dbc726eafe699ecc5fd78a5f1755de9503582c 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 = "<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(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"); + } +}
