This is an automated email from the ASF dual-hosted git repository. dashorst pushed a commit to branch wicket-8.x in repository https://gitbox.apache.org/repos/asf/wicket.git
commit df74ce0650be06a4764c98819c896bc0bd84d98a Author: Emond Papegaaij <[email protected]> AuthorDate: Mon Oct 5 10:53:43 2026 +0200 Escape what the default button field writes into its attributes Form#appendDefaultButtonField builds its markup as a string, so the attributes it fills in are not escaped for it: the css class from the resource bundles, the input name, and the markup id inside the onclick handler. They are now escaped with Strings#escapeMarkup, so a markup id from a custom IMarkupIdGenerator that ignores its documented contract cannot end the literal through a character reference either. Co-Authored-By: Claude Opus 5.5 <[email protected]> --- .../org/apache/wicket/markup/html/form/Form.java | 8 +- .../html/form/FormDefaultButtonFieldTest.java | 117 +++++++++++++++++++++ 2 files changed, 122 insertions(+), 3 deletions(-) diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java index b401e78f44..bc5364822c 100644 --- a/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java +++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/form/Form.java @@ -1269,7 +1269,8 @@ public class Form<T> extends WebMarkupContainer String cssClass = getString(CssUtils.key(Form.class, "hidden-fields")); // div that is not visible (but not display:none either) - buffer.append(String.format("<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\" class=\"%s\">", cssClass)); + buffer.append(String.format("<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\" class=\"%s\">", + Strings.escapeMarkup(cssClass))); // add an empty textfield (otherwise IE doesn't work) buffer.append("<input type=\"text\" tabindex=\"-1\" autocomplete=\"off\"/>"); @@ -1277,9 +1278,10 @@ public class Form<T> extends WebMarkupContainer // add the submitting component final Component submittingComponent = (Component)defaultSubmittingComponent; buffer.append("<input type=\"submit\" tabindex=\"-1\" name=\""); - buffer.append(defaultSubmittingComponent.getInputName()); + buffer.append(Strings.escapeMarkup(defaultSubmittingComponent.getInputName())); buffer.append("\" onclick=\" var b=document.getElementById('"); - buffer.append(submittingComponent.getMarkupId()); + // the browser decodes the attribute before it runs the handler + buffer.append(Strings.escapeMarkup(submittingComponent.getMarkupId())); buffer.append("'); if (b!=null&&b.onclick!=null&&typeof(b.onclick) != 'undefined') { var r = Wicket.bind(b.onclick, b)(); if (r != false) b.click(); } else { b.click(); }; return false;\" "); buffer.append(" />"); diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/form/FormDefaultButtonFieldTest.java b/wicket-core/src/test/java/org/apache/wicket/markup/html/form/FormDefaultButtonFieldTest.java new file mode 100644 index 0000000000..316128fd4e --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/form/FormDefaultButtonFieldTest.java @@ -0,0 +1,117 @@ +/* + * 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.markup.html.form; + +import java.util.Locale; + +import org.apache.wicket.Component; +import org.apache.wicket.DefaultMarkupIdGenerator; +import org.apache.wicket.MarkupContainer; +import org.apache.wicket.core.util.string.CssUtils; +import org.apache.wicket.markup.IMarkupResourceStreamProvider; +import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.resource.loader.IStringResourceLoader; +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; + +/** + * {@link Form#appendDefaultButtonField()} writes its markup as a string, so it has to escape what it + * puts into the attributes itself. + */ +public class FormDefaultButtonFieldTest extends WicketTestCase +{ + /** + * The browser decodes the onclick attribute before it runs the handler, so a character reference + * in the markup id would end the string literal the id is written into. + */ + @Test + public void markupIdIsEscapedInTheOnclickAttribute() + { + tester.getApplication().getMarkupSettings().setMarkupIdGenerator( + new DefaultMarkupIdGenerator() + { + @Override + public String generateMarkupId(Component component, boolean createIfDoesNotExist) + { + // a custom generator that ignores the characters its contract allows + return "button".equals(component.getId()) ? "b'x" + : super.generateMarkupId(component, createIfDoesNotExist); + } + }); + + tester.startPage(new TestPage()); + + tester.assertContains("getElementById\\('b&#39x'\\)"); + } + + /** + * The css class of the hidden div comes from the resource bundles. + */ + @Test + public void cssClassIsEscaped() + { + final String key = CssUtils.key(Form.class, "hidden-fields"); + tester.getApplication().getResourceSettings().getStringResourceLoaders().add(0, + new IStringResourceLoader() + { + @Override + public String loadStringResource(Class<?> clazz, String k, Locale locale, + String style, String variation) + { + return key.equals(k) ? "a\"b" : null; + } + + @Override + public String loadStringResource(Component component, String k, Locale locale, + String style, String variation) + { + return loadStringResource((Class<?>)null, k, locale, style, variation); + } + }); + + tester.startPage(new TestPage()); + + tester.assertContains("class=\"a"b\""); + } + + /** a form with a default button */ + public static class TestPage extends WebPage implements IMarkupResourceStreamProvider + { + private static final long serialVersionUID = 1L; + + /** */ + public TestPage() + { + Form<Void> form = new Form<Void>("form"); + add(form); + + Button button = new Button("button"); + form.add(button); + form.setDefaultButton(button); + } + + @Override + public IResourceStream getMarkupResourceStream(MarkupContainer container, + Class<?> containerClass) + { + return new StringResourceStream("<html><body><form wicket:id=\"form\">" + + "<input wicket:id=\"button\" type=\"submit\"/></form></body></html>"); + } + } +}
