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&amp;&amp;b.onclick!=null&amp;&amp;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&#39x"
+                                               : 
super.generateMarkupId(component, createIfDoesNotExist);
+                               }
+                       });
+
+               tester.startPage(new TestPage());
+
+               tester.assertContains("getElementById\\('b&amp;#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&quot;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>");
+               }
+       }
+}

Reply via email to